# LLM Security — Version history Per-release notes for v7.0.0 onward. Imported from `CLAUDE.md` via `@docs/version-history.md`. ## v7.8.1 — Auto-cleaner command-injection fix (CRITICAL) Security patch for a CRITICAL defect introduced with the v7.8.0 auto-cleaner hardening pass. No feature changes; full suite green at 1865/0. `scanners/auto-cleaner.mjs` validated candidate `.mjs`/`.js`/`.cjs` content by shelling out to ``node --check "${tmpPath}"`` via `execSync`. `tmpPath` is derived from the finding's `file` field — an untrusted scanned-repo filename. The F-2 containment guard that landed in v7.8.0 verifies the resolved path stays inside the scanned tree, but performs no shell-metacharacter handling, so a filename whose `"` terminates the interpolated quote injects a second command. Because `/security clean` runs in live mode by default, scanning a hostile repository was sufficient to execute attacker-chosen commands locally. The defect was reproduced with a live proof-of-concept before being fixed. Both subprocess sites now use `spawnSync` with an argv array — the syntax check and the CLI's inline scan-orchestrator fallback — so no shell interprets a path. As defense-in-depth, `applyFixes()` additionally refuses any finding whose `file` carries shell or control metacharacters, surfacing it as `skipped`. Regression coverage is deliberately split across both layers (`tests/scanners/auto-cleaner-rce.test.mjs`): one test drives `validateContent` directly to prove the shell is gone, because the metacharacter guard would otherwise stop the hostile input before it reached the sink and the test would pass without testing the fix. ## v7.8.0 — Trigger, signature, and AST-taint scanners Three new deterministic deep-scan scanners (TRG/SIG/AST), each with its own finding prefix, OWASP/AST mapping, policy block, fixtures, and graceful-skip behaviour. They target the skills/agents activation and code surface that the permission and shape-based scanners do not cover. Built behind a security-fix gate — the F-1/F-2/F-3 shell-injection and path-traversal fixes landed first. No existing scanner, hook, or command behaviour changes; full suite green (1863 tests, 0 fail). - **TRG — trigger/activation-abuse** (`scanners/trigger-scanner.mjs`). Inspects command/agent/skill `name` + `description` frontmatter for three activation-surface abuses: `TRG-shadow` (name collides with a built-in command/tool and intercepts it), `TRG-baiting` (description uses maximally-activating phrases — "anything", "always", "all files" — to bait indiscriminate invocation), and `TRG-broad` (a generic name plus a universal-applicability claim, so the unit auto-activates beyond its stated purpose). Skills/agents auto-activate on their description, so this is a skill-native surface not covered by the permission or taint scanners. Descriptions pass through the decode pipeline (zero-width strip → homoglyph fold → `normalizeForScan`) first, so obfuscated baiting still trips. Thresholds and the phrase/built-in lists are overridable via `.llm-security/policy.json` (`trg`). OWASP LLM06 (excessive agency); AST04. - **SIG — known-bad-identity signatures** (`scanners/signature-scanner.mjs`). A pure-Node signature engine for known-malware *identity* — webshells, reverse shells, cryptominers, hacktools — complementary to the shape-based entropy/taint scanners. Unlike a raw byte-matcher (e.g. YARA), every signature is tested against both the raw bytes and the project decode pipeline (base64/hex/url/entity/unicode decode, homoglyph fold, rot13), so obfuscated known-malware a byte-matcher misses is still caught. Rules ship in `knowledge/signatures.json`; families are policy-selectable. OWASP LLM03 (supply chain) primary; LLM02. - **AST — Python taint analysis** (`scanners/ast-taint-scanner.mjs`). Shells out to a PARSE-ONLY `python3` helper (`scanners/lib/py-ast-taint.py`) for variable-level, scope-aware taint analysis of Python skill code — higher recall/precision than the ~70%-recall regex `taint-tracer.mjs`. The helper only `ast.parse`s the target and never executes it, so analysing hostile code is side-effect-free. Falls back to the regex tracer whenever `python3` is unavailable, so the scan never hard-fails. OWASP LLM01, LLM02; AST02. Packaging/wiring: `TRG`/`SIG`/`AST` registered as valid finding prefixes (`scanners/lib/output.mjs`); all three wired into the scan orchestrator and policy loader; `knowledge/` added to the `package.json` `files` whitelist. ## v7.7.2 — Language consistency pass Norwegian had crept into surface text across v7.5–v7.7. Per the `~/.claude/CLAUDE.md` convention (English for code and documentation, Norwegian for dialog only), this release translates: the HTML Report-step in all 18 skill commands, the canonical CLI renderer `scripts/lib/report-renderers.mjs` (display strings + JS comments), the playground UI strings, the `skill-scanner-agent` and `mcp-scanner-agent` system prompts, the playground architecture prose + Recent versions table in the plugin `README.md`, the v7.7.x highlights in the plugin `CLAUDE.md`, the llm-security entries in the marketplace root `README.md` + `CLAUDE.md`, and six table cells in `docs/scanner-reference.md`. Demo-state fixture content for the `dft-komplett-demo` project (intentional Norwegian persona) and regex alternations that match Norwegian-language report markdown (`/^high|^høy/`, `/resolution|løsning/`) were preserved. CHANGELOG and this version-history file were deferred per operator decision — they remain in the language they were written in. No scanner, hook, or behavior changes — purely surface text. ## v7.0.0 — Severity-dominated risk scoring (v2 model, BREAKING) Three changes target the false-positive cascade on real codebases (hyperframes.com gave `BLOCK / Extreme / 100`, ~70% noise): 1. **Risk-score v2 formula** (`scanners/lib/severity.mjs`) — severity-dominated, log-scaled within tier. Replaces v1 sum-and-cap that collapsed every non-trivial scan to 100/Extreme. Tiers: critical → 70–95, high only → 40–65, medium only → 15–35, low only → 1–11. Verdict cutoffs realigned to new bands (BLOCK ≥65, WARNING ≥15). `info` findings are observability-only — counted in OWASP aggregates but contribute zero to risk_score, verdict, and riskBand (B3, v7.2.0 — was undocumented pre-7.2.0). See `severity.mjs` JSDoc for full contract. 2. **Rule-based entropy scanner with file-extension skip, 8 line-level suppression rules, and configurable policy** — extensions skipped (`.glsl/.frag/.vert/.shader/.wgsl/.css/.scss/.sass/.less/.svg/.min.*/.map`); line-suppression rules (GLSL keywords, CSS-in-JS, inline SVG, ffmpeg `filter_complex`, User-Agent strings, SQL DDL, `throw new Error(\`...\`)`, markdown image URLs). Configurable via `.llm-security/policy.json` `entropy` section (thresholds, `suppress_extensions`, `suppress_line_patterns`, `suppress_paths`). Envelope `calibration` block reports skip counters + effective thresholds + policy source. 3. **DEP typosquat allowlist expansion** — 22 npm + 5 PyPI entries for short-name tools that tripped Levenshtein detection on every modern codebase (`knip`, `oxlint`, `tsx`, `nx`, `rimraf`, `uv`, `ruff`, etc.). See `docs/security-hardening-guide.md` §6 for the calibration story. ## v7.1.1 — Scan-rapport narrative coherence (patch) Three coordinated edits address the whiplash symptom that survived v7.0.0 (numbers fixed, narrative still walked findings back as "false positive" in prose): (a) `agents/skill-scanner-agent.md` Step 2.5 mandates context-first severity assignment — every signal has exactly one disposition (suppressed OR reported), no per-finding walk-back; (b) `templates/unified-report.md` gains a `### Narrative Audit` block in Executive Summary surfacing `summary.narrative_audit.suppressed_findings.{count, by_category}` from the agent's trailing JSON; (c) both files updated from stale v1 risk-formula constants to the v2 model that has been authoritative in `severity.mjs` since v7.0.0. Counter is distinct from the existing top-level `output.suppressed` (`.llm-security-ignore` rule integer). Out-of-scope but flagged: `commands/scan.md:113-114` retains the v1 formula; resolution deferred to Batch B. ## v7.3.0 — MCP cumulative-drift baseline (Wave C of Batch C) Closes E14 from `docs/critical-review-2026-04-20.md`. The `mcp-description-cache.mjs` schema gains a sticky `baseline` slot per tool plus a 10-event rolling `history` array (FIFO). Cumulative drift = `levenshtein(current, baseline) / max(|current|, |baseline|)`; when the ratio crosses `mcp.cumulative_drift_threshold` (default 0.25), `post-mcp-verify.mjs` emits a separate MEDIUM `mcp-cumulative-drift` advisory. The existing per-update >10% drift signal is unchanged — both fire independently. Slow-burn rug-pulls that keep each update under the per-update threshold but cumulatively diverge from baseline are now caught. Baseline survives the 7-day TTL purge so detection persists across the full window. New `/security mcp-baseline-reset` slash command (plus `scanners/mcp-baseline-reset.mjs` CLI: `--list`, `--target `, or no-args clear-all) lets the user acknowledge a legitimate MCP server upgrade — clearing the baseline causes the next call to seed a fresh one from the incoming description; description, firstSeen, lastSeen, and history are preserved for audit. `LLM_SECURITY_MCP_CACHE_FILE` env var overrides the cache path for end-to-end testing without polluting the user's real `~/.cache/llm-security/mcp-descriptions.json`. ## v7.3.0 — Env-var deprecation warnings (D3 of Batch C, Wave D) Closes 8.7 from `.claude/projects/2026-04-29-batch-c-scope-finalize/plan.md`. `scanners/lib/policy-loader.mjs` exports a new helper `getPolicyValueWithEnvWarn(section, key, envVarName, defaultValue)` — env still wins per Preferences (existing behaviour), but when both the env-var AND the `policy.json` key are explicitly set, the helper emits a single per-process stderr line: `[llm-security] Deprecation: env-var ${ENVVAR} will be removed in v8.0.0; policy.json key ${section}.${key} also set — env wins for now. Suppress with LLM_SECURITY_DEPRECATION_QUIET=1.` Module-scoped `Set` dedupes per env-var name across call-sites. Four overlapping vars are wired through the helper: `LLM_SECURITY_INJECTION_MODE` ↔ `injection.mode` (in `pre-prompt-inject-scan.mjs`), `LLM_SECURITY_TRIFECTA_MODE` ↔ `trifecta.mode` and `LLM_SECURITY_ESCALATION_WINDOW` ↔ `trifecta.escalation_window` (in `post-session-guard.mjs`), `LLM_SECURITY_AUDIT_LOG` ↔ `audit.log_path` (in `scanners/lib/audit-trail.mjs`). `DEFAULT_POLICY` gains `trifecta.escalation_window: 5` to close the gap noted in the plan revisions table (M10). Env-only vars without policy.json equivalents (`LLM_SECURITY_UPDATE_CHECK`, `LLM_SECURITY_PRECOMPACT_MODE`, `LLM_SECURITY_PRECOMPACT_MAX_BYTES`, `LLM_SECURITY_IDE_ROOTS`, `LLM_SECURITY_MCP_CACHE_FILE`) are unchanged — they emit no deprecation signal because there is nothing to deprecate yet. ## v7.5.0 — Playground (additive surface, no scanner/hook behavior changes) Single-file SPA at `playground/llm-security-playground.html` (~10 200 lines) for onboarding, demo og workshop-bruk uten Claude Code-installasjon. Parser + renderer for alle 18 `produces_report=true`-kommandoer i `CATALOG`. State i IndexedDB primær (`llm-security-playground-v1`) med localStorage-fallback, sirkelfri Proxy + EventTarget store, microtask-batchet render. Theme-bootstrap med FOUC-prevention. 4 overflater: onboarding (5 grupper) → home (3 tracks) → catalog (20 kommandoer) ⇄ project (rapporter / oversikt / kontekst / eksport). Demo-state har tre prosjekter inline; `dft-komplett-demo` har alle 18 rapporter ferdig parsed for klikk-gjennom. Vendor-synket design-system under `playground/vendor/playground-design-system/` (sjekksum-låst via `MANIFEST.json`, redigeres aldri direkte). Test-fixtures under `playground/test-fixtures/` (én markdown-fil per kommando) er kontrakt-anker for parser-utvikling. Skjermdumper i `playground/screenshots/v7.5.0/`. Eksponerte vinduer-globaler for testing/automasjon: `__store`, `__navigate`, `__loadDemoState`, `__scheduleRender`, `__PARSERS`, `__RENDERERS`, `__CATALOG`, `__inferVerdict`, `__inferKeyStats`, `__renderPageShell`, `__handlePasteImport`. Inkluderer fix av `normalizeVerdictText` regex-rekkefølge: GO-WITH-CONDITIONS sjekkes før GO så betinget verdict ikke kollapser til ALLOW. ## v7.6.0 — Playground Tier 3-referanse-case (additive surface, no scanner/hook behavior changes) Playgroundet er nå en visuelt og strukturelt fullført referanse-implementasjon for `shared/playground-design-system/` Tier 3-supplementet. 8 nye Tier 3-komponenter integrert i de 18 rapport-rendererne: `tfa-flow` + `tfa-leg` + `tfa-arrow` (lethal trifecta-kjede med `