daemon-sec-lotl

DÆMONBins: GTFOBins × LOLBAS × WADComs × LOOBins in one filterable catalog
git clone https://git.daemon-sec.xyz/daemon-sec-lotl.git
Log | Files | Refs | Submodules | README | LICENSE

technique-schema.mjs (3569B)


      1 /**
      2  * The unified Technique vocabulary + Zod schema — the single source of truth
      3  * shared by the dataset builder (build-dataset.mjs), the standalone validator
      4  * (validate-data.mjs, run in CI over the committed JSON) and the one-shot
      5  * migrations. Keep it dependency-free beyond zod so it loads anywhere.
      6  *
      7  * Mirrors src/lib/taxonomy.ts; the TS side is the UI vocabulary, this is the
      8  * data contract. They must agree — validate-data.mjs cross-checks them.
      9  */
     10 import { z } from 'zod';
     11 
     12 export const PLATFORMS = ['Linux', 'Windows', 'macOS', 'ActiveDirectory'];
     13 export const SOURCES = ['GTFOBins', 'LOLBAS', 'WADComs', 'LOOBins', 'DAEMON'];
     14 export const CAPABILITIES = [
     15   'Execution', 'Reverse/Bind Shell',
     16   'File Download', 'File Upload', 'File Read', 'File Write', 'File Copy',
     17   'Library Load', 'Compile',
     18   'Privilege Escalation', 'UAC Bypass', 'AWL / Policy Bypass',
     19   'Defense Evasion', 'Credential Access',
     20   'Discovery', 'Enumeration', 'Persistence', 'Lateral Movement', 'Collection',
     21 ];
     22 
     23 // Only absolute http(s) URLs may become <a href> — a stray `javascript:` or
     24 // relative reference from an upstream merge must fail validation, not render.
     25 const HTTP_URL = /^https?:\/\/\S+$/;
     26 export const HttpUrl = z.string().regex(HTTP_URL, 'must be an absolute http(s) URL');
     27 
     28 export const TechniqueSchema = z.object({
     29   id: z.string().min(1),
     30   toolId: z.string().min(1),
     31   toolName: z.string().min(1),
     32   name: z.string().optional(),
     33   source: z.enum(SOURCES),
     34   platform: z.array(z.enum(PLATFORMS)).min(1),
     35   capability: z.array(z.enum(CAPABILITIES)),
     36   nativeCategory: z.array(z.string()),
     37   command: z.string().min(1),
     38   description: z.string().optional(),
     39   usecase: z.string().optional(),
     40   mitre: z.array(z.string().regex(/^T\d{4}(\.\d{3})?$/, 'must be a MITRE ATT&CK technique id')),
     41   privilege: z.string().optional(),
     42   context: z.string().optional(),
     43   requires: z.array(z.string()).optional(),
     44   services: z.array(z.string()).optional(),
     45   fullPath: z.array(z.string()).optional(),
     46   toolType: z.string().optional(),
     47   detection: z.array(z.object({ type: z.string(), value: z.string() })).optional(),
     48   references: z.array(HttpUrl),
     49   added: z.boolean().optional(),
     50   verifyNote: z.string().optional(),
     51   environment: z.array(z.string()).optional(),
     52   aliases: z.array(z.string()).optional(),
     53   availability: z.string().optional(),
     54   verification: z.enum(['Upstream reference', 'Documentation checked', 'Lab tested']).optional(),
     55   reviewedAt: z.string().optional(),
     56   compatibility: z.string().optional(),
     57   expected: z.string().optional(),
     58   troubleshooting: z.string().optional(),
     59   sideEffects: z.string().optional(),
     60   restore: z.string().optional(),
     61   template: z.object({
     62     command: z.string(), shell: z.enum(['posix', 'powershell']),
     63     variables: z.array(z.object({ key: z.string().regex(/^[a-z][a-z0-9_]*$/), label: z.string(), default: z.string() })),
     64   }).optional(),
     65 });
     66 
     67 /**
     68  * Validate a whole Technique[]: schema per record + unique ids. Returns a list
     69  * of human-readable problems (empty = valid). Pure; callers decide how to fail.
     70  */
     71 export function validateTechniques(techniques) {
     72   const problems = [];
     73   const ids = new Set();
     74   for (const t of techniques) {
     75     const r = TechniqueSchema.safeParse(t);
     76     if (!r.success) {
     77       const why = r.error.issues.map((i) => `${i.path.join('.')} ${i.message}`).join('; ');
     78       problems.push(`invalid ${t && t.id ? t.id : '<no id>'}: ${why}`);
     79       continue;
     80     }
     81     if (ids.has(t.id)) problems.push(`duplicate id ${t.id}`);
     82     ids.add(t.id);
     83   }
     84   return problems;
     85 }