Closes E11. Three new pieces, plus integration:
1. Re-interpolation detector (Appsmith GHSL-2024-277 stealth pattern).
The scanner now collects env: bindings (key -> source-expression
text) by walking parsed events whose parentChain includes 'env',
then for each `${{ env.<KEY> }}` inside run:, re-injects MEDIUM
if the binding source matches the 23-field blacklist. This
catches the pattern where developers apply env-indirection but
then re-interpolate the env var in run:, which cancels the
mitigation (template substitution happens before shell parsing).
2. Auth-bypass category (Synacktiv 2023 Dependabot spoofing).
Detects `if: ${{ github.actor == 'dependabot[bot]' }}` and
variants. MEDIUM, owasp: 'LLM06' (Excessive Agency). Distinct
from injection — same expression syntax, different threat class.
Recommendation steers users to `github.event.pull_request.user.login`.
3. severity.mjs OWASP map registration. WFL prefix added to all
four maps:
- OWASP_MAP['WFL'] = ['LLM02', 'LLM06']
- OWASP_AGENTIC_MAP['WFL'] = ['ASI04']
- OWASP_SKILLS_MAP['WFL'] = []
- OWASP_MCP_MAP['WFL'] = []
Empty arrays for skills/MCP are explicit, not omitted — keeps
`Object.keys(OWASP_MAP)` symmetric across maps.
4. scan-orchestrator.mjs registration. workflowScan added between
supply-chain and toxic-flow (toxic-flow correlates after primaries).
Verified via integration: orchestrator emits 9 WFL findings on
tests/fixtures/workflows/.
Bug fix: extractTriggers in workflow-yaml-state.mjs was collecting
sub-properties (`branches:`, `types:`) as triggers. Now tracks the
first nested indent level and ignores anything deeper.
Tests:
- 6 new cases in tests/scanners/workflow-scanner.test.mjs:
re-interp TP, no-double-count, auth-bypass TP, auth-bypass FP
(startsWith head_ref is not auth-bypass), OWASP map shape,
orchestrator import + SCANNERS array entry.
- 2 new fixtures: tp-reinterpolation.yml, auth-bypass-dependabot.yml.
- Existing 14 scanner tests + 15 state-machine tests unchanged.
Test count: 1732 -> 1738 (+6). Wave B total: +53 over baseline 1685.
Pre-compact-scan flake unchanged (passes in isolation).
The v7.0.0 entropy-scanner ran rules 11-13 (GLSL/CSS-in-JS/inline-markup
line-proximity suppressions) for every line regardless of file type. A
polyglot `.ts` file with an embedded fragment-shader template literal
could therefore mask a real high-entropy credential when the credential
literal happened to share a line with a GLSL keyword. Critical-review
B5 documented the false-negative class.
Refactor:
* New `classifyFileContext(absPath, lines)` returns
`'shader-dominant' | 'markup-dominant' | 'code-dominant' | 'mixed'`,
keyed off file extension with a content-density fallback for
code-extension files (≥50% of sampled non-blank lines matching
GLSL/inline-markup → downgrade to `mixed`).
* `isFalsePositive(str, line, absPath, context)` gates rules 11-13
on `context !== 'code-dominant'`. Rules 1-10 and 14-19 still run
unconditionally, so URL/path/test-fixture/ffmpeg/UA/SQL/error-
template suppression behaves identically.
* `scanFileContent` computes `fileContext` once per file and threads
it through every per-string suppression check.
Conservative defaults to keep the regression surface minimal:
* Files with `<5` sampled non-blank lines fall back to `mixed`
(preserves the existing rule-11/12/13 behaviour for the single-
line .js fixtures used by entropy-context.test.mjs).
* Unknown extensions fall back to `mixed`.
* Code-extension files densely populated with shader/markup
content fall back to `mixed`.
Net effect: a `.ts` file with an embedded GLSL block but mostly TS
code on the surrounding lines now surfaces credentials that the
v7.0.0 line-proximity heuristic suppressed. Pure shader/markup
files are unaffected (extension skip / mixed default).
New fixture: tests/fixtures/entropy/polyglot-ts-with-glsl.ts (with
runtime placeholder so it does not commit a high-entropy literal).
+3 tests in tests/scanners/entropy-context.test.mjs (26 → 29).
Existing entropy.test.mjs and entropy-context.test.mjs all remain
green. Full suite 1658 → 1661.
Refs: Batch B Wave 5 / Step 12 / v7.2.0
critical-review-2026-04-20.md §B5
Critical-review §4 E15 finding: agent files in .claude/agents/ are loaded
as Claude Code subagent system prompts and are a direct memory-poisoning
surface. Pre-v7.2.0 the scanner covered CLAUDE.md, .claude/rules/*.md,
memory/*.md, REMEMBER.md, .local.md, and .claude-plugin/plugin.json —
but not .claude/agents/*.md.
Single-line addition to MEMORY_FILE_PATTERNS:
/(?:^|\/)\.claude\/agents\/[^/]+\.md$/
The existing scan loop, scanForInjection integration, and severity-
mapping logic all apply unchanged. STRICT_FILES_PATTERN intentionally
NOT extended — agents may legitimately quote shell commands as examples
(consistent with CLAUDE.md treatment).
Tests: +3 cases in tests/scanners/memory-poisoning.test.mjs:
- "scans .claude/agents/*.md" (smoke test — at least one finding from
the new fixture)
- "agent file injection pattern detected"
- "agent file credential path detected"
New fixture: tests/fixtures/memory-scan/poisoned-project/.claude/agents/
poisoned-agent.md — agent with injection, credential ref, permission
expansion, and exfil URL. Triggers all 4 detection categories.
Suite: 1591 → 1594 (+3). All green.
Synthetic skill content mimicking the noise profile of frontend
animation projects (HTML5 canvas, framework env-vars, inline SVG data
URIs, CSS keyframes) plus exactly one genuine HITL trap signal.
Used by tests/scanners/skill-scanner-narrative.test.mjs (added in
v7.1.1) to exercise:
- content-extractor: HIGH HITL trap signal + framework env-var
references (process.env.REACT_APP_*, VITE_PUBLIC_*)
- entropy scanner: inline SVG data URI suppressed via line-context rules
The .llm-security-ignore file uses the SCANNER:glob format
(scanners/scan-orchestrator.mjs:34-40) — ENT:**/*.md suppresses any
entropy-scanner findings when the fixture is run through scan-orchestrator
in the Step 6 smoke test.
Part of v7.1.1 narrative-coherence patch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>