/** * Security Scanner — encoded (base64) payload detect-decode-recursively-rescan * @module @skillsmith/core/security/scanner/SecurityScanner.encoding * * SMI-6033 Wave 2 (Gap 2): before this file, `base64 -d | sh` (the DECODE * INVOCATION syntax) was already caught by CODE_EXECUTION_PATTERNS, but an * inert base64 blob the agent is merely instructed to decode and run itself * scored zero. * * Rather than a parallel "this looks suspicious" heuristic (high FP risk — * base64 blobs are extremely common in legitimate skills: image data URIs, * git hashes, JWTs), this detector DECODES the candidate and recursively runs * the caller's full detector suite against the decoded text, reusing the * whole pattern arsenal instead of duplicating it. The escalation this gap * achieves is free: a decoded `curl|bash` natively trips `code_execution` at * ITS OWN severity, exactly as if the attacker had shipped it undecoded. * * Depth-1-only, STRUCTURALLY (not by convention). This module never scans * itself — `rescan`, the callback the caller supplies, is expected to be the * caller's OWN "run every detector" entry point with the encoded-payload * detector specifically disabled for that inner call (see * `SecurityScanner.ts`'s `runDetectors(content, lineContexts, * skipEncodedPayload)`, where the recursive callback always passes `true`). * The inner call therefore cannot reach this detector again no matter what * the decoded content contains — a base64 blob discovered INSIDE decoded * content is never itself decoded. * * Candidate detection: a contiguous base64-alphabet run, * `[A-Za-z0-9+/]{120,}={0,2}`. Deliberately EXCLUDES `-`/`_` (base64url), so * a JWT never becomes a candidate at all — that is the intentional mechanism * keeping JWTs out, not a separate check. Bounded/ReDoS-safe: a single * character-class quantifier has no catastrophic-backtracking surface, so * this intentionally does NOT route through `safeRegexTest`'s 10,000-char * truncation — a legitimate base64 blob routinely runs past that on one long * line, and truncating it would silently defeat detection. * * The wrapper finding (`encoded_payload`) is advisory-tier ONLY (weight 1.2 / * coefficient 0.04 — the sensitive_path/typosquat tier, NOT the 2.0/0.40 tier * the other three Wave 2 detectors use, see weights.ts): this detector is * pure observability/provenance-marking, not itself a strong signal. Every * finding folded in from the decoded content carries a NEW `decodedFrom` * field (types.ts) set to the OUTER document line the blob was found on — * the same provenance-marker role `filePath` already plays for a * sibling-file finding, just for a decoded-blob origin instead of a * different file. */ import type { SecurityFinding } from './types.js'; import type { LineContext } from './SecurityScanner.helpers.js'; /** * Callback the caller (`SecurityScanner`) supplies to run its OWN full * detector suite against decoded text, with the encoded-payload detector * itself disabled — the structural depth-1 guarantee (see module header). */ export type EncodedPayloadRescanner = (decodedContent: string) => SecurityFinding[]; export declare function scanEncodedPayload(content: string, lineContexts: LineContext[] | undefined, rescan: EncodedPayloadRescanner): SecurityFinding[]; //# sourceMappingURL=SecurityScanner.encoding.d.ts.map