test(llm-security): add red a2 sub-probe for SIG payload literals in test sources

(a) keeps .mjs/.js out of its denominator, so contiguous payload literals in
test sources were ungated. a2 walks tests/**/*.{mjs,js} (tests/golden/**
excluded) and matches the webshell/reverse_shell/cryptominer SIG rules on raw
text. Measured red: a2=3 (scan-pipeline e2e, signature-scanner,
signature-scanner-custom-rules) of 116 test sources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-22 13:00:50 +02:00
commit 30a179a88e
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q

View file

@ -34,6 +34,12 @@
// denominator: decoding compressed PNG/WOFF2 bytes as UTF-8 yields these // denominator: decoding compressed PNG/WOFF2 bytes as UTF-8 yields these
// codepoints by chance, and no reader ever sees them as characters. // codepoints by chance, and no reader ever sees them as characters.
// (d) any tracked file under a known payload tree. // (d) any tracked file under a known payload tree.
// (a2) a test source (tests/**/*.mjs|.js, tests/golden/** excluded) whose RAW
// text holds a contiguous literal matching one of the same SIG rules.
// Raw bytes only, comments included: this is what sits on disk, and a
// payload built at test time from fragments ('ev' + 'al') or rot13 does
// not match here — which is the point. Added in S1 (2026-09-22) because
// (a) keeps .mjs out of its denominator.
// //
// This test was written RED on purpose (order S0, 2026-09-22): it is the // This test was written RED on purpose (order S0, 2026-09-22): it is the
// failing test for sessions S1-S3 of the v8.1.0 plan. It is expected to fail // failing test for sessions S1-S3 of the v8.1.0 plan. It is expected to fail
@ -54,6 +60,7 @@ const PAYLOAD_FAMILIES = new Set(['webshell', 'reverse_shell', 'cryptominer']);
const QUOTING_HOSTS = new Set(['.mjs', '.js', '.cjs', '.json']); const QUOTING_HOSTS = new Set(['.mjs', '.js', '.cjs', '.json']);
const VENDORED = 'scanners/commons/'; const VENDORED = 'scanners/commons/';
const CONFORMANCE = 'scanners/commons/conformance/'; const CONFORMANCE = 'scanners/commons/conformance/';
const TEST_SOURCE = /^tests\/(?!golden\/).*\.(?:mjs|js)$/;
// A line counts as a comment when it opens with a comment marker. `#!` is a // A line counts as a comment when it opens with a comment marker. `#!` is a
// shebang, not a comment, and stays in. // shebang, not a comment, and stays in.
@ -90,6 +97,8 @@ function measureAvSurface() {
const a = []; const a = [];
const b = []; const b = [];
const c = []; const c = [];
const a2 = [];
let testSources = 0;
let textFiles = 0; let textFiles = 0;
for (const rel of files) { for (const rel of files) {
@ -119,6 +128,12 @@ function measureAvSurface() {
} }
if (!rel.startsWith(CONFORMANCE) && CARRIER.test(text)) c.push(rel); if (!rel.startsWith(CONFORMANCE) && CARRIER.test(text)) c.push(rel);
if (TEST_SOURCE.test(rel)) {
testSources++;
const hits = rules.filter(r => r.re.test(text)).map(r => r.id);
if (hits.length > 0) a2.push(`${rel} [${hits.join(', ')}]`);
}
} }
const d = PAYLOAD_TREES const d = PAYLOAD_TREES
@ -129,6 +144,8 @@ function measureAvSurface() {
tracked: files.length, tracked: files.length,
textFiles, textFiles,
a, a,
a2,
testSources,
b, b,
bFiles: new Set(b.map(x => x.split(' :: ')[0])).size, bFiles: new Set(b.map(x => x.split(' :: ')[0])).size,
c, c,
@ -146,6 +163,11 @@ describe('av-surface: tracked tree carries no AV-triggering payloads', () => {
assert.deepEqual(m.a, [], report('files with a SIG payload', m.a)); assert.deepEqual(m.a, [], report('files with a SIG payload', m.a));
}); });
it(`(a2) no contiguous SIG payload literal in a test source (of ${m.testSources} test sources)`, (t) => {
t.diagnostic(`a2=${m.a2.length}`);
assert.deepEqual(m.a2, [], report('test sources with a SIG payload literal', m.a2));
});
it(`(b) no base64 blob decoding to a shell command (of ${m.textFiles} text files)`, (t) => { it(`(b) no base64 blob decoding to a shell command (of ${m.textFiles} text files)`, (t) => {
t.diagnostic(`b=${m.b.length} blobs in ${m.bFiles} files`); t.diagnostic(`b=${m.b.length} blobs in ${m.bFiles} files`);
assert.deepEqual(m.b, [], report('base64 blobs', m.b)); assert.deepEqual(m.b, [], report('base64 blobs', m.b));