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
46
tests/fixtures/cps-fenced/inside-fence/CLAUDE.md
vendored
Normal file
46
tests/fixtures/cps-fenced/inside-fence/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Fenced Documentation Project
|
||||
|
||||
Stable preamble. All volatility below is illustrative, inside a code fence.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
```bash
|
||||
export STAMP=${TIMESTAMP}
|
||||
!deploy.sh --at 2026-06-26T10:00:00
|
||||
echo {date} > /tmp/log
|
||||
[2026-06-26 12:00] starting
|
||||
```
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
55
tests/fixtures/cps-fenced/mixed/CLAUDE.md
vendored
Normal file
55
tests/fixtures/cps-fenced/mixed/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Fenced Plus Prose Project
|
||||
|
||||
Stable preamble. A fence below, plus one genuine volatile prose line.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
```bash
|
||||
export STAMP=${TIMESTAMP} # documented, not live
|
||||
```
|
||||
Body line 38.
|
||||
Body line 39.
|
||||
Body line 40.
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
Body line 47.
|
||||
Body line 48.
|
||||
Body line 49.
|
||||
!git log -1 # genuine shell-exec in live prose at line 50
|
||||
Body line 51.
|
||||
Body line 52.
|
||||
Body line 53.
|
||||
Body line 54.
|
||||
Body line 55.
|
||||
55
tests/fixtures/cps-inline-code/mixed/CLAUDE.md
vendored
Normal file
55
tests/fixtures/cps-inline-code/mixed/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Inline Code Mixed Project
|
||||
|
||||
Stable preamble. Backticked template plus a live var in prose.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
Body line 35.
|
||||
Body line 36.
|
||||
Body line 37.
|
||||
Body line 38.
|
||||
Body line 39.
|
||||
Output template is `run-{date}.md` (documented).
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
Body line 47.
|
||||
Body line 48.
|
||||
Body line 49.
|
||||
Deployed at ${RELEASE_STAMP} on every push.
|
||||
Body line 51.
|
||||
Body line 52.
|
||||
Body line 53.
|
||||
Body line 54.
|
||||
Body line 55.
|
||||
46
tests/fixtures/cps-inline-code/pure/CLAUDE.md
vendored
Normal file
46
tests/fixtures/cps-inline-code/pure/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Inline Code Doc Project
|
||||
|
||||
Stable preamble. A filename template is documented in backticks below.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
Body line 35.
|
||||
Body line 36.
|
||||
Body line 37.
|
||||
Body line 38.
|
||||
Body line 39.
|
||||
| `--brief <path>` | writes to `.claude/plans/run-{date}-{slug}.md` |
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
46
tests/fixtures/cps-stable-vars/non-whitelisted/CLAUDE.md
vendored
Normal file
46
tests/fixtures/cps-stable-vars/non-whitelisted/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Non-Whitelisted Var Project
|
||||
|
||||
Stable preamble with a genuine runtime substitution below.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
Body line 35.
|
||||
Body line 36.
|
||||
Body line 37.
|
||||
Body line 38.
|
||||
Body line 39.
|
||||
Deployed build tag: ${DEPLOY_TIMESTAMP} (changes every release).
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
46
tests/fixtures/cps-stable-vars/whitelisted/CLAUDE.md
vendored
Normal file
46
tests/fixtures/cps-stable-vars/whitelisted/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Stable CC Vars Project
|
||||
|
||||
Stable preamble — only CC-provided path vars appear below.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
Body line 35.
|
||||
Body line 36.
|
||||
Body line 37.
|
||||
Body line 38.
|
||||
Body line 39.
|
||||
Hooks resolve under ${CLAUDE_PLUGIN_ROOT}/hooks and project root is ${CLAUDE_PROJECT_DIR}.
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
|
|
@ -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