@clear-capabilities/agentic-security-scanner 0.134.0 → 0.136.9

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 (170) hide show
  1. package/CHANGELOG.md +432 -0
  2. package/bin/agentic-security-audit.js +2 -1
  3. package/bin/agentic-security-consistency.js +2 -1
  4. package/bin/agentic-security.js +448 -74
  5. package/dist/113.index.js +16 -7
  6. package/dist/117.index.js +3 -1
  7. package/dist/178.index.js +1 -1
  8. package/dist/207.index.js +5 -4
  9. package/dist/220.index.js +5 -3
  10. package/dist/238.index.js +4 -4
  11. package/dist/317.index.js +300 -0
  12. package/dist/384.index.js +1 -1
  13. package/dist/435.index.js +196 -21
  14. package/dist/444.index.js +20 -11
  15. package/dist/449.index.js +8 -1
  16. package/dist/513.index.js +7 -3
  17. package/dist/526.index.js +6 -6
  18. package/dist/637.index.js +1 -1
  19. package/dist/675.index.js +7 -5
  20. package/dist/839.index.js +4 -3
  21. package/dist/905.index.js +1173 -0
  22. package/dist/agentic-security.mjs +14 -14
  23. package/dist/agentic-security.mjs.sha256 +1 -1
  24. package/dist/compliance-frameworks/ccpa.json +32 -0
  25. package/dist/compliance-frameworks/eu-ai-act.json +51 -0
  26. package/dist/compliance-frameworks/gdpr.json +45 -0
  27. package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
  28. package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
  29. package/dist/compliance-frameworks/nist-csf-2.json +73 -0
  30. package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
  31. package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
  32. package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
  33. package/package.json +24 -12
  34. package/src/badge.js +2 -1
  35. package/src/dataflow/CLAUDE.md +10 -4
  36. package/src/dataflow/builtin-summaries.js +1 -1
  37. package/src/dataflow/cross-service-taint.js +2 -1
  38. package/src/dataflow/engine.js +324 -60
  39. package/src/dataflow/ifds-precise.js +6 -4
  40. package/src/dataflow/implicit-flow.js +68 -36
  41. package/src/dataflow/incremental.js +25 -8
  42. package/src/dataflow/index.js +2 -1
  43. package/src/dataflow/proven-clean.js +41 -0
  44. package/src/dataflow/sanitizer-gate.js +35 -9
  45. package/src/dataflow/sanitizer-proof.js +21 -3
  46. package/src/dataflow/stub-aware-filter.js +36 -13
  47. package/src/dataflow/summaries.js +21 -2
  48. package/src/discovery/CLAUDE.md +10 -0
  49. package/src/discovery/index.js +175 -3
  50. package/src/discovery/llm-invoke.js +90 -1
  51. package/src/discovery/memory.js +163 -0
  52. package/src/engine.js +247 -50
  53. package/src/integrations/tickets.js +7 -6
  54. package/src/ir/CLAUDE.md +4 -1
  55. package/src/ir/balanced-call.js +55 -0
  56. package/src/ir/ir-stats.js +1 -1
  57. package/src/ir/parser-cpp.js +1 -1
  58. package/src/ir/parser-cs.js +62 -9
  59. package/src/ir/parser-go.js +29 -11
  60. package/src/ir/parser-java.js +96 -19
  61. package/src/ir/parser-js.js +151 -20
  62. package/src/ir/parser-php.js +44 -9
  63. package/src/ir/parser-rb.js +37 -7
  64. package/src/ir/ssa.js +6 -1
  65. package/src/leaderboard.js +3 -2
  66. package/src/llm-validator/consistency.js +6 -2
  67. package/src/llm-validator/index.js +1 -2
  68. package/src/lsp/server.js +28 -2
  69. package/src/mcp/CLAUDE.md +9 -2
  70. package/src/mcp/audit.js +2 -1
  71. package/src/mcp/redact.js +26 -0
  72. package/src/mcp/tools.js +159 -17
  73. package/src/posture/CLAUDE.md +45 -8
  74. package/src/posture/accuracy-scorecard.js +67 -1
  75. package/src/posture/agents-memory.js +5 -3
  76. package/src/posture/aibom.js +12 -8
  77. package/src/posture/auditor-walkthrough.js +111 -10
  78. package/src/posture/auth-posture-import.js +5 -4
  79. package/src/posture/autopilot.js +8 -1
  80. package/src/posture/calibration-drift.js +11 -5
  81. package/src/posture/calibration.js +24 -2
  82. package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
  83. package/src/posture/compliance-frameworks/owasp-asvs-5.json +1 -1
  84. package/src/posture/compliance-policy.js +40 -10
  85. package/src/posture/confidence.js +44 -10
  86. package/src/posture/corpus-enroll.js +9 -5
  87. package/src/posture/corpus-match.js +19 -0
  88. package/src/posture/csharp-analysis.js +62 -3
  89. package/src/posture/custom-rules.js +7 -5
  90. package/src/posture/cve-alert-daemon.js +6 -5
  91. package/src/posture/dep-add-guard.js +2 -1
  92. package/src/posture/deploy-platform.js +4 -1
  93. package/src/posture/deterministic.js +3 -2
  94. package/src/posture/drift.js +7 -1
  95. package/src/posture/epss.js +13 -1
  96. package/src/posture/evidence-bundle.js +276 -0
  97. package/src/posture/exploitability-probability.js +15 -2
  98. package/src/posture/falsification.js +23 -2
  99. package/src/posture/feature-flags.js +3 -2
  100. package/src/posture/findings-memory.js +3 -3
  101. package/src/posture/fix-history.js +5 -2
  102. package/src/posture/fix-metrics.js +5 -5
  103. package/src/posture/fix-plan.js +2 -1
  104. package/src/posture/fix-verify-loop.js +10 -1
  105. package/src/posture/grader-calibration.js +3 -4
  106. package/src/posture/iac-reachability.js +14 -8
  107. package/src/posture/integrity.js +25 -7
  108. package/src/posture/intent-context.js +2 -1
  109. package/src/posture/learning.js +4 -3
  110. package/src/posture/license-attributions.js +5 -7
  111. package/src/posture/license-graph.js +2 -1
  112. package/src/posture/license-policy.js +2 -1
  113. package/src/posture/model-rescan.js +69 -3
  114. package/src/posture/mttr.js +5 -0
  115. package/src/posture/network-policy-import.js +3 -2
  116. package/src/posture/poc-inprocess.js +27 -8
  117. package/src/posture/pqc-migration-plan.js +7 -5
  118. package/src/posture/pr-augment.js +8 -5
  119. package/src/posture/privacy-framework.js +262 -0
  120. package/src/posture/regression-test-gen.js +23 -8
  121. package/src/posture/reverse-blast-radius.js +5 -1
  122. package/src/posture/risk-dollars.js +20 -3
  123. package/src/posture/router.js +5 -4
  124. package/src/posture/ruleset-version.js +2 -2
  125. package/src/posture/runtime-correlation.js +2 -1
  126. package/src/posture/sbom-diff.js +12 -3
  127. package/src/posture/sca-policy.js +7 -4
  128. package/src/posture/scan-checkpoint.js +15 -0
  129. package/src/posture/secret-history.js +20 -11
  130. package/src/posture/security-trend.js +7 -1
  131. package/src/posture/stack-playbook.js +22 -1
  132. package/src/posture/state-dir.js +34 -0
  133. package/src/posture/telemetry-ingest.js +4 -3
  134. package/src/posture/threat-model-auto.js +4 -1
  135. package/src/posture/threat-model-grounding.js +13 -3
  136. package/src/posture/time-to-fix.js +3 -2
  137. package/src/posture/triage-memory.js +3 -2
  138. package/src/posture/validator-metrics.js +10 -3
  139. package/src/posture/verifier.js +32 -57
  140. package/src/posture/waf-ingest.js +6 -5
  141. package/src/posture/watch-mode.js +4 -3
  142. package/src/report/index.js +183 -14
  143. package/src/runScan.js +1 -1
  144. package/src/sast/_comment-strip.js +15 -4
  145. package/src/sast/_secret-entropy.js +1 -1
  146. package/src/sast/authz.js +6 -4
  147. package/src/sast/bench-shape/index.js +2 -7
  148. package/src/sast/claude-md-prompt-injection.js +14 -3
  149. package/src/sast/cloud-iam.js +60 -7
  150. package/src/sast/code-injection-multilang.js +29 -0
  151. package/src/sast/cpp-bench-extras.js +1 -1
  152. package/src/sast/csrf.js +7 -5
  153. package/src/sast/env-hygiene.js +5 -2
  154. package/src/sast/iac-terraform.js +25 -0
  155. package/src/sast/java-bench-extras.js +1 -1
  156. package/src/sast/java-constant-fold.js +5 -5
  157. package/src/sast/llm-owasp.js +4 -2
  158. package/src/sast/mcp-audit.js +7 -0
  159. package/src/sast/pipeline.js +8 -0
  160. package/src/sast/prompt-template.js +8 -6
  161. package/src/sast/prototype-pollution.js +6 -2
  162. package/src/sast/redos-nfa.js +6 -6
  163. package/src/sast/secret-concat.js +13 -2
  164. package/src/sast/ssrf-cloud-metadata.js +6 -3
  165. package/src/sast/xss-reflected-multilang.js +1 -1
  166. package/src/sast/xxe.js +1 -1
  167. package/src/sca/CLAUDE.md +3 -4
  168. package/src/sca/container.js +35 -3
  169. package/src/sca/dep-confusion.js +9 -1
  170. package/src/sca/sarif-ingest.js +0 -187
package/CHANGELOG.md CHANGED
@@ -1,5 +1,437 @@
1
1
  # Changelog
2
2
 
3
+ > **A note on `docs/*_PRD.md` references below.** Product requirement documents in
4
+ > this project are removed once their workstreams land — the durable parts move
5
+ > into the code and tests that enforce them, where they cannot drift out of date.
6
+ > Entries below cite PRD files that were live at the time of that release and no
7
+ > longer exist. They are left as written: a changelog records what was true when
8
+ > it was written, and rewriting past entries to hide a since-deleted file would
9
+ > make the history less accurate, not more.
10
+
11
+
12
+ ## 0.136.9 — the real bug: the single-file bundle was never actually self-contained
13
+
14
+ 0.136.8's diagnostic logging answered the question immediately:
15
+
16
+ ```
17
+ Error [ERR_MODULE_NOT_FOUND]: Cannot find module
18
+ '.../scanner/dist/449.index.js' imported from
19
+ '.../scanner/dist/agentic-security.mjs'
20
+ ```
21
+
22
+ `dist/agentic-security.mjs` is documented and committed as a self-contained
23
+ single-file bundle — `.gitignore`'s own comment says the reusable
24
+ `scan.yml` workflow fetches *only that one file* from
25
+ raw.githubusercontent.com so downstream users need no install step. That
26
+ was never quite true. `bin/agentic-security.js` lazily loads each
27
+ subcommand's implementation with `await import('../src/...')` — about 58
28
+ call sites, deliberately, so running `agentic-security scan` doesn't pay
29
+ the cost of loading every posture/discovery/compliance module `agentic-security
30
+ compliance` or `agentic-security hunt` would need. `ncc` code-splits every
31
+ one of those into its own `dist/NNN.index.js` chunk rather than inlining
32
+ it, and the chunk is loaded at runtime via a path relative to the bundle's
33
+ own location on disk — but `.gitignore` only ever allowlisted the main
34
+ `.mjs`, its sha256 sidecar, and the compliance-frameworks data. All 38
35
+ chunk files were silently gitignored. `.claude/settings.local.json`-style
36
+ invisible: they exist on any machine that has ever run `npm run build`
37
+ locally (which is every contributor's, permanently, from the first
38
+ `npm install`), so nobody — human or gate — had a checkout that lacked
39
+ them until this release's hosted CI runs did.
40
+
41
+ This is the third occurrence of the identical bug class in this file's own
42
+ history — `.gitignore` already carries a comment about the same thing
43
+ happening to `compliance-frameworks/` data before ("the shipped CLI
44
+ silently listed ZERO frameworks and exited 0"). Same shape both times: a
45
+ single tracked entry point quietly depends on sibling files nobody
46
+ allowlisted.
47
+
48
+ Fixed by allowlisting `scanner/dist/*.index.js` and committing all 38
49
+ current chunks (744 KB), and by adding a permanent regression guard
50
+ (`test/dist-chunks-tracked.test.js`, wired into `test:lifecycle`) that
51
+ diffs `dist/*.index.js` on disk against `git ls-files dist` and fails
52
+ loud, by name, the moment a future build produces a chunk nobody
53
+ allowlisted — rather than waiting for a lazy code path to hit it in
54
+ production. Verified RED against the pre-fix git state (all 38 chunks
55
+ correctly reported untracked) and GREEN after staging them.
56
+
57
+ ## 0.136.8 — instrumenting a real, still-unexplained hosted-CI-only failure
58
+
59
+ 0.136.7's hosted release run got past every other check and failed on
60
+ exactly one: `test/mttr.test.js`'s CLI-wiring test, which asserts a real
61
+ `agentic-security scan` reports an `mttr` field in `last-scan.json`. That
62
+ field comes from a deliberately best-effort code path in `cmdScan`
63
+ (`bin/agentic-security.js`) wrapped in a bare `catch { /* MTTR is
64
+ best-effort — never block a scan write */ }` — correct as a product
65
+ decision (a scan must never fail because a secondary metric couldn't be
66
+ computed), but it means whatever throws inside that block has never been
67
+ visible to anyone, including this investigation.
68
+
69
+ This is now confirmed NOT the shared-runner flakiness 0.136.5/0.136.6
70
+ blamed it as: reran the identical failed job against the identical commit
71
+ (`gh run rerun --failed`, no code change) and got the exact same failure,
72
+ same file, same line, same message, a fourth consecutive time. Extensive
73
+ local reproduction attempts — simulating `CI=true`/`GITHUB_ACTIONS=true`,
74
+ running the full 1328-test `test:posture` scope concurrently to match CI's
75
+ exact invocation shape, invoking the real bundled CLI directly under the
76
+ same env vars — all passed cleanly. Whatever this is, it is deterministic
77
+ on GitHub's hosted runner and has not reproduced anywhere else tried so far.
78
+
79
+ Rather than keep guessing, the silent catch now logs the actual error to
80
+ stderr whenever `CI`/`GITHUB_ACTIONS` is set (or `AGENTIC_SECURITY_MTTR_DEBUG=1`
81
+ locally), and the test's own assertion message now includes the scan
82
+ subprocess's stderr. Scan behavior is unchanged — this is instrumentation
83
+ only, shipped specifically to get a real answer out of the next hosted run
84
+ instead of another guess.
85
+
86
+ ## 0.136.7 — the doc-drift checker was resolving paths outside the repo
87
+
88
+ 0.136.6's hosted release run failed on a check this same audit added a few
89
+ releases ago: `check-doc-drift.mjs`'s own regression test, "the real
90
+ repository currently has zero mechanically-checkable drift," flagged root
91
+ CLAUDE.md's documented (and correctly gitignored) `.claude/settings.local.json`
92
+ reference as a dangling path.
93
+
94
+ The actual defect was in `resolveCandidate`'s search bases, not in the
95
+ reference: for a CLAUDE.md near the repo root, one of the fallback bases is
96
+ "two directories up from the CLAUDE.md's own directory" — meant for nested
97
+ CLAUDE.md files reaching back toward the repo root, but for the ROOT
98
+ CLAUDE.md itself that lands OUTSIDE the checkout entirely, in whatever
99
+ happens to be the parent of wherever the repo was cloned. On the
100
+ maintainer's laptop that's their home directory, which happens to contain
101
+ an unrelated, machine-global `~/.claude/settings.local.json` from ordinary
102
+ Claude Code usage — so the checker "resolved" the reference against a file
103
+ that has nothing to do with this project, passed locally, and failed on a
104
+ clean CI checkout where no such coincidence exists. `resolveCandidate` now
105
+ rejects any candidate base or resolved path outside the repo root, and
106
+ `settings.local.json` — genuinely optional, gitignored, user-created — joins
107
+ the checker's existing known-example-basename allowlist as a second,
108
+ independent fix.
109
+
110
+ Reproduced without touching CI: moved the local override file aside,
111
+ confirmed the existing regression test still passed (proving the checker was
112
+ resolving against something else entirely), traced it to the home-directory
113
+ escape, fixed both the escape and the allowlist gap, verified clean with the
114
+ file present and absent, then restored it.
115
+
116
+ ## 0.136.6 — correction: the "flaky" dataflow tests were a real, deterministic gap
117
+
118
+ 0.136.5's entry below called a cluster of `test:dataflow` failures on the
119
+ hosted runner resource-contention flakiness, because a second, simultaneous
120
+ workflow run of the identical commit showed a completely different failure
121
+ and neither reproduced locally. That diagnosis was wrong, and the actual
122
+ cause is more interesting: 13 test files pass `runScan(dir, { deep: true })`
123
+ to exercise the interprocedural taint engine directly, but never opt into
124
+ `AGENTIC_SECURITY_DEEP_IN_CI` — and `engine.js` deliberately auto-disables
125
+ deep mode under any detected CI environment unless that second flag is also
126
+ set, precisely so a pathological file can't hang a CI pipeline. Every one of
127
+ those 13 files has therefore been silently CI-broken (never actually
128
+ exercising the deep engine there, only recording the "skipped in CI"
129
+ placeholder finding) since the day it was written — invisible until this
130
+ release's tag push put 62 accumulated commits through hosted CI for the
131
+ first time. Reproduced deterministically with `CI=true node --test
132
+ <file>.test.js` locally (no CI service required), fixed by threading
133
+ `deepInCi: true` alongside `deep: true` at all 21 call sites across those 13
134
+ files, and confirmed both directions: `npm test` is clean with `CI` unset
135
+ and with `CI=true` set.
136
+
137
+ The earlier flakiness diagnosis wasn't entirely wrong — the *other* workflow
138
+ run's single MTTR-wiring failure genuinely didn't reproduce on a third run
139
+ and remains unexplained — but it was wrong about *this* failure cluster,
140
+ which was 100% reproducible once isolated with the right environment
141
+ variable rather than blamed on shared-runner load.
142
+
143
+ ## 0.136.5 — the release workflow gets its own missing dependency
144
+
145
+ 0.136.4 was tagged and its release workflow ran — for the first time ever,
146
+ since the 60-odd commits it carried had accumulated across many local
147
+ sessions without a single hosted-CI push. The gate caught a real gap in
148
+ itself: `nist-catalog-freshness` (a check this same release adds, see below)
149
+ shells out to `scripts/nist-compliance/build-catalog.py`, which needs
150
+ `openpyxl` to read the source workbook. That's present on the maintainer's
151
+ machine via a system Python install, but nothing installs it on the hosted
152
+ runner — so the check could never have passed there. `release.yml` now
153
+ installs it explicitly before the gate runs.
154
+
155
+ The same CI run also showed roughly a dozen dataflow tests fail — a
156
+ completely different set than the *other* workflow (`ci.yml`) failed on the
157
+ same commit at the same time (one flaky MTTR-wiring test, no overlap with the
158
+ dataflow set). Neither set reproduces locally, isolated or otherwise. That
159
+ non-overlap is the signature of resource-contention flakiness on a shared
160
+ runner, not a deterministic regression, so it isn't chased further here — but
161
+ it's worth knowing about if a future release gate flakes on `npm test` again.
162
+
163
+ Per the project's own precedent (see 0.136.1 below): a tag that failed to
164
+ publish stays where it is rather than being moved. 0.136.4 is that tag. This
165
+ ships as 0.136.5 instead, otherwise identical.
166
+
167
+ ## 0.136.4 — dominance-correct implicit-flow taint, and wiring three dead-reachable tools
168
+
169
+ A capability-PRD follow-up audit found several places where a real, tested
170
+ mechanism existed in the code but nothing in the product could actually reach
171
+ it. This release closes those gaps rather than documenting them further.
172
+
173
+ **`implicit-flow.js` now uses real dominance, not a depth counter.** The
174
+ branch-scoping check for implicit taint (does a constant-argument sink sit
175
+ *inside* a tainted branch, or after it closes?) was a path-dependent DFS
176
+ depth-counter, which cannot distinguish a join point from a nested branch and
177
+ both over- and under-attributes depending on CFG shape. It's replaced with a
178
+ proper dominance check (`ssa.js`'s `computeDominators`, already used for φ-node
179
+ placement, now exported and reused here) plus a predecessor-count "sole
180
+ parent" test — needed because an else-less `if`'s CFG lowering links the
181
+ condition directly to the join node, which a naive dominance check alone would
182
+ still misread as a branch root. `dataflow/engine.js`'s implicit-flow post-pass
183
+ is split into two correctly-scoped passes: a sink-call-must-be-inside-the-
184
+ branch check for constant-argument leaks, and an ungated check for
185
+ already-tainted-variable-as-argument leaks, which don't need the sink itself
186
+ to be branch-local.
187
+
188
+ **Three previously-dead mechanisms are now reachable.** `verify_fix`'s PoC-
189
+ recheck leg always reported `not-requested` — the caller never had the PoC to
190
+ pass it, so it's now looked up server-side from the finding's own
191
+ `last-scan.json` entry. `fix-honesty-gate.js`'s deterministic honesty checks
192
+ (vague-assurance residual prose, unbacked false-positive verdicts) were fully
193
+ built and consulted by the verifier, but `fixMeta` was never in `apply_fix` or
194
+ `verify_fix`'s MCP schema, so no caller could ever supply one; a dishonest
195
+ `fixMeta` now blocks the write itself, not just the report. `verifyRunAttestation`
196
+ had no CLI caller at all — `verify-attestation` now auto-detects a
197
+ run-attestation shape and re-scans the target to check it reproduces the
198
+ attested digest, backed by two new release-gate checks
199
+ (`attestation-self-check`, `nist-catalog-freshness`).
200
+
201
+ **Stale docs, fixed instead of flagged.** `docs/compliance/{nist-ai-600-1,
202
+ owasp-asvs}-coverage.md` carried static control tables that drifted from the
203
+ live evaluator; both now point at the `/compliance` walkthrough/report instead
204
+ of duplicating data that can go stale. The ASVS doc also had a genuine
205
+ version mismatch (4.0.3 vs. the 5.0 catalog actually in use). A new
206
+ `scripts/check-doc-drift.mjs` catches this class of staleness mechanically —
207
+ it resolves every backtick-quoted path/export reference in a CLAUDE.md file
208
+ against the real filesystem — after this audit found several by hand.
209
+
210
+ **CVE-replay corpus:** four new capability entries (IaC open-ingress, LLM
211
+ system-prompt injection, MCP untrusted-install, API missing-auth/BFLA),
212
+ closing four of six previously-flagged zero-coverage categories; each verified
213
+ `pre:TP post:TN` against the real runner before joining the baseline. SBOM and
214
+ SCA-reachability are documented in `bench/cve-replay/CONTRIBUTING.md` as
215
+ structurally unable to fit this corpus's binary presence/absence schema —
216
+ they're covered by their own test suites instead.
217
+
218
+ CMP-1's family-alias table also closed three more gaps
219
+ (`k8s-pod-security-privileged`, `mcp-audit.js`'s `agent-tool-exec` backfill,
220
+ dependency-confusion family tagging).
221
+
222
+ **No functional change from 0.136.2.** This version exists for one reason, and
223
+ it is worth stating plainly rather than dressing up: 0.136.2 reached npm from a
224
+ maintainer's laptop, not from CI, so it carries **no provenance attestation** —
225
+ nothing ties that tarball to this repository or this commit beyond trust in the
226
+ publisher.
227
+
228
+ npm will not accept a re-publish of an existing version, so obtaining provenance
229
+ requires a new one. 0.136.3 is that, and nothing else.
230
+
231
+ The release path itself was already proven end to end on 0.136.2: the gate passed
232
+ on a clean runner, npm signed a provenance statement and recorded it in the
233
+ Sigstore transparency log, and only the final registry upload was rejected —
234
+ because the token in CI was not authorized to publish. With a valid automation
235
+ token that last step completes, and the attestation that was already being
236
+ generated actually lands.
237
+
238
+ Verify it yourself once published:
239
+
240
+ ```
241
+ npm view @clear-capabilities/agentic-security-scanner@0.136.3 --json | jq .dist.attestations
242
+ ```
243
+
244
+ `null` means it went out unattested again. `dist.signatures` is NOT the same
245
+ thing — the registry signs every package it serves; provenance is the separate
246
+ Sigstore statement binding the artifact to its source.
247
+
248
+ ## 0.136.2 — authenticate the gate on the path that actually runs it
249
+
250
+ 0.136.1 removed the self-deadlock and the release workflow got further: every
251
+ gate check passed, then `npm publish` failed with *"the forge CLI is not
252
+ authenticated"*.
253
+
254
+ The gate runs **twice** in that job. Once as an explicit `Release gate` step,
255
+ which sets `GH_TOKEN` and passed. Then again inside `npm publish`, which
256
+ triggers `prepublishOnly` — and that step set only `NODE_AUTH_TOKEN`. The second
257
+ run had no token, could not read hosted CI, and refused.
258
+
259
+ The gate was right to refuse: unverifiable is not green, and the remedy it
260
+ suggests — `--allow-unverified-ci` — would have published without proving the
261
+ commit was green at all. So the fix is to authenticate it, not to relax it.
262
+ `checks: read` is now declared explicitly too; the gate passed without it on the
263
+ default token, but depending on an undeclared default is how a tightened default
264
+ becomes a mystery failure a year later.
265
+
266
+ The same shape as the deadlock it follows: a control that works on the path
267
+ that was tested, and fails on the path that actually ships.
268
+
269
+ ## 0.136.1 — the release gate stops deadlocking on itself
270
+
271
+ `npm publish` had been impossible since 0.135.0, and the cause was not the one I
272
+ reported. Not a missing npm token: **the release gate was waiting for itself.**
273
+
274
+ The release workflow's job is named `publish`. The gate runs inside that job,
275
+ queries hosted CI for HEAD, sees a check run named `publish` that is
276
+ `in_progress` — itself — and requires it to finish before allowing the release.
277
+ It never can. Every tag push failed this way with all nine real checks green, and
278
+ the resulting `publish: failure` then blocked local publishes too, which is the
279
+ error that finally surfaced it.
280
+
281
+ `.github/required-checks.json` gains a third category, **self**, beside blocking
282
+ and informational. A self check is EXCLUDED, not trusted: it cannot report a
283
+ conclusion until the gate it contains has already passed, so requiring it is a
284
+ deadlock and believing it would be believing a check that has not run. The file
285
+ already insisted every check be classified deliberately — this is the category
286
+ that was missing, and its absence meant `publish` fell through to the safe
287
+ default of blocking, which was exactly wrong here.
288
+
289
+ Proven in three directions, because excluding a check must not weaken a gate:
290
+ a pending self check no longer blocks, a FAILED self check no longer blocks, and
291
+ a genuinely red blocking check still does. Verified against live CI state:
292
+ `PASS Hosted CI is green for HEAD`.
293
+
294
+ v0.135.0 and v0.136.0 were tagged but never reached npm for this reason. The
295
+ tags stay where they are — a public tag is not moved — so this ships as 0.136.1.
296
+
297
+ ## 0.136.0 — NIST Privacy Framework 1.1, and the frameworks that never shipped
298
+
299
+ ### A privacy compliance scan that says what it did not check
300
+
301
+ All 104 PF 1.1 controls, assessed on every scan, artifacts at
302
+ `.agentic-security/privacy-framework.{json,md}`. Each gap is emitted as an
303
+ ordinary finding (`family: privacy-compliance`, `CWE-359`) carrying an actionable
304
+ remediation, so `/fix` handles it like anything else.
305
+
306
+ The design turns on one column in NIST's own workbook. PF 1.1 rates each control
307
+ for code-testability — **23 yes, 33 partial, 48 no** — and that rating says a
308
+ control *could* be assessed from source, not that this engine assesses it.
309
+ Collapsing the two is how a privacy report marks "the organizational mission is
310
+ communicated" as PASSED because no rule fired against it, and someone hands that
311
+ to an auditor. So every control lands in exactly one stated bucket:
312
+
313
+ | Bucket | Meaning |
314
+ |---|---|
315
+ | gap | mapped to an engine signal, and that signal is failing — the only bucket that emits a finding |
316
+ | not assessed | NIST rates it code-testable, this engine has no signal — named, never a pass |
317
+ | manual | NIST rates it not code-testable — governance, outside any scanner's reach |
318
+ | satisfied | mapped, and the signal is clean |
319
+
320
+ Measured on a live fixture: 9 gaps, 20 satisfied, 27 not assessed, 48 manual.
321
+ The satisfied rate is reported over the **29 assessed** controls, never over 104.
322
+
323
+ A **vacuous-satisfaction guard** was added after the module's own test caught it:
324
+ a `family:`-mapped control clears when no findings of that family are open, which
325
+ is equally true of a scan that read zero files. Pointing the tool at an empty
326
+ directory was reporting privacy controls as satisfied on the strength of having
327
+ looked at nothing. Now every mapped control degrades to *not assessed* and the
328
+ summary says so.
329
+
330
+ Findings are opt-in (`AGENTIC_SECURITY_PRIVACY_FRAMEWORK=1`). The assessment
331
+ always runs and persists; appending findings by default would change every
332
+ severity count and gate verdict downstream, and a compliance opinion should not
333
+ silently become someone's build failure.
334
+
335
+ ### A real `compliance` subcommand
336
+
337
+ ```
338
+ agentic-security compliance [--gap] [--list] [--walkthrough <id>]
339
+ [--format cli|json|md] [--fail-on gap]
340
+ ```
341
+
342
+ `/compliance --privacy` was documented as a mode with no binary behind it. It
343
+ reads `last-scan.json` rather than re-scanning — a compliance answer is a
344
+ statement about a scan that happened. With no scan to read it exits **2** rather
345
+ than assessing an empty project. Exit codes: 0 report produced, 1 only with
346
+ `--fail-on gap` and a failing control, 2 nothing to assess.
347
+
348
+ ### Every bundled framework had been invisible from the published artifact
349
+
350
+ Running the new subcommand from the shipped bundle printed nothing and exited 0.
351
+ `auditor-walkthrough` resolves its data directory from `import.meta.url` — inside
352
+ the bundle that is `dist/`, and `dist/compliance-frameworks/` never existed
353
+ because the build only emitted the `.mjs`. `listFrameworks` catches the readdir
354
+ failure and returns `[]`.
355
+
356
+ So GDPR, ASVS, NIST AI 600-1 — all nine — worked perfectly from source and were
357
+ **silently absent from the npm package**. Nothing caught it because every test
358
+ ran against `src/`. The build now copies the data next to the bundle, the data is
359
+ tracked in git for the same reason the bundle is, and a test drives the BUNDLE so
360
+ it cannot regress.
361
+
362
+ Twice in this release, testing the shipped artifact rather than the source found
363
+ something the whole suite was blind to.
364
+
365
+ ### Also
366
+
367
+ Removed all remaining PRD documents, repairing 18 files that referenced them.
368
+ `bench/proof-corpus`'s section-level citations (parse-coverage rule, acceptance
369
+ criterion 2, criterion 4, the disclosure boundary) are now stated inline and
370
+ owned by the files that depend on them — the rationale outlived the document.
371
+
372
+ ## 0.135.0 — a scan stops modifying what it scans, and the benchmark stops flattering
373
+
374
+ Two findings, one of which reverses something this project previously reported.
375
+
376
+ ### `--no-state`: a scan is an observation again
377
+
378
+ Pointing the engine at a directory used to write **10 files** into it. For a user
379
+ that means CI asserting a clean tree fails after a scan, and scanning a
380
+ dependency or a customer's code leaves artifacts in a tree they own. Worse, our
381
+ own output contains CWE identifiers, so a second scan could read the first
382
+ scan's conclusions as source.
383
+
384
+ `AGENTIC_SECURITY_NO_STATE=1` (and `--no-state`) now adds **zero paths** while
385
+ reporting byte-identical findings. Both halves are asserted, and the test was
386
+ proven to FAIL with the switch off — the switch must change what is written,
387
+ never what is found. The engine also skips `.agentic-security/` when walking, so
388
+ our output can never become our input.
389
+
390
+ Three defects were found in the guard meant to enforce this, each worth more
391
+ than the line that fixed it:
392
+
393
+ - **`git status` is not sufficient evidence.** Git does not track empty
394
+ directories, so an earlier revision reported a CLEAN tree while still creating
395
+ `sbom-history/` and `fix-history/`. Directory creation is mutation: it fails on
396
+ a read-only mount and is litter in someone else's repo. The acceptance test
397
+ compares full path listings.
398
+ - **The guard was blind to `bin/`** — where the three largest artifacts
399
+ (`findings.json`, `last-scan.json`, `.sig`) are written. A seam guard that
400
+ cannot see the CLI entry point misses the primary writer.
401
+ - **The detector counted documentation as a violation**, then over-corrected: a
402
+ glob inside `// .agentic-security/rules/*.yml` opened a block comment that
403
+ consumed 12,198 characters and hid a real violation. Comment-strip order is
404
+ now load-bearing and asserted in both directions.
405
+
406
+ Stated plainly: 55 modules still build state paths by hand and remain on a
407
+ migration ledger. What changed permanently is that a 56th cannot be added.
408
+
409
+ ### The independent recall figure was wrong, and the correction is downward
410
+
411
+ Benchmark trees had been contaminated by the engine's own state files (220
412
+ polluted trees, 544 carrying `CWE-` strings). Fixing that coincided with a second
413
+ change — restricting matches to the files the advisory's fix commit touched — and
414
+ recall fell from a previously reported 33.6% to 12.7%.
415
+
416
+ Attributing that fall to the wrong cause would have been the same reasoning error
417
+ as the contamination, pointed the other way, so the runner now scores both ways
418
+ in one pass:
419
+
420
+ | | advisory-local (**the claim**) | wide (diagnostic) |
421
+ |---|---|---|
422
+ | recall | **12.7%** (14/110) | 33.6% (37/110) |
423
+ | precision | **50.0%** (14/28) | 50.0% (37/74) |
424
+ | F1 | **0.203** | 0.402 |
425
+
426
+ The wide figure is identical to the pre-purge number. **The contamination was
427
+ real and had to be fixed, but it was not inflating the measurement** — every one
428
+ of the 20.9 points comes from the benchmark becoming honest about *where* a
429
+ finding has to be. 12.7% is the true recall, and it always was.
430
+
431
+ It is published as a low number rather than quietly requalified, because the
432
+ point of owning the instrument is to be able to trust it when it disagrees.
433
+
434
+
3
435
  ## 0.134.0 — the loop closes, and the logic tier learns how to be wrong
4
436
 
5
437
  The remaining PRD epics. Two of them are new capability; the other two are the
@@ -23,6 +23,7 @@ import * as fs from 'node:fs';
23
23
  import * as path from 'node:path';
24
24
  import { verifyAuditLog } from '../src/mcp/audit.js';
25
25
 
26
+ import { statePath } from '../src/posture/state-dir.js';
26
27
  function args() {
27
28
  const a = process.argv.slice(2);
28
29
  const sub = a[0] || 'review';
@@ -48,7 +49,7 @@ function _parseDuration(s) {
48
49
  return n * u;
49
50
  }
50
51
 
51
- function _logPath(root) { return path.join(root, '.agentic-security', 'mcp-audit.log'); }
52
+ function _logPath(root) { return statePath(root, 'mcp-audit.log'); }
52
53
 
53
54
  function _readEntries(root) {
54
55
  const fp = _logPath(root);
@@ -26,6 +26,7 @@ import * as fs from 'node:fs';
26
26
  import * as path from 'node:path';
27
27
  import { measureConsistency, summarize } from '../src/llm-validator/consistency.js';
28
28
 
29
+ import { statePath } from '../src/posture/state-dir.js';
29
30
  function args() {
30
31
  const a = process.argv.slice(2);
31
32
  const out = { trials: 5, top: 5, json: false, root: process.cwd() };
@@ -40,7 +41,7 @@ function args() {
40
41
 
41
42
  async function main() {
42
43
  const opts = args();
43
- const scanFile = path.join(opts.root, '.agentic-security', 'last-scan.json');
44
+ const scanFile = statePath(opts.root, 'last-scan.json');
44
45
  if (!fs.existsSync(scanFile)) {
45
46
  console.error(`no last-scan.json at ${scanFile} — run a scan first`);
46
47
  process.exit(2);