fix(llm-security): normalization/discovery evasion + SIG embedded-base64 & custom rules (#21,#23,#30,#36,#42,#52,#55)

#21 the bash normalizer decoded only \xHH, leaving ANSI-C octal/\u/\U forms literal so canonical rm/curl never surfaced; now decodes all three. #23 file-discovery keyed on extname so .env.local/.env.example (extname .local/.example) were silently skipped; now matches multi-part suffixes. #42 a legitimate leading UTF-8 BOM was flagged HIGH (and the tool's own auto-cleaner refused to strip it); pos-0 BOM now excepted. #52 collapseLetterSpacing used a literal space, letting multi-space/tab spacing evade; now [ \t]+. #55 redact(_,60,0) did slice(-0) and leaked the whole unredacted URL; showEnd===0 now means no tail.

#30 embedded base64 (const x = "<base64>") never satisfied the whole-string decode, so the SIG identity engine never saw it; added decodeEmbeddedBase64 as an OPT-IN param on normalizeForScan (default off — appending a decoded copy would double-count per-match findings, e.g. content-extractor's injection scan) and enabled it only in signature-scanner, which dedups variants. #36 signature-scanner ignored the documented sig.custom_rules_path policy option; now loads+merges custom rules through the same family filter. Suite 2004/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
This commit is contained in:
Kjell Tore Guttormsen 2026-07-18 10:35:56 +02:00
commit 21c6c2b534
11 changed files with 602 additions and 42 deletions

View file

@ -92,7 +92,11 @@ function scanLineForZeroWidth(line, lineNumber, relPath) {
for (const char of line) {
const cp = char.codePointAt(0);
if (ZERO_WIDTH_CHARS.has(cp)) {
hits.push({ cp, pos });
// Preserve a legitimate UTF-8 BOM at file position 0 (line 1, col 0) —
// mirrors auto-cleaner's stripZeroWidth exception (#42, v7.8.3).
if (!(cp === 0xFEFF && lineNumber === 1 && pos === 0)) {
hits.push({ cp, pos });
}
}
pos += char.length; // codePointAt handles surrogates; advance by JS char count
}