fix(acr): CPS ignores fenced/inline code + CC-stable path vars (M-BUG-7)

CPS flagged ${CLAUDE_PLUGIN_ROOT}/${CLAUDE_PROJECT_DIR} (CC-provided stable
paths) and {date}/timestamp tokens shown in documentation as cache-busters.
Fix: skip fenced code blocks, strip inline-code spans, and whitelist CC-stable
vars before pattern-matching. Suppress-only — frozen v5.0.0 snapshots untouched
(CPS yields findings:[] there), no re-seed. Suite 1316/0 (+6). Dogfood ~/.claude
5->2 (3 doc false-positives suppressed; 2 remaining = own volatile test fixtures).
This commit is contained in:
Kjell Tore Guttormsen 2026-06-26 12:45:14 +02:00
commit dd9db60fc9
8 changed files with 403 additions and 11 deletions

View file

@ -86,6 +86,66 @@ describe('CPS scanner — volatile content in @imported files (v5.10 B6)', () =>
});
});
describe('CPS scanner — CC-stable vars are not cache-busters (M-BUG-7)', () => {
it('does NOT flag ${CLAUDE_PLUGIN_ROOT} / ${CLAUDE_PROJECT_DIR} in prose', async () => {
// These are CC-provided path substitutions that resolve to a stable value
// every turn — referencing them in CLAUDE.md prose is not a cache-buster.
const result = await runScanner('cps-stable-vars/whitelisted');
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
assert.equal(f, undefined,
`CC-stable path vars must not trip CPS; got: ${f?.evidence}`);
});
it('STILL flags a genuine non-CC ${VAR} substitution (whitelist is selective)', async () => {
const result = await runScanner('cps-stable-vars/non-whitelisted');
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
assert.ok(f, `expected a finding for a genuine runtime var; got none`);
assert.match(String(f.evidence || ''), /line 40/);
assert.match(String(f.evidence || ''), /\$\{VAR\} substitution/i);
});
});
describe('CPS scanner — volatility inside fenced code blocks is documentation (M-BUG-7)', () => {
it('does NOT flag volatile-looking lines inside a ```fence```', async () => {
// Content inside a fenced code block is illustrative, byte-stable literal
// text — not runtime volatility — so it must not break the cached prefix.
const result = await runScanner('cps-fenced/inside-fence');
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
assert.equal(f, undefined,
`fenced code content must not trip CPS; got: ${f?.evidence}`);
});
it('STILL flags genuine volatility in live prose outside the fence', async () => {
const result = await runScanner('cps-fenced/mixed');
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
assert.ok(f, `expected a finding for the out-of-fence volatile line; got none`);
assert.match(String(f.evidence || ''), /line 50/);
assert.match(String(f.evidence || ''), /shell-exec/i);
assert.doesNotMatch(String(f.evidence || ''), /line 3[567]/,
'fenced lines 3537 must not appear in the evidence');
});
});
describe('CPS scanner — volatility inside inline code is documentation (M-BUG-7)', () => {
it('does NOT flag a {date} placeholder shown inside `inline code`', async () => {
// A filename template like `.claude/plans/run-{date}.md` in backticks is
// literal documentation text — byte-stable, not a runtime substitution.
const result = await runScanner('cps-inline-code/pure');
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
assert.equal(f, undefined,
`inline-code documentation must not trip CPS; got: ${f?.evidence}`);
});
it('STILL flags a live ${VAR} that sits outside backticks', async () => {
const result = await runScanner('cps-inline-code/mixed');
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
assert.ok(f, `expected a finding for the out-of-backtick volatile line; got none`);
assert.match(String(f.evidence || ''), /line 50/);
assert.doesNotMatch(String(f.evidence || ''), /line 40/,
'the backticked {date} on line 40 must not appear in the evidence');
});
});
describe('CPS scanner — orchestrator wiring', () => {
it('CPS appears in scan-orchestrator scanner list', async () => {
const orch = await import('../../scanners/scan-orchestrator.mjs');