test(av-surface): probe (e) — payload literals in session-loaded files, red
New probe (e) over agents/**, commands/** and hooks/** — what a Claude Code session loads at the user's end, where a quarantine breaks the installed plugin, not just a clone. It fails on a contiguous literal matching the SIG reverse-shell/webshell/miner rules, a download piped into a shell, or a base64 decode piped into a shell. Raw text, comments included, per line, so every hit names file:line. knowledge/** is measured by the same rule and reported in the diagnostic as its own number, not gated in S3. Measured red before any change (v8.1.0 S3, 2026-09-22): e = 8 hits in 6 of 36 files; knowledge = 17 hits of 22 files. No SIG rule hit anywhere in the four directories; validated against built known positives (all three families match). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
52084204da
commit
e6a7aec971
1 changed files with 53 additions and 0 deletions
|
|
@ -49,6 +49,17 @@
|
||||||
// payload built at test time from fragments ('ev' + 'al') or rot13 does
|
// 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
|
// not match here — which is the point. Added in S1 (2026-09-22) because
|
||||||
// (a) keeps .mjs out of its denominator.
|
// (a) keeps .mjs out of its denominator.
|
||||||
|
// (e) a file under agents/**, commands/** or hooks/** — what a Claude Code
|
||||||
|
// session loads at the user's end, so a quarantine there breaks the
|
||||||
|
// installed plugin, not just a clone — whose RAW text holds a contiguous
|
||||||
|
// literal matching one of the same SIG rules, a pipe into a shell
|
||||||
|
// (curl/wget ... | sh|bash) or a base64 decode piped into a shell. Raw
|
||||||
|
// text, comments included, one line at a time, every file type: a hook
|
||||||
|
// script's comment sits on disk like any other byte. knowledge/** is
|
||||||
|
// measured by the same rule and REPORTED as its own number in the
|
||||||
|
// diagnostic, not gated (S3, 2026-09-22): part of it is a scanner's or
|
||||||
|
// the attack simulator's input, and S3 records which literal stays and
|
||||||
|
// why. Added in S3 (2026-09-22).
|
||||||
//
|
//
|
||||||
// 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
|
||||||
|
|
@ -81,6 +92,11 @@ const COMMAND_SHAPE = /^\s*(?:curl|wget|bash|sh|python[0-9.]*|eval|nc)\b|\|\s*(?
|
||||||
|
|
||||||
const CARRIER = /[\u{E0000}-\u{E007F}\u200B-\u200D\u2060\uFEFF\u202A-\u202E\u2066-\u2069]/u;
|
const CARRIER = /[\u{E0000}-\u{E007F}\u200B-\u200D\u2060\uFEFF\u202A-\u202E\u2066-\u2069]/u;
|
||||||
|
|
||||||
|
const SESSION_LOADED = /^(?:agents|commands|hooks)\//;
|
||||||
|
const KNOWLEDGE = /^knowledge\//;
|
||||||
|
const PIPE_TO_SHELL = /\b(?:curl|wget)\b[^\n|]*\|\s*(?:sudo\s+)?(?:ba|z)?sh\b/i;
|
||||||
|
const BASE64_PIPE_TO_SHELL = /\bbase64\s+(?:-d|-D|--decode)\b[^\n|]*\|\s*(?:sudo\s+)?(?:ba|z)?sh\b/i;
|
||||||
|
|
||||||
const PAYLOAD_TREES = [
|
const PAYLOAD_TREES = [
|
||||||
'tests/fixtures/signature-scan/poisoned/',
|
'tests/fixtures/signature-scan/poisoned/',
|
||||||
'tests/fixtures/memory-scan/poisoned-project/',
|
'tests/fixtures/memory-scan/poisoned-project/',
|
||||||
|
|
@ -107,8 +123,34 @@ function measureAvSurface() {
|
||||||
const b = [];
|
const b = [];
|
||||||
const c = [];
|
const c = [];
|
||||||
const a2 = [];
|
const a2 = [];
|
||||||
|
const e = [];
|
||||||
|
const eKnowledge = [];
|
||||||
let testSources = 0;
|
let testSources = 0;
|
||||||
let textFiles = 0;
|
let textFiles = 0;
|
||||||
|
let sessionLoaded = 0;
|
||||||
|
let knowledgeFiles = 0;
|
||||||
|
|
||||||
|
// (e): every hit is `rel:line [rule]`, so the report names the exact literal.
|
||||||
|
const loadedSurfaceHits = (rel, text) => {
|
||||||
|
const hits = [];
|
||||||
|
text.split('\n').forEach((line, i) => {
|
||||||
|
const ids = rules.filter(r => r.re.test(line)).map(r => r.id);
|
||||||
|
if (PIPE_TO_SHELL.test(line)) ids.push('pipe-to-shell');
|
||||||
|
if (BASE64_PIPE_TO_SHELL.test(line)) ids.push('base64-pipe-to-shell');
|
||||||
|
if (ids.length > 0) hits.push(`${rel}:${i + 1} [${ids.join(', ')}]`);
|
||||||
|
});
|
||||||
|
return hits;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const rel of files) {
|
||||||
|
if (SESSION_LOADED.test(rel)) {
|
||||||
|
sessionLoaded++;
|
||||||
|
e.push(...loadedSurfaceHits(rel, readFileSync(resolve(ROOT, rel), 'utf8')));
|
||||||
|
} else if (KNOWLEDGE.test(rel)) {
|
||||||
|
knowledgeFiles++;
|
||||||
|
eKnowledge.push(...loadedSurfaceHits(rel, readFileSync(resolve(ROOT, rel), 'utf8')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const rel of files) {
|
for (const rel of files) {
|
||||||
const buf = readFileSync(resolve(ROOT, rel));
|
const buf = readFileSync(resolve(ROOT, rel));
|
||||||
|
|
@ -159,6 +201,10 @@ function measureAvSurface() {
|
||||||
bFiles: new Set(b.map(x => x.split(' :: ')[0])).size,
|
bFiles: new Set(b.map(x => x.split(' :: ')[0])).size,
|
||||||
c,
|
c,
|
||||||
d,
|
d,
|
||||||
|
e,
|
||||||
|
eKnowledge,
|
||||||
|
sessionLoaded,
|
||||||
|
knowledgeFiles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,4 +238,11 @@ describe('av-surface: tracked tree carries no AV-triggering payloads', () => {
|
||||||
t.diagnostic(`d=${m.d.length}`);
|
t.diagnostic(`d=${m.d.length}`);
|
||||||
assert.deepEqual(lines, [], report('payload trees', lines));
|
assert.deepEqual(lines, [], report('payload trees', lines));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`(e) no payload literal in what a session loads (of ${m.sessionLoaded} files in agents/commands/hooks)`, (t) => {
|
||||||
|
t.diagnostic(`e=${m.e.length}`);
|
||||||
|
// Reported, not gated: see the header.
|
||||||
|
t.diagnostic(report(`knowledge (of ${m.knowledgeFiles} files, not gated)`, m.eKnowledge));
|
||||||
|
assert.deepEqual(m.e, [], report('payload literals in session-loaded files', m.e));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue