@clear-capabilities/agentic-security-scanner 0.86.0 → 0.119.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +660 -0
  2. package/bin/agentic-security.js +78 -1
  3. package/dist/178.index.js +1 -1
  4. package/dist/384.index.js +1 -1
  5. package/dist/415.index.js +90 -0
  6. package/dist/444.index.js +89 -0
  7. package/dist/503.index.js +288 -0
  8. package/dist/637.index.js +15 -1
  9. package/dist/838.index.js +1 -1
  10. package/dist/985.index.js +5 -1
  11. package/dist/agentic-security.mjs +84 -84
  12. package/dist/agentic-security.mjs.sha256 +1 -1
  13. package/package.json +21 -10
  14. package/src/dataflow/CLAUDE.md +18 -6
  15. package/src/dataflow/catalog.js +59 -11
  16. package/src/dataflow/engine.js +106 -5
  17. package/src/dataflow/implicit-flow.js +4 -2
  18. package/src/dataflow/index.js +17 -4
  19. package/src/dataflow/proof-gate.js +100 -0
  20. package/src/dataflow/summaries.js +63 -6
  21. package/src/engine.js +292 -10
  22. package/src/ir/CLAUDE.md +1 -0
  23. package/src/ir/callgraph.js +12 -1
  24. package/src/ir/parser-go.js +8 -0
  25. package/src/ir/tree-sitter-loader.js +108 -0
  26. package/src/mcp/tools.js +6 -2
  27. package/src/posture/corpus-status.js +74 -0
  28. package/src/posture/coverage-report.js +113 -0
  29. package/src/posture/fix-history.js +24 -0
  30. package/src/posture/holdout-eval.js +57 -0
  31. package/src/posture/provenance.js +39 -0
  32. package/src/posture/router.js +33 -3
  33. package/src/posture/sca-verdict.js +130 -0
  34. package/src/posture/secret-history.js +72 -0
  35. package/src/pr-delta.js +13 -0
  36. package/src/report/index.js +67 -0
  37. package/src/sast/CLAUDE.md +9 -3
  38. package/src/sast/agent-untrusted-flow.js +107 -0
  39. package/src/sast/api-authz.js +71 -0
  40. package/src/sast/code-injection-multilang.js +117 -0
  41. package/src/sast/cross-service.js +98 -0
  42. package/src/sast/crypto-protocol.js +43 -1
  43. package/src/sast/csharp-structural.js +65 -0
  44. package/src/sast/csharp.js +3 -0
  45. package/src/sast/csrf.js +24 -3
  46. package/src/sast/csv-injection.js +84 -0
  47. package/src/sast/event-entrypoint.js +90 -0
  48. package/src/sast/frontend-hygiene.js +106 -0
  49. package/src/sast/go-structural.js +60 -0
  50. package/src/sast/iac-terraform.js +107 -0
  51. package/src/sast/java-structural.js +81 -0
  52. package/src/sast/js-framework-structural.js +115 -0
  53. package/src/sast/kotlin.js +72 -0
  54. package/src/sast/ldap-injection.js +73 -17
  55. package/src/sast/open-redirect.js +47 -5
  56. package/src/sast/php.js +35 -0
  57. package/src/sast/python-structural.js +83 -0
  58. package/src/sast/rbac-consistency.js +113 -0
  59. package/src/sast/redos-nfa.js +75 -0
  60. package/src/sast/response-splitting.js +48 -19
  61. package/src/sast/ruby.js +38 -0
  62. package/src/sast/secret-concat.js +60 -0
  63. package/src/sast/ssrf-cloud-metadata.js +13 -0
  64. package/src/sast/stored-taint.js +88 -0
  65. package/src/sast/tree-sitter-sinks.js +62 -0
  66. package/src/sast/weak-password-hash.js +28 -0
  67. package/src/sast/weak-randomness.js +46 -5
  68. package/src/sast/wrong-context-sanitizer.js +149 -0
  69. package/src/sast/xpath-injection.js +30 -6
  70. package/src/sast/xss-reflected-multilang.js +116 -0
  71. package/src/sast/xxe.js +67 -0
  72. package/src/sca/image-packages.js +74 -0
  73. package/src/sca/import-reachability.js +271 -0
  74. package/src/sca/install-script-analysis.js +82 -0
  75. package/bin/.agentic-security/findings.json +0 -1907
  76. package/bin/.agentic-security/last-scan.json +0 -1907
  77. package/bin/.agentic-security/last-scan.json.sig +0 -1
  78. package/bin/.agentic-security/scan-history.json +0 -166
  79. package/bin/.agentic-security/streak.json +0 -20
  80. package/src/.agentic-security/findings.json +0 -117732
  81. package/src/.agentic-security/last-scan.json +0 -117732
  82. package/src/.agentic-security/last-scan.json.sig +0 -1
  83. package/src/.agentic-security/scan-history.json +0 -12946
  84. package/src/.agentic-security/streak.json +0 -21
  85. package/src/dataflow/.agentic-security/findings.json +0 -6086
  86. package/src/dataflow/.agentic-security/last-scan.json +0 -6086
  87. package/src/dataflow/.agentic-security/last-scan.json.sig +0 -1
  88. package/src/dataflow/.agentic-security/scan-history.json +0 -250
  89. package/src/dataflow/.agentic-security/streak.json +0 -21
  90. package/src/ir/.agentic-security/findings.json +0 -4011
  91. package/src/ir/.agentic-security/last-scan.json +0 -4011
  92. package/src/ir/.agentic-security/last-scan.json.sig +0 -1
  93. package/src/ir/.agentic-security/scan-history.json +0 -193
  94. package/src/ir/.agentic-security/streak.json +0 -20
  95. package/src/mcp/.agentic-security/findings.json +0 -8632
  96. package/src/mcp/.agentic-security/last-scan.json +0 -8632
  97. package/src/mcp/.agentic-security/last-scan.json.sig +0 -1
  98. package/src/mcp/.agentic-security/scan-history.json +0 -331
  99. package/src/mcp/.agentic-security/streak.json +0 -20
  100. package/src/posture/.agentic-security/dpia.md +0 -26
  101. package/src/posture/.agentic-security/findings.json +0 -77181
  102. package/src/posture/.agentic-security/last-scan.json +0 -77181
  103. package/src/posture/.agentic-security/last-scan.json.sig +0 -1
  104. package/src/posture/.agentic-security/pqc-migration-plan.json +0 -65
  105. package/src/posture/.agentic-security/pqc-migration-plan.md +0 -30
  106. package/src/posture/.agentic-security/sbom-history/7d45b5e03804aac084b4a2b4dc8c6f10107d2005.json +0 -6
  107. package/src/posture/.agentic-security/scan-history.json +0 -8904
  108. package/src/posture/.agentic-security/streak.json +0 -21
  109. package/src/posture/.agentic-security/threat-model.json +0 -2038
  110. package/src/posture/.agentic-security/threat-model.md +0 -73
  111. package/src/posture/compliance-frameworks/.agentic-security/findings.json +0 -80
  112. package/src/posture/compliance-frameworks/.agentic-security/last-scan.json +0 -80
  113. package/src/posture/compliance-frameworks/.agentic-security/last-scan.json.sig +0 -1
  114. package/src/posture/compliance-frameworks/.agentic-security/scan-history.json +0 -90
  115. package/src/posture/compliance-frameworks/.agentic-security/streak.json +0 -22
  116. package/src/report/.agentic-security/sbom-history/7d45b5e03804aac084b4a2b4dc8c6f10107d2005.json +0 -6
  117. package/src/report/.agentic-security/threat-model.json +0 -7
  118. package/src/report/.agentic-security/threat-model.md +0 -22
  119. package/src/sast/.agentic-security/findings.json +0 -6154
  120. package/src/sast/.agentic-security/last-scan.json +0 -6154
  121. package/src/sast/.agentic-security/last-scan.json.sig +0 -1
  122. package/src/sast/.agentic-security/scan-history.json +0 -941
  123. package/src/sast/.agentic-security/streak.json +0 -22
  124. package/src/sca/.agentic-security/findings.json +0 -7460
  125. package/src/sca/.agentic-security/last-scan.json +0 -7460
  126. package/src/sca/.agentic-security/last-scan.json.sig +0 -1
  127. package/src/sca/.agentic-security/scan-history.json +0 -113
  128. package/src/sca/.agentic-security/streak.json +0 -21
package/CHANGELOG.md CHANGED
@@ -1,5 +1,665 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.119.1 — packaging: exclude internal scan-state from the npm tarball
4
+
5
+ Packaging-only hotfix for the `@clear-capabilities/agentic-security-scanner` npm
6
+ package; no functional change to the scanner or the plugin.
7
+
8
+ - The published tarball had ballooned to 23.5 MB / 565 files because the `files`
9
+ allowlist pulled in `src/**/.agentic-security/` and `bin/.agentic-security/` —
10
+ the dogfooded self-scan state (findings.json, last-scan.json, scan-history,
11
+ threat-model). `npm pack` runs from `scanner/` and does not consult the
12
+ repo-root `.gitignore`, and a root `.npmignore` is ignored when a `files` field
13
+ is present, so the carve-out is done with negation entries in `files`
14
+ (`!**/.agentic-security`, `!**/.agentic-security/**`). The tarball is now
15
+ 7.9 MB / 393 files with no scan-state.
16
+
17
+ ## 0.119.0 — SAST/SCA capability program (PRD R1–R25, ex-R14)
18
+
19
+ Implements the `docs/SAST_SCA_IMPROVEMENT_PRD.md` backlog — 24 of 25
20
+ recommendations (DAST-lite/R14 intentionally out). Every change shipped with a
21
+ fixture + test and the corpus gate held at 185/185.
22
+
23
+ **Core engine**
24
+ - **R1**: deep interprocedural taint (IR + field-/value-context-sensitive) is now
25
+ ON by default for local/interactive CLI scans, budgeted, CI-off-by-default
26
+ (`--no-deep` / `AGENTIC_SECURITY_DEEP=0` opt-out).
27
+ - **R23**: fixed the incremental cache cold-start no-op (it never persisted a
28
+ baseline), with an end-to-end regression test.
29
+ - **R4**: collection-element taint (`arr.push(tainted); sink(arr[i])`) on by
30
+ default; implicit/control-dependence flow wired in opt-in
31
+ (`AGENTIC_SECURITY_IMPLICIT_FLOW=1`).
32
+ - **R2**: bounded k=1 call-string context sensitivity, opt-in
33
+ (`AGENTIC_SECURITY_KCFA_CALLSTRING=1`); default-off keys are byte-identical.
34
+ - **R3**: deep-flow parity extended to **Go + Kotlin** — call-shaped sources
35
+ (`r.FormValue()`), string callees (`db.Query`), Go string-concat lowering,
36
+ Go `database/sql` sinks. (PHP/Ruby/C# parser concat-lowering remains.)
37
+ - **R17**: per-finding corroboration ("one issue, many signals") + rank tiebreak.
38
+ - **R13**: "provably safe" is a first-class verdict (`--hide-proven-safe`).
39
+ - **R5**: context-aware sanitizer adequacy (HTML-escape before a shell/SQL sink).
40
+ - **R6**: non-HTTP entrypoint taint (queue consumers / scheduled tasks / serverless).
41
+
42
+ **SCA / supply chain**
43
+ - **R7**: import-aware function-level reachability (JS/TS + Python) — resolves
44
+ aliased/namespace imports, gated to files that import the package.
45
+ - **R12**: deterministic decision-first verdict per dependency
46
+ (AUTO_MERGE_PATCH / WAIT_FOR_PATCH / MANUAL_REVIEW / ACCEPT_RISK / WONT_FIX).
47
+ - **R11**: OpenVEX export (`--format vex`) from reachability verdicts.
48
+ - **R9**: static malicious install-script analysis (download-exec / base64-exec /
49
+ env-exfil lifecycle hooks).
50
+ - **R10**: Gradle transitive dependency graph.
51
+ - **R8**: container-image OS-package inventory (dpkg/apk).
52
+
53
+ **Detection surfaces, validation, workflow**
54
+ - **R20**: agent-loop taint (untrusted RAG/tool content → high-privilege tool).
55
+ - **R19**: route-level BOLA/BFLA over the API inventory.
56
+ - **R21**: RBAC role-tier authorization consistency.
57
+ - **R22**: cross-service dataflow (client-call → matched route edge inference).
58
+ - **R18**: semantic IaC (Terraform with variable resolution).
59
+ - **R16**: independent-eval harness (per-family precision/recall/F1 + gate).
60
+ - **R24**: PR-native net-new CI gate (`--fail-on-new`).
61
+ - **R15**: git-history secret sweep (`--secret-history`).
62
+ - **R25**: auto-fix acceptance-rate metric (surfaced in `apply_fix`).
63
+
64
+ Several latent bugs were found and fixed along the way (the dead `implicit-flow`
65
+ module, an `Object.assign`-taint path silently dying on a `const` binding, the
66
+ IR-TAINT conversion dropping the `implicit` flag). Each opt-in/partial item and
67
+ its remainder is tracked in the PRD rollup.
68
+
69
+ ## 0.118.2 — Scanner F1 benchmark: restore F1 to 91.3% (3 FPs removed)
70
+
71
+ The "Scanner F1 benchmark" CI job (`test/benchmark/bench.js`, separate from the
72
+ cve-replay corpus) was failing. Root-caused three false positives and fixed all
73
+ three at the detector level (no baseline edit):
74
+
75
+ - **ReDoS double-flag** (introduced in v0.118.0): `scanRegexReDoS` (the
76
+ multi-language NFA detector) ran on all languages including JS, double-flagging
77
+ regexes already covered by `scanReDoS`. Restricted to the languages
78
+ `scanReDoS` misses string-form regexes in (java/cs/kt/py/php); js/rb stay with
79
+ `scanReDoS`.
80
+ - **rule-library-shape SQLi-in-string** (pre-existing): `js-framework-structural`
81
+ matched a `db.query(\`…\`)` that was the *content* of a rule-definition string.
82
+ Added a same-line string-literal guard.
83
+ - **secret double-count** (pre-existing): a split API key was reported twice —
84
+ once by `secret-concat` (CWE-798) and once as a High-Entropy Credential
85
+ Candidate. Drop the entropy candidate when a named secret detector already
86
+ flagged the same `file:line`.
87
+ - **Measured: bench F1 90.3% → 91.3%, FP 20 → 17;** cve-replay corpus unchanged
88
+ at 185/185 F1=1.000; full gate green.
89
+
90
+ ## 0.118.1 — README: Language coverage section
91
+
92
+ Documents the 8 first-class languages (JS/TS, Python, Java, Kotlin, Go, Ruby,
93
+ PHP, C#) and the cross-language vuln-class coverage built out over v0.108–0.118
94
+ (SQLi, command/code/LDAP/XPath injection, XSS, SSRF, XXE, deserialization,
95
+ secrets, weak hashing/ciphers, static IV, CSRF, open redirect, response
96
+ splitting, ReDoS), measured by the blind, regression-gated 185-entry CVE-replay
97
+ corpus at F1=1.000. Docs + version only; no src/bundle/corpus change.
98
+
99
+ ## 0.118.0 — Multi-CWE gap fill: 167→185 corpus, matrix to 152/160 cells
100
+
101
+ Closes the remaining detector-backed gaps across five CWEs in one batch, via
102
+ detector extensions (each verified pre:TP post:TN; no corpus regression).
103
+
104
+ - **`redos-nfa.js`** (CWE-1333): the multi-language NFA detector (was dormant)
105
+ now wired in for Java/C#/Kotlin, plus PHP/Ruby string-form regex extractors.
106
+ Go intentionally not scanned — its `regexp` package is RE2 (linear-time).
107
+ - **`weak-password-hash.js`** (CWE-916): added Ruby, C#, Kotlin, PHP MD5/SHA1
108
+ forms. Password-context-gated; bcrypt/argon2 nearby suppresses.
109
+ - **`open-redirect.js`** (CWE-601): added Go, Ruby, C#, Kotlin sinks, with
110
+ allow-list recognition and literal-target exemption.
111
+ - **`ruby.js`** (CWE-502): `Marshal.load`/`YAML.load` on a non-literal arg.
112
+ **`go-structural.js`** (CWE-502): `encoding/gob` decode of untrusted bytes.
113
+ **`php.js`** (CWE-918): cURL / fopen-wrapper fetch of a `$_GET`-controlled URL.
114
+ - **18 new corpus entries.** Not filled (intentional): CWE-1321 prototype
115
+ pollution in 7 non-JS languages, and CWE-1333 in Go (RE2) — 8 cells empty by
116
+ design. **Corpus 167 → 185; matrix 134 → 152/160 (95%).** F1=1.000; gate green.
117
+
118
+ ## 0.117.0 — Cross-language XPath injection (CWE-643) + 7 corpus entries
119
+
120
+ Extends `xpath-injection.js` from Java/Python/JS to PHP, Go, Ruby, C#, Kotlin
121
+ (`DOMXPath`, `SelectNodes`/interp, Nokogiri `.xpath`, xmlpath/htmlquery,
122
+ `javax.xml.xpath`). All string-literal matching is embedded-quote-tolerant — an
123
+ expression like `"//user[@name='" + x` embeds the attribute delimiter quote
124
+ (also fixed a latent miss in the existing Java/Python patterns). Two issues
125
+ caught pre-commit (a Kotlin post:FP, a test-author `${u}` JS-interpolated before
126
+ the scanner). **Corpus 160 → 167; matrix 127 → 134/160.** F1=1.000; gate green.
127
+
128
+ ## 0.116.0 — Cross-language CSRF detection (CWE-352) + 3 corpus entries
129
+
130
+ Extends `csrf.js` to Go (gin/echo/fiber/chi, net/http mux), Rails routes, and
131
+ ASP.NET MVC `[HttpPost]` action attrs. Defence recognition added (gorilla/csrf,
132
+ nosurf, `protect_from_forgery`, `[ValidateAntiForgeryToken]`/IAntiforgery).
133
+ Token-auth exemption: `[ApiController]` + explicit Bearer is CSRF-safe; bare
134
+ `[Authorize]` is not (cookie auth is the ASP.NET default). **Corpus 157 → 160;
135
+ matrix 124 → 127/160.** F1=1.000; gate green.
136
+
137
+ ## 0.115.0 — Java reflected XSS (CWE-79): fills last XSS cell
138
+
139
+ Adds Java to `xss-reflected-multilang.js` — servlet `response.getWriter()`
140
+ write/print + `PrintWriter`, building an HTML string by concat. Static literals
141
+ and OWASP/Commons/ESAPI encoders excluded. The reflected-XSS row (CWE-79) is now
142
+ complete across all 8 languages. **Corpus 156 → 157; matrix 123 → 124/160.**
143
+ F1=1.000; gate green.
144
+
145
+ ## 0.114.0 — Cross-language weak-cipher detection (CWE-327) + 3 corpus entries
146
+
147
+ Extends `crypto-protocol.js` weak-cipher detection to the Go (`crypto/des`,
148
+ `crypto/rc4`), PHP (`openssl_encrypt`/legacy `mcrypt_*` weak algos), and Ruby
149
+ (`OpenSSL::Cipher` DES/RC4/Blowfish) idioms. AES-GCM stays clean. **Corpus
150
+ 153 → 156; matrix 120 → 123/160.** F1=1.000; gate green.
151
+
152
+ ## 0.113.0 — Cross-language static-IV detection (CWE-329) + 7 corpus entries
153
+
154
+ Extends `crypto-protocol.js` static-IV detection to JVM (`IvParameterSpec`/
155
+ `GCMParameterSpec` from a zero array), C# (zero `.IV`), PHP (empty/`str_repeat`
156
+ IV), Ruby (fixed-literal IV), and Go (zero `make([]byte,…)` with a CBC/CTR mode,
157
+ suppressed when filled from `crypto/rand`). CSPRNG-derived IVs stay clean across
158
+ all languages. **Corpus 146 → 153; matrix 113 → 120/160.** F1=1.000; gate green.
159
+
160
+ ## 0.112.0 — Cross-language HTTP response splitting (CWE-113) + 7 corpus entries
161
+
162
+ Extends `response-splitting.js` across all 8 languages (added Go, Ruby, C#,
163
+ Kotlin header sinks) and fixes two latent bugs: the PHP value was read from the
164
+ header-NAME capture group (never fired), and the request-scope param heuristic
165
+ is generalized from Java to Java/Kotlin/C#. Sanitizer recognition extended
166
+ (Python chained `.replace`, Ruby `gsub`/`delete`, Go `NewReplacer`/`ReplaceAll`,
167
+ PHP `str_replace`) so fixed forms stay clean. Biggest single matrix gain this
168
+ session. **Corpus 139 → 146; matrix 106 → 113/160.** F1=1.000; gate green.
169
+
170
+ ## 0.111.0 — Cross-language XXE detector (CWE-611) + 3 corpus entries
171
+
172
+ Extends `xxe.js` (Java/Python) to PHP, Go, Ruby. Each non-JVM XML stack is
173
+ XXE-safe by default, so the detector flags the explicit external-entity opt-in
174
+ (PHP `LIBXML_NOENT`/`DTDLOAD`, Go `Strict=false`/custom Entity map, Nokogiri
175
+ `noent`/`dtdload`/`replace_entities`) rather than the default-safe parse.
176
+ **Corpus 136 → 139; matrix 103 → 106/160.** F1=1.000; gate green.
177
+
178
+ ## 0.110.0 — Cross-language code-injection detector (CWE-94) + 4 corpus entries
179
+
180
+ New `code-injection-multilang.js` closes the CWE-94 gap in Java/C#/Go/Kotlin
181
+ (JS/Python/Ruby `eval` stay with the flow engine). Flags dynamic
182
+ code/expression evaluators on a non-literal argument — Java/Kotlin
183
+ (`ScriptEngine.eval`, GroovyShell, SpEL, MVEL, OGNL), C# (Roslyn `CSharpScript`,
184
+ `DataTable.Compute`), Go (yaegi/gomacro `interp.Eval`, `text/template` parse of a
185
+ non-literal body). A literal argument does not match. **Corpus 132 → 136; matrix
186
+ 99 → 103/160.** F1=1.000; gate green.
187
+
188
+ ## 0.109.0 — Cross-language LDAP injection detector (CWE-90) + 6 corpus entries
189
+
190
+ Extends `ldap-injection.js` from JS/Java/Python to PHP, Go, C#, Ruby, Kotlin.
191
+ Per-language filter patterns gated by an LDAP attribute set (uid/cn/mail/…) so
192
+ it doesn't fire on generic `key=value`. Two precision guards (both fix real FPs
193
+ found while building): a backtrack-proof call-guard so an escaped call at the
194
+ concat isn't flagged, and a file-level escape-API guard (`ldap_escape`,
195
+ `EscapeFilter`, `escape_filter_chars`, …) for escape-then-use spanning lines.
196
+ **Corpus 126 → 132; matrix 93 → 99/160.** F1=1.000; gate green.
197
+
198
+ ## 0.108.1 — Corpus scale-up batch 3 — 117→126, matrix 84→93 cells
199
+
200
+ 9 verified harvest entries across previously-empty cells the scanner already
201
+ detects (no detector change): CWE-798 {java,go,ruby,php}, CWE-916 {java,go,php},
202
+ CWE-918 {ruby}, CWE-601 {java}. Two discipline notes encoded in the generator so
203
+ they don't recur: placeholder `AKIA…EXAMPLE` keys read as pre:FN (the secrets
204
+ scanner correctly skips placeholders — regenerated with realistic keys), and a
205
+ guarded open-redirect "fixed" variant read as post:FP (the detector doesn't yet
206
+ recognize allow-list guards — post now uses literal targets). **126/126
207
+ F1=1.000; gate exit 0.**
208
+
209
+ ## 0.108.0 — Corpus scale-up to 117 + baseline CI gate + cross-language XSS
210
+
211
+ Squashes the v0.106.0–v0.107.3 development churn (7 commits, several corrective)
212
+ into one coherent release.
213
+
214
+ - **Corpus 101 → 117 entries**, all pre:TP post:TN, F1=1.000, verified blind
215
+ (manifest outside the scanned trees; identical under
216
+ `AGENTIC_SECURITY_BLIND_BENCH=1`). Batch 2 harvest (11) + cross-language
217
+ reflected XSS (5).
218
+ - **`xss-reflected-multilang.js`** (new): taint-independent reflected XSS for
219
+ Go/Ruby/PHP/C#/Kotlin (input into an HTML response via concat/interp), each
220
+ with a per-language output-encoder exclusion. JS/Python XSS stays with the
221
+ flow engine.
222
+ - **Corpus baseline CI gate** (the process fix): `corpus-baseline.json` records
223
+ the verdict for every entry; `runner.mjs --check-baseline`/`--update-baseline`
224
+ + npm scripts. The gate fails on any drift — a regressed entry, a removed
225
+ entry, or a new entry that does not pass. First `.github/workflows/ci.yml`
226
+ (repo had no CI): runs `npm test`, verifies the committed bundle matches
227
+ source, runs the corpus gate.
228
+ - **CLAUDE.md gains the "Verification discipline" section.** 117/117, gate exit 0.
229
+
230
+ ## 0.105.0 — Corpus scale-up batch 1 (Tier 4) — 88→101, matrix 64→77 cells
231
+
232
+ First batch of the cve-replay scale-up toward 500, prioritized by empty
233
+ CWE×language cells. 13 new capability entries across 9 previously-empty cells
234
+ (CWE-338 weak PRNG, CWE-94, CWE-352, CWE-22, CWE-502), each verified pre:TP
235
+ post:TN. Two additive detector extensions unlock them: `weak-randomness.js`
236
+ gains snake_case carriers + Java/Kotlin/C# PRNG patterns (CWE-338; SecureRandom
237
+ stays clean), and `php.js`/`ruby.js` gain structural path traversal (CWE-22).
238
+ **Corpus stays F1=1.000; matrix 64 → 77/160.**
239
+
240
+ ## 0.104.0 — Close remaining corpus FN/FP — cve-replay F1 0.929 → 1.000
241
+
242
+ Implements the full remaining recall+precision backlog. Aggregate cve-replay now
243
+ TP=88 FP=0 FN=0 TN=88 (F1=1.000); every language and CWE slice at F1=1.000.
244
+
245
+ - **`secret-concat.js`** (new): language-agnostic hardcoded-secret split across
246
+ concatenated literals — reassembles `'AKIA'+'IOSF…'`/`'ghp'+'_…'`/`'sk'+'_live_…'`
247
+ and matches provider prefixes (CWE-798).
248
+ - **`crypto-protocol.js`**: pyca/cryptography zero/static IV (CWE-329).
249
+ **`weak-randomness.js`**: camelCase security carrier; reclassified CWE-330 →
250
+ CWE-338. **`php.js`**: structural SQLi covers deprecated `mysql_query`.
251
+ **`js-framework-structural.js`**: libxmljs XXE (CWE-611).
252
+ **`python-structural.js`**: embedded-quote-tolerant literal matching +
253
+ `open()`/`send_file` path traversal (CWE-22). **`csrf.js`**: Symfony support.
254
+ - **Precision (`engine.js` `dropGuardedFindings`)**: reflected-XSS
255
+ output-encoding guard (drop CWE-79 when the value passed through a *captured*
256
+ HTML escaper); `dedupeFindingsWithEvidence` prefers an interprocedural flow
257
+ finding over a flat structural match at the same sink.
258
+
259
+ ## 0.103.0 — Go recall: structural detectors (PRD Tier 1)
260
+
261
+ - **`go-structural.js`** (new): SQL injection via a `database/sql` query method
262
+ built with `fmt.Sprintf` or string concat (CWE-89), and path traversal via
263
+ `os.Open`/`os.ReadFile`/etc. built with concat or `fmt.Sprintf` (CWE-22).
264
+ High precision: parameterized queries (`db.Query("… ?", x)`) and
265
+ canonicalized paths (`filepath.Base` + `strings.HasPrefix`) have no
266
+ Sprintf/concat in the sink argument, so they don't match.
267
+ - The centralized guard pass (`dropGuardedFindings`) gained Go path-guard
268
+ tokens (`filepath.Base`/`Abs`, `HasPrefix`).
269
+ - **Measured: 2 FN → TP** (gin-sqli, go-path). **Go F1 0.75 → 1.000;** corpus
270
+ FN 12 → 10; aggregate **F1 0.916 → 0.929.** No new FPs; full gate green.
271
+
272
+ ## 0.102.0 — JS/Python framework recall: structural detectors (PRD Tier 1)
273
+
274
+ JavaScript and Python were the weakest languages (F1 ~0.75) — their remaining
275
+ FNs were framework handlers where input arrives via a framework source
276
+ (`@Query`, `ctx.query`, `request.GET`) and is concatenated into a sink, which
277
+ the taint engine misses without a modeled source.
278
+
279
+ - **`js-framework-structural.js`** (new): SQLi via `.query`/`.execute` concat /
280
+ template-literal (TypeORM/mysql/NestJS, CWE-89); koa-send path traversal
281
+ (CWE-22); `ctx.body` reflected XSS, suppressed by `escape()` (CWE-79);
282
+ NestJS `HttpService.get` SSRF (CWE-918); deep-merge prototype pollution,
283
+ suppressed by a `__proto__`/`FORBIDDEN_KEYS` filter (CWE-1321).
284
+ - **`python-structural.js`** (new): Flask `render_template_string` built from
285
+ input — reflected XSS + SSTI (CWE-79; a Jinja `{{ }}` template is safe and
286
+ does NOT match); Django `.raw`/`.extra` and `cursor.execute` built with
287
+ concat / f-string / `%`-operator format (CWE-89; the `%s` parameterized
288
+ placeholder form does NOT match).
289
+ - High precision (verified on the corpus pre/post pairs + a regression the
290
+ unit test caught: `execute("… %s", [v])` is the *safe* DB-API placeholder,
291
+ not `%`-format injection).
292
+ - **Measured: 8 FN → TP** (flask-xss, hoek, koa-path, koa-xss, nestjs-sqli,
293
+ node-sqli, django, nestjs-ssrf). **Corpus FN 20 → 12; aggregate F1 0.861 →
294
+ 0.916.** JavaScript 0.76 → 0.90, Python 0.82 → 0.88. No new FPs; full gate green.
295
+
296
+ ## 0.101.0 — SSRF/path guard recognition: precision (PRD #1)
297
+
298
+ The recall work (v0.98–v0.100) lifted aggregate F1 ~0.50 → ~0.80 but surfaced a
299
+ precision problem: the scanner flagged **14 *fixed* files as still-vulnerable**
300
+ (precision ~0.83) — mostly SSRF, where the fix adds a host allow/deny check, and
301
+ path traversal, where the fix adds a containment guard. Multiple independent
302
+ detectors decided SSRF/path with no shared notion of "this sink is hardened."
303
+
304
+ - **`ssrf-cloud-metadata.js`**: rule #1 no longer flags the `169.254.169.254`
305
+ literal when it sits in a deny-list / host-comparison guard (the remediation,
306
+ not the vuln).
307
+ - **`engine.js` `dropGuardedFindings(findings, fc)`** — a single, centralized
308
+ pass after all detectors that drops a CWE-918 finding when the sink window has
309
+ a host allow/deny check (deny/allow-list, `getHost`/`hostname` comparison,
310
+ RFC1918/metadata prefix check, `ipaddress`/`getaddrinfo`/`ssrf-req-filter`), or
311
+ a CWE-22 finding when the window has a path containment guard
312
+ (`basename`/`GetFileName`/`secure_filename`/`send_from_directory`, or
313
+ canonicalize + `startsWith`). Uniform across every emitter (regex, structural,
314
+ per-language flow, PY-SAST, CSHARP, GO). The window is **comment-stripped** so
315
+ a "no allow-list / 169.254…" vuln comment can't read as a guard. Opt out:
316
+ `AGENTIC_SECURITY_NO_GUARD_RECOGNITION=1`.
317
+ - **Measured: corpus false-positives 14 → 2** (the 2 remaining are XSS
318
+ escape-html recognition — a different family, next up). **Precision ≈ 0.97;
319
+ aggregate F1 0.80 → 0.861 — past the 0.85 target.** Java + C# now 1.000;
320
+ Python/Go/PHP at 0 FP. **Zero recall loss** (vulnerable-tier TP=68 unchanged) —
321
+ the invariant that proves the guards don't hide real flows.
322
+ - New `guard-recognition.test.js`; full gate green.
323
+
324
+ ## 0.100.0 — Java/Spring + C# recall: structural detectors (PRD Tier 1)
325
+
326
+ Continues the recall-led PRD. The flow-based `csharp.js` and AST/bench Java
327
+ modules miss standalone methods whose tainted-by-convention parameter has no
328
+ in-file source; Java has no string templates, so the shape is concatenation.
329
+
330
+ - **`java-structural.js`** (new): SQLi (executeQuery/createQuery + concat,
331
+ CWE-89), command injection (Runtime.exec/ProcessBuilder + concat, CWE-78),
332
+ path traversal (new File/Paths.get + concat, CWE-22, suppressed by
333
+ canonical/normalize/startsWith guards), SSRF (new URL/URI from a non-literal,
334
+ CWE-918, suppressed by a host allow/deny guard).
335
+ - **`csharp-structural.js`** (new): hardcoded credential in a const/static
336
+ field — including the **split-concat evasion** (`"sk_" + "live_…"`) that
337
+ defeats plain secret regexes (CWE-798, value gated on length/known-prefix so
338
+ header-name constants aren't flagged); guarded SSRF via WebClient/HttpClient
339
+ (CWE-918).
340
+ - **Measured: 5 more FN → TP** (spring-path, spring-ssrf, spring-sqli,
341
+ cs-hardcoded, cs-ssrf). Both modules return clean on the fixed versions.
342
+ - **Session total: corpus false-negatives 35 → 20** (Kotlin 6 + Ruby/PHP 4 +
343
+ Java/C# 5 = 15 FN closed). Additive, language-scoped, no regressions. New
344
+ `java-csharp-structural.test.js` + fixtures; full gate green.
345
+
346
+ Note: spring-ssrf / cs-ssrf still show a pre-existing `post:FP` from other
347
+ detectors on the fixed file (not introduced here) — a precision item for later.
348
+
349
+ ## 0.99.0 — Ruby + PHP recall: structural injection detectors (PRD Tier 1)
350
+
351
+ Continues the recall-led PRD down the corpus FN list. Same root cause as
352
+ Kotlin: the fixtures route request data through a local var
353
+ (`name = params[:name]` / `$f = $r->query->get(...)`) before the sink, so the
354
+ existing rules — which require the literal `params`/`$_GET` token on the sink
355
+ line — miss them.
356
+
357
+ - **`ruby.js`**: structural ActiveRecord SQLi (`.where("… #{x}")` / concat,
358
+ CWE-89) and shell cmdi (backtick / `system` / `exec` with `#{}` or concat,
359
+ CWE-78).
360
+ - **`php.js`**: structural shell cmdi (`shell_exec`/`exec`/`system`/`proc_open`
361
+ with `'…' .` concat or `"… $var"` interpolation, CWE-78) and raw SQLi
362
+ (`DB::raw`/`whereRaw`/`mysqli_query` with concat/interp, CWE-89).
363
+ - High precision: parameterized queries (`.where('… ?', x)`,
364
+ `DB::select('…?', [$x])`), array-form exec (`proc_open(['gzip',$f])`,
365
+ `Open3.capture2('finger', user)`) do **not** match.
366
+ - **Measured: 4 more FN → TP** (rails-sqli, rails-cmdi, symfony-cmdi,
367
+ laravel-sqli). **Total corpus FN this session: 35 → 25** (Kotlin 6 + Ruby/PHP
368
+ 4). Purely additive, language-scoped, no regressions. New
369
+ `ruby-php-structural.test.js` + fixtures; full gate green.
370
+
371
+ Deferred: symfony-csrf (CSRF is a state-change/token detector, FP-prone as a
372
+ regex — needs framework-aware modeling, not a structural injection rule).
373
+
374
+ ## 0.98.0 — Kotlin recall: close all 6 corpus false-negatives (roadmap Tier 1)
375
+
376
+ First execution of the "perfect SAST" PRD, which leads with **recall** (the
377
+ measured CVE-replay F1 ≈ 0.50 is a missed-detection problem). Kotlin was the
378
+ worst single language — 6 FNs across SQLi/cmdi/path/SSRF/XXE/deser — because
379
+ those fixtures are standalone DAO/handler methods with a tainted-by-convention
380
+ parameter and no in-file taint source, so the taint engine saw nothing.
381
+
382
+ - **`sast/kotlin.js`** gains taint-independent **structural** detectors: a
383
+ dangerous sink built with a Kotlin string template (`${x}`) or concat
384
+ (`"…" +`) is the injection shape regardless of variable names —
385
+ SQLi (CWE-89), command injection (CWE-78), path traversal (CWE-22). Plus
386
+ guarded SSRF (CWE-918, suppressed when a host allow/deny check is present),
387
+ insecure XML config XXE (CWE-611, suppressed when secure-processing is set),
388
+ and `ObjectInputStream.readObject` deserialization (CWE-502).
389
+ - High precision: a parameterized query / array-form exec / literal URL / XML
390
+ factory with `setFeature(...)` does **not** match. Verified on the corpus
391
+ pre/post pairs — all 6 vulnerable fixtures fire, all 6 fixed versions stay
392
+ clean (scanner-level).
393
+ - **Measured result: 6 Kotlin FN → TP; total corpus false-negatives 35 → 29.**
394
+ No regressions (purely additive, `.kt`-scoped). New `kotlin-structural.test.js`
395
+ + fixtures; full gate green.
396
+
397
+ Establishes the per-family/per-language porting pattern; next: Rails/Laravel/
398
+ Symfony/Gin/Spring SQLi+cmdi and the remaining second-tier FNs.
399
+
400
+ ## 0.97.0 — Tree-sitter foundation for long-tail languages (roadmap #8)
401
+
402
+ First step of bringing languages with no first-class IR parser
403
+ (rust/solidity/cpp/c/go/swift/dart) onto real AST analysis.
404
+
405
+ - **Optional, ABI-pinned dependency:** `web-tree-sitter` 0.20.8 +
406
+ `tree-sitter-wasms` 0.1.13 as **optionalDependencies**, marked `--external`
407
+ in the ncc build so the committed bundle never embeds WASM (verified: bundle
408
+ stays ~3.5 MB and contains only the external `require`, not the grammars).
409
+ - **`ir/tree-sitter-loader.js`** — lazy, cached, graceful. Loads the runtime +
410
+ grammar at runtime and returns null when absent, so the scanner stays fully
411
+ bootable offline / without the optional deps (long-tail languages fall back
412
+ to the existing pattern detectors).
413
+ - **`sast/tree-sitter-sinks.js`** — first AST-accurate detector, gated behind
414
+ `AGENTIC_SECURITY_TREE_SITTER=1`: Rust shell-spawn command injection (CWE-78,
415
+ `Command::new("sh").arg("-c").arg(<dynamic>)`). Anchoring on real AST nodes
416
+ means the same pattern in a comment or string literal does NOT false-match —
417
+ the precision win over regex.
418
+ - Opt-in + optional-dep gated ⇒ default scan behavior and the committed bundle
419
+ are unchanged. Tests + full gate green.
420
+
421
+ Next on #8: more languages/rules (Solidity dangerous primitives, Go `exec`),
422
+ and eventually CFG construction to route long-tail languages through the full
423
+ taint engine.
424
+
425
+ ## 0.96.0 — Surface coverage honesty + make the corpus reporter runnable
426
+
427
+ Completes the measurement threads from 0.91/0.95 — the data existed but wasn't
428
+ usable. No detection changes.
429
+
430
+ - **Coverage line in the scan report.** `toCLI` now renders a one-line
431
+ blind-spot summary from `_scanMeta`: files scanned, which languages got flow
432
+ analysis (`flow=[…]`) vs pattern-only, how many files were skipped, and the
433
+ unmodeled-sink-candidate count. The #5/#6 data is finally visible to users
434
+ instead of sitting unread in the JSON.
435
+ - **`bench/cve-replay/corpus-status.mjs`** — a runnable report for #10:
436
+ `node bench/cve-replay/corpus-status.mjs [--gaps|--json]` prints progress
437
+ toward the 500-entry target and the empty CWE×language cells. On the current
438
+ corpus: 88/500 entries, 64/160 cells covered, 96 gaps (thinnest: ruby/go/php).
439
+
440
+ ### Deliberately NOT done this round
441
+ - **#8 long-tail languages onto the IR** — needs a tree-sitter dependency
442
+ (can't be installed/bundle-validated in this environment) or five hand-written
443
+ parsers. A dependency decision, not a safe single-pass change.
444
+ - **#1 full** — context-tagged taint lattice (core-engine rewrite).
445
+
446
+ ## 0.95.0 — Stored taint (#2, opt-in) + corpus coverage reporter (#10)
447
+
448
+ - **#2 — Second-order / stored taint** (`sast/stored-taint.js`, **opt-in** via
449
+ `AGENTIC_SECURITY_STORED_TAINT=1`). Flags a value read from a persistence
450
+ store (DB/cache) that flows into an injection sink (XSS/SQLi/cmd/code)
451
+ without re-validation — the stored-XSS / second-order-SQLi shape the forward
452
+ taint engine can't see across the persistence boundary. Lower confidence by
453
+ design (stored data isn't always attacker-controlled), so OFF by default:
454
+ enabling it cannot change default scan behavior.
455
+ - **#10 — Corpus coverage reporter** (`posture/corpus-status.js`). Turns the
456
+ CVE-replay corpus into an actionable map: progress toward the 500-entry
457
+ target and exactly which CWE×language cells have zero ground-truth entries.
458
+ Measures the existing corpus only — it never fabricates entries. This is the
459
+ measurement substrate that makes scaling the corpus prioritizable.
460
+
461
+ Tests for both; full gate green.
462
+
463
+ ### Still genuinely open (need a decision or are research-grade)
464
+ - **#8 long-tail languages onto the IR** — five net-new IR parsers, or a
465
+ tree-sitter dependency (which conflicts with the offline-degradation rule).
466
+ This is a dependency decision, not a quick fix.
467
+ - **#1 full** — context-tagged taint lattice (a core-engine rewrite); the
468
+ practical wrong-context slice already ships.
469
+
470
+ ## 0.94.0 — Same-file-preference call resolution (roadmap #3)
471
+
472
+ The cross-file resolver (`ir/callgraph.js` `resolve()`) was name-based: a bare
473
+ name defined in several files (`handler`, `save`, `query`, …) resolved to
474
+ whichever file Map was iterated first, mis-targeting interprocedural taint to a
475
+ same-named function in an unrelated file (wrong-target FPs and missed-target
476
+ FNs).
477
+
478
+ - `resolve(name, callerFile)` now **prefers the caller's own file** when it
479
+ defines the name — overwhelmingly the intended callee. The taint engine
480
+ threads the caller's file at all three resolution sites (assign-call,
481
+ plain-call, higher-order callback).
482
+ - **Backward-compatible by construction:** with no `callerFile` or no local
483
+ match, the original resolution order is unchanged, so no resolution edge is
484
+ ever dropped (no new false negatives). The full gate — incl. interproc-k2's
485
+ context test, deep-taint, calibration — stays green.
486
+ - New `test/callgraph-resolve.test.js`.
487
+
488
+ ## 0.93.0 — CSV/formula injection (CWE-1236) + scoped validation-lib sanitizers (#7)
489
+
490
+ - **CSV / formula injection** (`sast/csv-injection.js`, CWE-1236): flags user
491
+ data written to a CSV/spreadsheet writer without neutralizing leading formula
492
+ characters (`= + - @` / tab). High precision — requires a CSV-write API + a
493
+ user-taint hint on the same statement and no formula-escape helper nearby.
494
+ - **#7 validation-library sanitizers (correct, narrow):** zod `safeParse`/
495
+ `parseAsync` and class-validator `validateOrReject` are now modeled as
496
+ sanitizers **scoped to `mongo-operator` (NoSQL/operator injection) ONLY**.
497
+ Validating a typed shape defeats operator injection, but a validated string
498
+ is still an XSS/SQL payload — so they are deliberately NOT tagged for those
499
+ families (a blanket tag would cause false negatives). Distinctive callees
500
+ only, to avoid colliding with `JSON.parse`.
501
+
502
+ Fixtures + tests for both; full gate green.
503
+
504
+ ## 0.92.0 — Front-end hygiene detectors (3 verified-missing, additive)
505
+
506
+ New `sast/frontend-hygiene.js` — three high-precision client-side detectors,
507
+ each a verified coverage gap (no prior matches in the detector tree). All
508
+ additive: they emit new finding classes and cannot reduce existing detection.
509
+
510
+ - **Reverse tabnabbing (CWE-1022):** `<a target="_blank">` without
511
+ `rel="noopener"` — the opened page can rewrite `window.opener`. (low)
512
+ - **Missing Subresource Integrity (CWE-353):** a cross-origin
513
+ `<script>` / stylesheet `<link>` with no `integrity=` — a compromised CDN
514
+ runs in your origin. Skips same-origin/relative assets. (medium/low)
515
+ - **Angular sanitizer bypass (CWE-79):** `DomSanitizer.bypassSecurityTrust*`
516
+ on a non-literal value explicitly disables Angular's XSS protection on
517
+ attacker-influenced data. Skips constant-string arguments. (high)
518
+
519
+ Fixtures + 6 tests; full gate green.
520
+
521
+ ## 0.91.0 — Coverage honesty (#5+#6) + wrong-context encoding (#1 slice)
522
+
523
+ Additive precision/trust features that cannot reduce existing detection.
524
+
525
+ ### #5 + #6 — Analysis-coverage honesty report
526
+ - New `posture/coverage-report.js`. `_scanMeta` now publishes the scanner's
527
+ blind spots: per-language **analysis tier** (which languages got the IR +
528
+ taint engine vs. pattern-only — c/c++/rust/swift/solidity/dart are
529
+ pattern-only today), a `filesDenseSkipped` counter (dense files were
530
+ previously dropped with no count), and **unmodeled-sink candidates** —
531
+ dangerous-call shapes (eval/exec/deserialize/yaml.load/…) with no finding on
532
+ their line, i.e. recall blind spots to verify.
533
+
534
+ ### #1 (practical slice) — Wrong-context output encoding (CWE-79)
535
+ - New `sast/wrong-context-sanitizer.js` flags an HTML-entity encoder
536
+ (`escapeHtml`/`htmlspecialchars`/`he.encode`/`lodash.escape`) applied to a
537
+ value used in a URL context (`href`/`src`/`location`). HTML-entity encoding
538
+ does NOT neutralize `javascript:`/`data:` schemes, so the value is still XSS
539
+ while looking sanitized. High precision: excludes `encodeURIComponent` (a
540
+ different, non-XSS mistake) and suppresses when a URL-scheme allow-list is
541
+ near. Fixtures + tests; full gate green.
542
+
543
+ ### Deferred (need core-engine work or carry correctness traps — not faked)
544
+ - **#1 (full):** context-tagged taint through the lattice — the engine's taint
545
+ is binary and can't express "sanitized-for-HTML vs -for-JS". Core change.
546
+ - **#7 validation libraries:** modeling zod/joi/etc. as blanket sanitizers
547
+ would cause FALSE NEGATIVES — a schema-validated string is still an XSS/SQL
548
+ payload. Needs schema-type awareness (validation defeats type-confusion /
549
+ NoSQL-operator injection, not output injection). Deferred deliberately.
550
+ - **#2 stored/second-order taint, #3 import/type-aware call resolution,
551
+ #8 long-tail languages onto the IR, #10 corpus scale** — each its own lift.
552
+
553
+ ## 0.90.0 — Context-sensitive taint completed + bounded (roadmap #2, FR-SEM-2)
554
+
555
+ The interprocedural engine was already value-context sensitive at the
556
+ assign-call site (a distinct summary per entry-taint-state, computed lazily;
557
+ v0.66). This release completes and bounds it.
558
+
559
+ - **Plain-call sites now lazily compute context-specific summaries too.**
560
+ Previously the plain (non-assign) call site only did a cache `get`, so a
561
+ param mutated *only* when the callee is invoked with tainted input was
562
+ missed there. It now mirrors the assign-call site (compute under the actual
563
+ tainted-arg context on a miss).
564
+ - **Per-function context cap** in `SummaryCache` bounds the number of distinct
565
+ non-empty entry contexts kept per function so lazy per-call-site computation
566
+ can't blow up. Over the cap → reuse the empty-entry (monovariant) summary.
567
+ Tunable via `AGENTIC_SECURITY_KCFA_MAX_CONTEXTS` (default 16; **0 = pure
568
+ monovariant**, a clean kill-switch).
569
+ - **Docs corrected:** `summaries.js` and `dataflow/CLAUDE.md` previously called
570
+ the engine "k=1 monovariant / one summary under empty entry." It is in fact
571
+ value-context sensitive; the real remaining limits are call-string (k>1)
572
+ sensitivity and param-level (not access-path-level) entry granularity.
573
+ - New `test/kcfa-context.test.js` (context-sensitivity + cap + kill-switch +
574
+ clear); full gate green.
575
+
576
+ ## 0.89.0 — Per-language metrics (#9) + roadmap audit (#3, #5 already shipped)
577
+
578
+ Continuing the multi-language SAST roadmap. Investigation revealed several
579
+ items were already implemented — so this release delivers the one genuinely
580
+ missing safe item (#9) and corrects the record, rather than re-building what
581
+ exists.
582
+
583
+ ### #9 — Per-language precision/recall (new)
584
+ - `holdout-eval.js` now records a `language` per labeled sample (explicit
585
+ `language` field or derived from the `file` extension) and exposes
586
+ `perLanguage()` + `summarizePerLanguage()`.
587
+ - `evaluateHeldOut` returns a `perLanguage` breakdown and **flags any language
588
+ whose precision trails the aggregate by >0.15** (n≥20) — the regression an
589
+ aggregate would otherwise mask (a 90%-JS corpus hiding poor Ruby precision).
590
+ - New tests in `test/holdout-eval.test.js`.
591
+
592
+ ### #3 — Already implemented; doc corrected
593
+ Audit found mutated-parameter taint (`applyAtCallSite`), higher-order callback
594
+ taint (`_higherOrderInvocations` fed back into the worklist), and recursion via
595
+ a multi-pass fixed point (`MAX_FP_ITERS`) were all shipped in v0.66 and covered
596
+ by `interproc-k2` / `closure-capture` / `phase6-taint` tests. The stale
597
+ `dataflow/CLAUDE.md` "what we do NOT model" section is corrected.
598
+
599
+ ### #5 — Already at parity
600
+ The source/sink catalog already spans Spring, ASP.NET, Gin, Echo, Fiber, Chi,
601
+ Gorilla, Buffalo, Laravel, Symfony, Rails, Sinatra, Ktor, JDBC/JPA/Hibernate,
602
+ Dapper/ADO across all 8 languages. No new work needed.
603
+
604
+ ### Still deferred (research-grade; will ship as their own releases)
605
+ #1 universal IR (no tree-sitter dep; `universal-ir.js` unwired), #2 k-CFA
606
+ context-sensitivity (FR-SEM-2), #4 auto-derived library summaries, #7 dynamic
607
+ dispatch + type inference, #8 incremental-by-default (needs a cold==warm gate),
608
+ #10 LLM closed-loop validator. These are not faked into the engine.
609
+
610
+ ## 0.88.0 — Proof-gate precision pass (multi-language SAST roadmap #6)
611
+
612
+ First flagship of the "perfect multi-language SAST" program: report only
613
+ provably-feasible flows, and demote — never drop — flows we can prove are
614
+ clean or infeasible.
615
+
616
+ - **New `dataflow/proof-gate.js`** consolidates the engine's two independent
617
+ flow-proof signals (`provenClean` from `proven-clean.js`, `_provenUnreachable`
618
+ from `exploit-prover.js`) into one verdict per finding:
619
+ `finding.proof = { verdict: 'feasible' | 'proven-clean' | 'proven-infeasible' | 'unproven', reasons[] }`.
620
+ - **Wired the previously-dead `proven-clean.js`** into `runDeepAnalysis` — SQL
621
+ sinks reached only through a parameterizer are now proven clean by default.
622
+ - **Recall-preserving demotion:** proven-clean / proven-infeasible findings get
623
+ lowered `confidence` + `confidenceTier` + `exploitabilityTier` (and rank below
624
+ feasible findings), but **`severity` is left untouched** so a heuristic proof
625
+ can never hide a finding from a severity-based CI gate.
626
+ - Default on; opt out with `AGENTIC_SECURITY_NO_PROOF_GATE=1`. New
627
+ `test/proof-gate.test.js`; full gate green.
628
+
629
+ Remaining roadmap items (#1 universal IR, #2 k-CFA, #3 dormant taint paths,
630
+ #4 library summaries, #5 framework catalog parity, #7 dynamic dispatch,
631
+ #8 incremental-by-default, #9 per-language metrics, #10 LLM gate) ship as
632
+ their own benchmarked releases.
633
+
634
+ ## 0.87.0 — Sharpen the 12
635
+
636
+ Ergonomics + power features for the 12-command surface left after the
637
+ v0.86.0 consolidation.
638
+
639
+ ### Cross-cutting
640
+ - **Legacy-alias redirect hook** (`UserPromptSubmit`, `hooks/legacy-alias-redirect.js`):
641
+ typing a removed alias (`/status`, `/show-findings`, `/harden`, …) now
642
+ injects context that maps it to the new dispatcher mode, so the request
643
+ still runs. Covers all 44 removed aliases.
644
+ - **Trend-aware `/secure` router**: compares the last two scans and shows a
645
+ `↑ / → / ↓` arrow with what changed (never invents a trend from one scan).
646
+ - **Bare-invocation mode menus** and uniform `--json` documented across the
647
+ dispatchers.
648
+ - **Task-oriented `/secure --help`** + an old→new alias map.
649
+
650
+ ### Per-command
651
+ - `/scan --pick` — interactive mode menu.
652
+ - `/fix --checkpoint` — run a batch fix on a throwaway git branch (atomic revert).
653
+ - `/compliance --gap` — Not-Compliant worklist with the exact closing command per control; `--format oscal|json` machine-readable export.
654
+ - `/supply` — offer to bundle safe patch/minor upgrades into one PR after `--check`.
655
+ - `/posture` (bare) — combined dashboard (status + grade + trend).
656
+ - `/find-and-fix-everything` — auto checkpoint branch + PR-ready summary.
657
+ - `/triage` — order findings likely-FP-first from triage history.
658
+ - `/three-agent-review` — echo the call/wall-time budget before running.
659
+ - `/ci` — validate generated workflow YAML + offer a PR.
660
+ - `/labs` — graduation-status table for experimental modes.
661
+ - `/setup --all` — install hooks + CI + bodyguard + destructive-guard in one pass.
662
+
3
663
  ## 0.76.0 — Command consolidation: 80 → 38 slash commands
4
664
 
5
665
  Simplified the command surface from 80 individual slash commands down to