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:
parent
bfd577aeee
commit
dd9db60fc9
8 changed files with 403 additions and 11 deletions
|
|
@ -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 35–37 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');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue