daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

app.ts (17280B)


      1 // Progressive enhancement, re-run after every (view-transition) navigation.
      2 
      3 function applyTheme(next: 'light' | 'dark'): void {
      4   document.documentElement.setAttribute('data-theme', next);
      5   try { localStorage.setItem('theme', next); } catch {}
      6   document.documentElement.style.colorScheme = next;
      7   // A canvas bitmap does not inherit the cascade, so the banners' signal
      8   // field has no way to notice the mode changed. It listens for this.
      9   dispatchEvent(new Event('daemonmodechange'));
     10 }
     11 
     12 function initTheme(): void {
     13   const toggles = document.querySelectorAll<HTMLButtonElement>('[data-theme-toggle]');
     14   toggles.forEach((btn) => {
     15     if (btn.dataset.bound) return;
     16     btn.dataset.bound = '1';
     17     btn.addEventListener('click', () => {
     18       const cur = document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
     19       const next: 'light' | 'dark' = cur === 'light' ? 'dark' : 'light';
     20       const root = document.documentElement;
     21 
     22       // Feature/consent gate: no View Transitions or reduced-motion -> flip instantly.
     23       const startVT = (document as unknown as {
     24         startViewTransition?: (cb: () => void) => { finished: Promise<void> };
     25       }).startViewTransition;
     26       const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
     27       if (!startVT || reduce) { applyTheme(next); return; }
     28 
     29       // Origin + radius for the circular reveal: expand from the toggle's centre
     30       // out to the farthest viewport corner so the circle always covers the screen.
     31       const r = btn.getBoundingClientRect();
     32       const x = r.left + r.width / 2;
     33       const y = r.top + r.height / 2;
     34       const endR = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
     35       root.style.setProperty('--vt-x', `${x}px`);
     36       root.style.setProperty('--vt-y', `${y}px`);
     37       root.style.setProperty('--vt-r', `${endR}px`);
     38 
     39       root.classList.add('theme-vt');
     40       const t = startVT.call(document, () => applyTheme(next));
     41       t.finished.finally(() => root.classList.remove('theme-vt'));
     42     });
     43   });
     44 }
     45 
     46 function initMobileNav(): void {
     47   const toggles = document.querySelectorAll<HTMLButtonElement>('[data-mobile-nav-toggle]');
     48   toggles.forEach((toggle) => {
     49     if (toggle.dataset.bound) return;
     50     const menuId = toggle.getAttribute('aria-controls');
     51     const menu = menuId ? document.getElementById(menuId) : null;
     52     if (!menu) return;
     53 
     54     const setOpen = (open: boolean): void => {
     55       toggle.setAttribute('aria-expanded', String(open));
     56       toggle.setAttribute('aria-label', open ? 'Close navigation' : 'Open navigation');
     57       menu.hidden = !open;
     58     };
     59 
     60     toggle.dataset.bound = '1';
     61     toggle.addEventListener('click', () => {
     62       setOpen(toggle.getAttribute('aria-expanded') !== 'true');
     63     });
     64 
     65     menu.addEventListener('click', (event) => {
     66       if ((event.target as Element).closest('a')) setOpen(false);
     67     });
     68 
     69     toggle.closest('.site-header')?.addEventListener('keydown', (event) => {
     70       if (event.key !== 'Escape' || toggle.getAttribute('aria-expanded') !== 'true') return;
     71       setOpen(false);
     72       toggle.focus();
     73     });
     74   });
     75 }
     76 
     77 /* The retired site resolved labels left-to-right through this exact ASCII
     78    pool over nine 32ms steps. Keeping the source string in data-unscramble
     79    means the page is correct before JS, remains searchable, and exposes the
     80    final label to assistive technology while the visual text briefly settles. */
     81 const UNSCRAMBLE_POOL = '/\\|<>=+*·#%';
     82 const UNSCRAMBLE_STEPS = 9;
     83 
     84 function scrambled(text: string, step: number): string {
     85   const settled = Math.ceil((text.length * step) / UNSCRAMBLE_STEPS);
     86   let value = text.slice(0, settled);
     87   for (let i = settled; i < text.length; i += 1) {
     88     const char = text[i];
     89     value += /\s/.test(char)
     90       ? char
     91       : UNSCRAMBLE_POOL[(i * 7 + step * 3) % UNSCRAMBLE_POOL.length];
     92   }
     93   return value;
     94 }
     95 
     96 function initUnscramble(): void {
     97   document.querySelectorAll<HTMLElement>('.sheet-title, .subcat h2, .download-library__head h2, .prose > h2').forEach((node) => {
     98     if (!node.hasAttribute('data-unscramble')) node.dataset.unscramble = node.textContent ?? '';
     99   });
    100 
    101   const nodes = document.querySelectorAll<HTMLElement>('[data-unscramble]:not([data-unscramble-bound])');
    102   const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    103 
    104   const resolve = (node: HTMLElement, finalText: string): void => {
    105     let step = 0;
    106     node.textContent = scrambled(finalText, step);
    107     const timer = window.setInterval(() => {
    108       step += 1;
    109       if (step >= UNSCRAMBLE_STEPS) {
    110         clearInterval(timer);
    111         node.textContent = finalText;
    112         return;
    113       }
    114       node.textContent = scrambled(finalText, step);
    115     }, 32);
    116   };
    117 
    118   const observer = !reduced && 'IntersectionObserver' in window
    119     ? new window.IntersectionObserver((entries, current) => {
    120         entries.forEach((entry) => {
    121           if (!entry.isIntersecting) return;
    122           const node = entry.target as HTMLElement;
    123           resolve(node, node.dataset.unscramble ?? node.textContent ?? '');
    124           current.unobserve(node);
    125         });
    126       }, { rootMargin: '0px 0px -8% 0px', threshold: 0.25 })
    127     : null;
    128 
    129   nodes.forEach((node) => {
    130     node.dataset.unscrambleBound = '1';
    131     const finalText = node.dataset.unscramble ?? node.textContent ?? '';
    132     node.setAttribute('aria-label', finalText);
    133     if (reduced || !finalText) {
    134       node.textContent = finalText;
    135       return;
    136     }
    137 
    138     const rect = node.getBoundingClientRect();
    139     if (!observer || rect.top < window.innerHeight * 0.92) resolve(node, finalText);
    140     else observer.observe(node);
    141   });
    142 }
    143 
    144 /**
    145  * Bring the archive into view in short, staggered runs as the reader moves
    146  * down a page. The hidden state is applied by script rather than baked into
    147  * the stylesheet, so every word remains visible if JavaScript is unavailable.
    148  */
    149 function initScrollReveal(): void {
    150   const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    151   const nodes = document.querySelectorAll<HTMLElement>([
    152     '.subcat:not([data-reveal-bound])',
    153     '.download-library__head:not([data-reveal-bound])',
    154     '.rec:not([data-reveal-bound])',
    155     '.download-row:not([data-reveal-bound])',
    156     '.prose > h2:not([data-reveal-bound])',
    157     '.prose > h3:not([data-reveal-bound])',
    158     '.prose > p:not([data-reveal-bound])',
    159     '.prose > blockquote:not([data-reveal-bound])',
    160     '.prose > figure:not([data-reveal-bound])',
    161     '.prose > .table-scroll:not([data-reveal-bound])',
    162   ].join(','));
    163 
    164   if (!nodes.length) return;
    165 
    166   nodes.forEach((node, index) => {
    167     node.dataset.revealBound = '1';
    168     // Only nearby siblings cascade; long indexes should not accumulate delay.
    169     node.style.setProperty('--reveal-delay', `${(index % 6) * 70}ms`);
    170     if (reduced) node.classList.add('is-revealed');
    171     else node.classList.add('scroll-reveal');
    172   });
    173 
    174   if (reduced || !('IntersectionObserver' in window)) {
    175     nodes.forEach((node) => node.classList.add('is-revealed'));
    176     return;
    177   }
    178 
    179   const observer = new window.IntersectionObserver((entries, current) => {
    180     entries.forEach((entry) => {
    181       if (!entry.isIntersecting) return;
    182       (entry.target as HTMLElement).classList.add('is-revealed');
    183       current.unobserve(entry.target);
    184     });
    185   }, { rootMargin: '0px 0px -9% 0px', threshold: 0.06 });
    186 
    187   nodes.forEach((node) => {
    188     const rect = node.getBoundingClientRect();
    189     if (rect.top < window.innerHeight * 0.94) node.classList.add('is-revealed');
    190     else observer.observe(node);
    191   });
    192 }
    193 
    194 /** A two-pixel signal line makes page position readable on the longer CPTS sheets. */
    195 function initScrollProgress(): void {
    196   let progress = document.querySelector<HTMLElement>('[data-scroll-progress]');
    197   if (!progress) {
    198     progress = document.createElement('div');
    199     progress.className = 'scroll-progress';
    200     progress.dataset.scrollProgress = '1';
    201     progress.setAttribute('aria-hidden', 'true');
    202     document.body.append(progress);
    203   }
    204   if (progress.dataset.bound) return;
    205   progress.dataset.bound = '1';
    206 
    207   let ticking = false;
    208   const update = (): void => {
    209     const range = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
    210     progress!.style.setProperty('--scroll-progress', String(Math.min(1, Math.max(0, window.scrollY / range))));
    211     ticking = false;
    212   };
    213   const requestUpdate = (): void => {
    214     if (ticking) return;
    215     ticking = true;
    216     window.requestAnimationFrame(update);
    217   };
    218 
    219   window.addEventListener('scroll', requestUpdate, { passive: true });
    220   window.addEventListener('resize', requestUpdate, { passive: true });
    221   update();
    222 }
    223 
    224 /**
    225  * Every code block becomes the archive's terminal block: the language at
    226  * the left of a caption bar, the copy control at the right, both in the
    227  * 9.5px mono the rest of the site labels things in.
    228  *
    229  * No window chrome and no traffic-light dots — this is a printed listing
    230  * of terminal output, not a simulated window. The plate under it stays
    231  * dark in both modes (see prose.css).
    232  */
    233 const COPY_ICON =
    234   '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12"/><path d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"/></svg>';
    235 const CHECK_ICON =
    236   '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>';
    237 
    238 function enhanceCode(): void {
    239   const blocks = document.querySelectorAll<HTMLPreElement>('.prose pre.astro-code:not([data-enhanced])');
    240   blocks.forEach((pre) => {
    241     pre.dataset.enhanced = '1';
    242     const lang = pre.getAttribute('data-language') || 'text';
    243 
    244     const pane = document.createElement('figure');
    245     /* `.plate` pins the night palette for the pane's subtree so the caption
    246        tokens and corner brackets read as a terminal on the cream page too;
    247        `.corners` adds the cyberdeck brackets; the pane is `position: relative`
    248        in prose.css, so both anchor without further help. */
    249     pane.className = 'code-pane plate corners';
    250     const bar = document.createElement('figcaption');
    251     bar.className = 'code-pane__bar';
    252 
    253     const langEl = document.createElement('span');
    254     langEl.className = 'code-pane__lang';
    255     langEl.textContent = lang.replace(/[^a-z0-9+#-]/gi, '').slice(0, 16) || 'text';
    256 
    257     const copy = document.createElement('button');
    258     copy.className = 'code-copy';
    259     copy.type = 'button';
    260     const setLabel = (icon: string, word: string) => {
    261       copy.innerHTML = icon;
    262       copy.append(document.createTextNode(word));
    263     };
    264     setLabel(COPY_ICON, 'Copy');
    265 
    266     // The feedback lives on the button for 1400ms rather than in a toast
    267     // across the page — you copied this block, not the document.
    268     copy.addEventListener('click', async () => {
    269       const text = pre.innerText;
    270       try {
    271         await navigator.clipboard.writeText(text);
    272       } catch {
    273         const ta = document.createElement('textarea');
    274         ta.value = text; document.body.appendChild(ta); ta.select();
    275         try { document.execCommand('copy'); } catch {}
    276         ta.remove();
    277       }
    278       setLabel(CHECK_ICON, 'Copied');
    279       copy.classList.add('copied');
    280       setTimeout(() => { setLabel(COPY_ICON, 'Copy'); copy.classList.remove('copied'); }, 1400);
    281     });
    282 
    283     bar.append(langEl, copy);
    284     pre.parentNode?.insertBefore(pane, pre);
    285     pane.append(bar, pre);
    286 
    287     // The CRT film. Its own element rather than a pseudo, because `.corners`
    288     // has already spent both of the pane's pseudo-elements.
    289     const film = document.createElement('div');
    290     film.className = 'scanlines';
    291     film.setAttribute('aria-hidden', 'true');
    292     pane.append(film);
    293   });
    294 }
    295 
    296 /**
    297  * Obsidian/GitHub `> [!type]` callouts. The markdown pipeline renders these
    298  * as plain blockquotes whose first line is the literal `[!type]` marker (no
    299  * rehype plugin is in the build, by design). Here every blockquote is framed
    300  * as a terminal callout (.callout + .corners, styled in prose.css); when a
    301  * marker is present its type keys the accent and a mono label replaces the
    302  * marker text. Plain blockquotes are left with the frame and no label, so the
    303  * page reads right with JS off too.
    304  */
    305 const CALLOUT_ALIASES: Record<string, string> = {
    306   note: 'note', abstract: 'note', summary: 'note', tldr: 'note',
    307   info: 'info', todo: 'info',
    308   tip: 'tip', hint: 'tip', important: 'tip',
    309   success: 'success', check: 'success', done: 'success',
    310   question: 'question', help: 'question', faq: 'question',
    311   warning: 'warning', caution: 'warning', attention: 'warning',
    312   failure: 'danger', fail: 'danger', missing: 'danger',
    313   danger: 'danger', error: 'danger', bug: 'danger',
    314   example: 'example', quote: 'quote', cite: 'quote',
    315 };
    316 const CALLOUT_LABEL: Record<string, string> = {
    317   note: 'Note', info: 'Info', tip: 'Tip', success: 'Success',
    318   question: 'FAQ', warning: 'Warning', danger: 'Danger',
    319   example: 'Example', quote: 'Quote',
    320 };
    321 
    322 function firstTextNode(el: Element): Text | null {
    323   const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
    324   let n = walker.nextNode();
    325   while (n && !(n.textContent ?? '').trim()) n = walker.nextNode();
    326   return (n as Text) ?? null;
    327 }
    328 
    329 function enhanceCallouts(): void {
    330   document.querySelectorAll<HTMLQuoteElement>('.prose blockquote:not([data-callout-done])').forEach((bq) => {
    331     bq.dataset.calloutDone = '1';
    332     bq.classList.add('callout', 'corners');
    333 
    334     const first = bq.firstElementChild;
    335     if (!first) return;
    336     const node = firstTextNode(first);
    337     if (!node) return;
    338     const m = (node.textContent ?? '').match(/^\s*\[!(\w+)\]([+-]?)\s?/);
    339     if (!m) return;
    340 
    341     const variant = CALLOUT_ALIASES[m[1].toLowerCase()] ?? 'note';
    342     bq.setAttribute('data-callout', variant);
    343     node.textContent = (node.textContent ?? '').slice(m[0].length);
    344     // If the marker was the whole first line, drop the now-empty node so the
    345     // label is not trailed by a blank paragraph.
    346     if (!(first.textContent ?? '').trim() && !first.querySelector('*')) first.remove();
    347 
    348     const label = document.createElement('div');
    349     label.className = 'callout__label';
    350     label.textContent = CALLOUT_LABEL[variant] ?? variant;
    351     bq.insertBefore(label, bq.firstChild);
    352   });
    353 }
    354 
    355 function wrapTables(): void {
    356   document.querySelectorAll<HTMLTableElement>('.prose table:not([data-wrapped])').forEach((t) => {
    357     t.dataset.wrapped = '1';
    358     const scroll = document.createElement('div');
    359     scroll.className = 'table-scroll';
    360     t.parentNode?.insertBefore(scroll, t);
    361     scroll.appendChild(t);
    362   });
    363 }
    364 
    365 function initTOC(): void {
    366   const toc = document.querySelector('.toc');
    367   if (!toc) return;
    368   const links = Array.from(toc.querySelectorAll<HTMLAnchorElement>('a[href^="#"]'));
    369   if (!links.length) return;
    370   const byId = new Map(links.map((l) => [decodeURIComponent(l.getAttribute('href')!.slice(1)), l]));
    371   const heads = Array.from(document.querySelectorAll<HTMLElement>('.prose h2[id], .prose h3[id]'));
    372   const obs = new IntersectionObserver(
    373     (entries) => {
    374       entries.forEach((e) => {
    375         if (!e.isIntersecting) return;
    376         const id = (e.target as HTMLElement).id;
    377         links.forEach((l) => l.classList.remove('active'));
    378         byId.get(id)?.classList.add('active');
    379       });
    380     },
    381     { rootMargin: '-70px 0px -70% 0px', threshold: 0 }
    382   );
    383   heads.forEach((h) => obs.observe(h));
    384 }
    385 
    386 // `init` re-runs on every view-transition swap, so the window listener is
    387 // bound once here and re-reads the current button each time it fires;
    388 // binding inside `initBackToTop` would stack a listener per navigation.
    389 let backToTopBound = false;
    390 
    391 function syncBackToTop(): void {
    392   const btn = document.querySelector<HTMLButtonElement>('#to-top');
    393   if (btn) btn.classList.toggle('is-visible', window.scrollY > 600);
    394 }
    395 
    396 function initBackToTop(): void {
    397   const btn = document.querySelector<HTMLButtonElement>('#to-top');
    398   if (!btn) return;
    399   // A page that barely scrolls does not need the control at all, so it stays
    400   // `hidden` rather than fading in over a screenful of content.
    401   if (document.documentElement.scrollHeight < window.innerHeight * 2) return;
    402   btn.hidden = false;
    403 
    404   btn.addEventListener('click', () => {
    405     const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    406     window.scrollTo({ top: 0, behavior: reduced ? 'auto' : 'smooth' });
    407     // Focus follows the scroll so keyboard and screen-reader users land at the
    408     // top of the document rather than staying parked on the button.
    409     document.querySelector<HTMLElement>('#main')?.focus({ preventScroll: true });
    410   });
    411 
    412   if (!backToTopBound) {
    413     window.addEventListener('scroll', syncBackToTop, { passive: true });
    414     backToTopBound = true;
    415   }
    416   syncBackToTop();
    417 }
    418 
    419 function init(): void {
    420   initTheme();
    421   initMobileNav();
    422   initUnscramble();
    423   enhanceCode();
    424   enhanceCallouts();
    425   wrapTables();
    426   initScrollReveal();
    427   initScrollProgress();
    428   initTOC();
    429   initBackToTop();
    430 }
    431 
    432 document.addEventListener('astro:page-load', init);
    433 if (document.readyState !== 'loading') init();
    434 else document.addEventListener('DOMContentLoaded', init);