chore(llm-security): STATE — v7.8.3 code-complete locally, held for release decision

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
This commit is contained in:
Kjell Tore Guttormsen 2026-07-18 10:54:03 +02:00
commit d2648931ef

101
STATE.md
View file

@ -1,69 +1,50 @@
# STATE — llm-security
**👉 NESTE — START HER (fresh session, Opus 4.8 xhigh) — the MEDIUM sweep:**
**v7.8.2 SHIPPED** — all 5 HIGH from the completion review are fixed, released, tagged,
catalog-synced and pushed. Next is the MEDIUM tier (52 findings) from the same review.
Expect it to be its own release (v7.8.3 or v7.9.0), not a continuation of v7.8.2.
Same discipline that worked this session: **verify each finding against the code FIRST**
(STATE's own descriptions proved wrong 3x — see below), Iron Law per defect, one commit each.
Triage first: 52 MEDIUMs will contain duplicates and non-defects — do a verification pass and
report the real count before planning the release.
**👉 NESTE — START HER: v7.8.3 is CODE-COMPLETE LOCALLY but NOT released.**
10 commits on `main` (tip `4cffa30`), suite **2016/0**, gitleaks clean, NOT pushed/tagged/catalog-synced.
Two things gate "done", both need an operator decision:
1. **PUSH the release** (public mirror `open/llm-security`). Saturday → window open, but the commits
carry exploit descriptions; held for explicit go. Release mechanics (in order, per convention):
`git push origin``release-plugin.mjs llm-security --version 7.8.3 --create-tag`
`release-plugin.mjs llm-security --version 7.8.3 --write --commit --push``check-versions.mjs` = 0 ERROR.
(release-plugin REFUSES until plugin.json == README badge == 7.8.3 AND the tag exists — both true now / by step 2.)
2. **`post-mcp-verify.mjs` uncommitted change** (in working tree, NOT in any commit): reads
`tool_response ?? tool_output` instead of only `tool_output` — a REAL latent bug (live PostToolUse
sends `tool_response`, so MCP-output injection scanning silently never fired live). +3 tests, green.
BUT it is **unattributed** (appeared pre-staged mid-session, not one of the 52 findings) and OUT of the
approved v7.8.3 scope. Decide: ship as a 48th fix (own commit), hold for a follow-up, or it's your edit.
**Review output (harvest source):** confirmed array + per-finding adversarial reasoning in task
`w8s4rsaw8` output: `…/dfcab047-5467-4484-ab1a-5d6526439f78/tasks/w8s4rsaw8.output` (result.confirmed,
59 items). Durable journal: `~/.claude/projects/-Users-…-llm-security/
f99dcf73-d344-4377-b8ba-09fe878f32a2/subagents/workflows/wf_99daed37-f8b/journal.jsonl`.
## What v7.8.3 is (the MEDIUM sweep)
52 open completion-review findings triaged (5 Opus verifiers) → **48 confirmed, 3 feature-requests, 1 non-defect**.
Corrected severity: **0 CRITICAL/HIGH, 21 MEDIUM, 27 LOW** (every review-claimed HIGH fell to MEDIUM).
Shipped: **47 fixes** (48 minus #27, deferred). Full triage: `docs/medium-triage.local.md` (gitignored).
Deferred to v8: **#11** persistence detector (kept only its doc fix), **#27** AST f-string recall,
**#34/#35/#37** missing AST09/AST10/MCP05 detectors (feature-requests), **#49** non-defect.
## Lesson from v7.8.2 — the review's own text is a premiss, not a fact
Three of five findings were described inaccurately in STATE/the review. Verify before acting:
- Paths were wrong: hooks live in `hooks/scripts/`, the parser in `scanners/lib/`.
- Blast radius was understated (the rm-block also missed `/*`, `-fr`, and sudo-prefixed forms).
- Severity was overstated once: the char-ref defect does NOT break the no-throw contract
(`safe()` catches it) and needs non-well-formed XML, so it is below HIGH. Said so in the commit.
- The old `pre-bash-destructive.test.mjs` had **encoded the bug as expected behaviour** with a
wrong root-cause NOTE. Green suites can be green because the test was shaped around the defect —
when a test comment explains why something *isn't* caught, treat it as a lead, not a spec.
## ⚠️ NEVER `git add -A` in this repo
Did it this session and swept the 3 parked docs into a release commit aimed at a PUBLIC mirror.
Caught before push and amended out, but the guard is: **stage explicit paths, always.**
## PARKED — v8 family strategy (behind an operator visibility decision, DO NOT PUSH)
4 of 6 deliverables written, LOCAL ONLY: `docs/JOBS-TO-BE-DONE.md`, `docs/FAMILY-MAP.md`,
`docs/commons-extraction-plan.md`, `docs/roadmap.md` [gitignored]. Remaining v8 docs (do NOT start
until visibility is decided): formal `docs/completion-review-2026-07-17.md` and `V8-ANNOUNCEMENT.md`.
**DO NOT PUSH the 3 untracked docs:** origin `open/llm-security.git` is a PUBLIC mirror; they describe
cross-repo strategy + a sibling (`claude-code-llm-wiki`, "Private pending Anthropic ToS").
NOTE: `docs/FAMILY-MAP.md:42` says v7.8.0/1863 tests — stale, fix when the parking ends.
## The 10 commits (all HELD until tag)
`b224e18` yaml/workflow (#32,33,43) · `21c6c2b` normalization+SIG (#21,23,30,36,42,52,55) ·
`8a59d61` hooks (#9,10,12,13,#11-doc) · `66e8ce2` misc (#20,22,50,54,56) · `76f190c` AST (#28,29) ·
`196517f` supply-chain (#14-19,48) · `f3aaf54` trigger/toxic/policy (#26,38,39,40,41,57) ·
`207385f` robustness/DoS (#24,25,31,51,53) · `1407016` tests (#58,59) · `4cffa30` docs+v7.8.3 bump (#8,44,45,46,47).
NOTE: `git log` order differs (parallel waves); hashes above are the authoritative finding→commit map.
## Ground truth (verified 2026-07-18)
- **`npm test` = 1901/0 green** at tip (1865 + 36 new). Run with `npm test``node --test tests/`
is NOT valid; the script is `node --test 'tests/**/*.test.mjs'`.
- v7.8.2 released: tag `v7.8.2` pushed, catalog ref bumped, `check-versions.mjs` = **0 ERROR**
(the one WARN is `okr`, pre-existing and unrelated).
- Test-pollution trap (hit and fixed): `jetbrains-parser.test.mjs` asserts globally that no
`llmsec-jb-*` dir survives in tmpdir. Any new test using that mkdtemp prefix fails it
order-dependently — passed one full run, failed the next. Pick a non-colliding prefix.
- Exported for testability, do not "tidy" away: `validateContent` (auto-cleaner, v7.8.1),
`stripInjection` (content-extractor, v7.8.2), `scanOneExtension` (ide-extension-scanner).
`content-extractor.mjs` now runs `main()` behind an `isMain` guard — CLI re-verified working.
- Known residual gap (documented, not a bug to re-file): `stripInjection` cannot attribute a
payload encoded ACROSS lines; those findings carry `unstripped: true` by design. Whole-file
redaction was considered and rejected (normalizeForScan base64-decodes any long blob).
- Prior security: F-1/2/3/5/6 fixed; F-4 open (optional/LOW); B1/B2/E14 fixed.
- `npm test` = **2016/0** (2013 release + 3 post-mcp-verify). Release tip `4cffa30` = 2013/0.
- plugin.json == package.json == README badge == **7.8.3**; test badge 2013; doc-consistency 30/30.
- #30 was scoped: `decodeEmbedded` is OPT-IN on `normalizeForScan` (default off — appending a decoded
copy double-counted content-extractor's per-match findings; caught by skill-scanner-narrative). Only
signature-scanner opts in. Do NOT re-broaden it into the shared normalizer.
- Scanner `VERSION` constants are frozen at 7.5.0 (not bumped since; left as-is — out of scope).
## Position taken (strategic anchor)
llm-security **consolidates**; growth at **family level** (security-commons + `llm-retrieval-guard`
sibling for the LLM08 write→retrieval seam). JS/TS AST-taint parity + post-clone CLAUDE.md-poisoning
stay here; research artifact-scanners (model/pickle, .ipynb, insecure-ML) relocate to commons/guard.
v8.0.0 = deprecation cleanup (LLM_SECURITY_* env-vars + riskScoreV1) + behaviour-preserving commons
extraction + verified fixes + docs consistency.
## Guardrails that bit this session (keep)
- **NEVER `git add -A`; stage explicit paths.** A pre-staged external change (post-mcp-verify) still
rode into commit J via a plain `git commit` — had to `reset --soft` + `restore --staged` to amend it out.
Check `git status` for surprise staged/modified files BEFORE every commit.
- A commit MESSAGE containing the pipe-to-shell literal is blocked by the voyage pre-bash hook — reword release notes.
- Test-pollution trap: `jetbrains-parser.test.mjs` asserts no `llmsec-jb-*` mkdtemp survives — use another prefix.
## Continuity
origin = Forgejo `open/llm-security` (PUBLIC, never GitHub). Push window: weekdays 20:0023:00;
weekends/holidays anytime. STATE.md is intentionally TRACKED + committed (`.gitignore:19-20` NOTE).
**Disclosure convention:** a security fix to public code is committed but HELD until its release tag
exists, so the exploit description never lands before the fixed version. Fix, bump, tag, catalog, push.
**Release mechanics:** `catalog/scripts/release-plugin.mjs <plugin> --version X.Y.Z` (dry-run by
default; `--create-tag`, then `--write --commit --push`). It refuses unless plugin.json == README
badge == target. Push the plugin repo's commits BEFORE `--create-tag`.
origin = Forgejo `open/llm-security` (PUBLIC, never GitHub). STATE.md is intentionally TRACKED
(`.gitignore:19-20` NOTE). Disclosure: fix committed, HELD until its release tag exists. Push window:
weekdays 20:0023:00; weekends/holidays anytime. **DO NOT PUSH** the 3 untracked `docs/*.md` (parked v8 strategy).
Position (v8): llm-security consolidates; growth at family level (security-commons + `llm-retrieval-guard`).