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

astro.config.mjs (7925B)


      1 // @ts-check
      2 import { defineConfig } from 'astro/config';
      3 import mdx from '@astrojs/mdx';
      4 import sitemap from '@astrojs/sitemap';
      5 import Slugger from 'github-slugger';
      6 
      7 /**
      8  * MkDocs anchor aliases for the InternalAllTheThings mirror.
      9  *
     10  * Upstream is a MkDocs site, and python-markdown's slugifier collapses every
     11  * run of `[-\s]` to one dash where github-slugger (Astro's) keeps them: the
     12  * heading "Meterpreter - Basic" is `#meterpreter-basic` upstream and
     13  * `#meterpreter---basic` here. Every deep link ever written against the
     14  * upstream page carries the MkDocs form — outside bookmarks and search hits,
     15  * and the IATT links this site rewrites out of the PayloadsAllTheThings stubs
     16  * — so without an alias they all land at the top of the page.
     17  *
     18  * The github-slugger id stays primary, because IATT's own in-page tables of
     19  * contents are written GitHub-style and resolve against it (they are in fact
     20  * broken on the live upstream site). The MkDocs form is emitted as an empty
     21  * span *inside* the heading: a sibling before it would break
     22  * `.prose > h1:first-child`, which hides the duplicated page title.
     23  *
     24  * Ids are assigned here rather than left to Astro because user rehype plugins
     25  * run before its heading-id pass, so the id has to exist to be compared
     26  * against. Astro keeps an id that is already a string, so this is the same
     27  * value it would have written.
     28  */
     29 const RAW_NODE_TYPES = new Set(['text', 'raw', 'mdxTextExpression']);
     30 const CODE_TAG_NAMES = new Set(['code', 'pre']);
     31 
     32 /** python-markdown's `toc.slugify`, separator "-". */
     33 function mkdocsSlug(text) {
     34   return text
     35     .normalize('NFKD')
     36     .replace(/[^\x00-\x7f]/g, '')
     37     .replace(/[^\w\s-]/g, '')
     38     .trim()
     39     .toLowerCase()
     40     .replace(/[-\s]+/g, '-');
     41 }
     42 
     43 /** python-markdown disambiguates a repeat with `_1`, `_2`; github-slugger `-1`. */
     44 function uniqueMkdocsSlug(slug, used) {
     45   let id = slug;
     46   while (!id || used.has(id)) {
     47     const m = /^(.*)_(\d+)$/.exec(id);
     48     id = m ? `${m[1]}_${Number(m[2]) + 1}` : `${id}_1`;
     49   }
     50   used.add(id);
     51   return id;
     52 }
     53 
     54 // Mirrors @astrojs/markdown-remark's own heading-text collection, so the two
     55 // slugifiers are fed byte-identical input.
     56 function headingText(heading) {
     57   let text = '';
     58   const walk = (node, parent) => {
     59     if (node.type !== 'element' && parent !== null && RAW_NODE_TYPES.has(node.type)) {
     60       if (!(node.type === 'raw' && /^\n?<.*>\n?$/.test(node.value))) {
     61         text += CODE_TAG_NAMES.has(parent.tagName)
     62           ? node.value
     63           : node.value.replace(/\{/g, '${');
     64       }
     65     }
     66     if (node.children) for (const child of node.children) walk(child, node);
     67   };
     68   walk(heading, null);
     69   return text;
     70 }
     71 
     72 function rehypeMkdocsAnchors() {
     73   return (tree, file) => {
     74     const source = (file?.history?.[0] ?? '').replace(/\\/g, '/');
     75     if (!source.includes('/src/content/internal/')) return;
     76 
     77     const slugger = new Slugger();
     78     const mkdocsUsed = new Set();
     79     const walk = (node) => {
     80       if (node.type === 'element' && /^h[1-6]$/.test(node.tagName)) {
     81         const text = headingText(node);
     82         node.properties = node.properties || {};
     83         if (typeof node.properties.id !== 'string') {
     84           const slug = slugger.slug(text);
     85           node.properties.id = slug.endsWith('-') ? slug.slice(0, -1) : slug;
     86         }
     87         const alias = uniqueMkdocsSlug(mkdocsSlug(text), mkdocsUsed);
     88         if (alias !== node.properties.id) {
     89           node.children.unshift({
     90             type: 'element',
     91             tagName: 'span',
     92             properties: { id: alias, className: ['anchor-alias'] },
     93             children: [],
     94           });
     95         }
     96         return; // headings do not nest
     97       }
     98       if (node.children) for (const child of node.children) walk(child);
     99     };
    100     walk(tree);
    101   };
    102 }
    103 
    104 /**
    105  * The drop cap, as the main site sets it: the opening letter of a sheet
    106  * floated three lines deep in EB Garamond.
    107  *
    108  * The letter is split out of the paragraph's first text node and wrapped
    109  * in a `<span class="cap">` rather than left to `::first-letter`. That
    110  * keeps it real text — selectable, searchable, and read aloud as part of
    111  * the word it opens — and it is the only way to give it a different family
    112  * and weight that renders the same across engines.
    113  *
    114  * Only the first top-level paragraph of the document takes one, and only
    115  * if it is long enough to have three lines for the letter to sit in.
    116  * Short openers — an image caption, a one-line note, a lede that is really
    117  * a subtitle — are left alone, because a cap floated into a two-line
    118  * paragraph pushes the text into a column beside it.
    119  */
    120 const CAP_MIN_CHARS = 80;
    121 
    122 function rehypeDropCap() {
    123   return (tree) => {
    124     const paragraph = tree.children.find(
    125       (n) => n.type === 'element' && n.tagName === 'p' && textLength(n) >= CAP_MIN_CHARS,
    126     );
    127     if (!paragraph) return;
    128 
    129     // The first non-empty text node, wherever it sits — the paragraph may
    130     // legitimately open with a <strong> or an <a>, and the cap belongs to
    131     // the first *letter* rather than to the first direct child.
    132     const lead = firstText(paragraph);
    133     if (!lead) return;
    134     const trimmed = lead.value.trimStart();
    135     const letter = trimmed[0];
    136     // Punctuation and quotes make poor caps: floated at 72px an opening
    137     // quote reads as a stray mark rather than as a letter.
    138     if (!letter || !/[A-Za-z0-9]/.test(letter)) return;
    139 
    140     lead.value = trimmed.slice(1);
    141     paragraph.children.unshift({
    142       type: 'element',
    143       tagName: 'span',
    144       properties: { className: ['cap'] },
    145       children: [{ type: 'text', value: letter }],
    146     });
    147   };
    148 
    149   function textLength(node) {
    150     if (node.type === 'text') return node.value.trim().length;
    151     if (!node.children) return 0;
    152     return node.children.reduce((n, c) => n + textLength(c), 0);
    153   }
    154 
    155   function firstText(node) {
    156     if (!node.children) return null;
    157     for (const child of node.children) {
    158       if (child.type === 'text' && child.value.trim()) return child;
    159       if (child.type === 'element') {
    160         const found = firstText(child);
    161         if (found) return found;
    162       }
    163     }
    164     return null;
    165   }
    166 }
    167 
    168 // Deployed to Cloudflare Workers at the domain root — no base path.
    169 // `site` feeds the sitemap and canonical URLs, so it must be the live custom
    170 // domain from wrangler.jsonc, not a platform-generated hostname.
    171 export default defineConfig({
    172   site: 'https://cheatsheet.daemon-sec.xyz',
    173   trailingSlash: 'ignore',
    174   integrations: [mdx(), sitemap()],
    175   markdown: {
    176     rehypePlugins: [rehypeMkdocsAnchors, rehypeDropCap],
    177     shikiConfig: {
    178       // One theme, not two. Code blocks are dark plates in both modes (see
    179       // prose.css) — a listing that turns cream in light mode stops reading
    180       // as terminal output — so there is no second palette to flip to.
    181       theme: 'rose-pine-moon',
    182       wrap: false,
    183       // Fence languages the sheets actually use, mapped onto grammars Shiki
    184       // ships. Without these the block silently falls back to plaintext, so a
    185       // listing that should be highlighted reads as flat text.
    186       //
    187       // The capitalised three come from the generated mirrors
    188       // (payloads/, internal/) where upstream wrote ```SQL and Shiki's lookup
    189       // is case-sensitive. Those files are rewritten by the sync scripts, so
    190       // the alias is the only place the fix can live.
    191       //
    192       // The rest have no grammar of their own; each is mapped to the closest
    193       // one that reads correctly rather than left to plaintext. `smali` is
    194       // deliberately absent — nothing Shiki ships resembles it, so plaintext
    195       // is the honest result.
    196       langAlias: {
    197         SQL: 'sql',
    198         Javascript: 'javascript',
    199         C: 'c',
    200         conf: 'ini',
    201         svg: 'xml',
    202         yara: 'c',
    203         zeek: 'c',
    204         ldif: 'yaml',
    205         django: 'html',
    206         dataviewjs: 'javascript',
    207       },
    208     },
    209   },
    210 });