@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.
- package/CHANGELOG.md +432 -0
- package/bin/agentic-security-audit.js +2 -1
- package/bin/agentic-security-consistency.js +2 -1
- package/bin/agentic-security.js +448 -74
- package/dist/113.index.js +16 -7
- package/dist/117.index.js +3 -1
- package/dist/178.index.js +1 -1
- package/dist/207.index.js +5 -4
- package/dist/220.index.js +5 -3
- package/dist/238.index.js +4 -4
- package/dist/317.index.js +300 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +196 -21
- package/dist/444.index.js +20 -11
- package/dist/449.index.js +8 -1
- package/dist/513.index.js +7 -3
- package/dist/526.index.js +6 -6
- package/dist/637.index.js +1 -1
- package/dist/675.index.js +7 -5
- package/dist/839.index.js +4 -3
- package/dist/905.index.js +1173 -0
- package/dist/agentic-security.mjs +14 -14
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/ccpa.json +32 -0
- package/dist/compliance-frameworks/eu-ai-act.json +51 -0
- package/dist/compliance-frameworks/gdpr.json +45 -0
- package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
- package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
- package/dist/compliance-frameworks/nist-csf-2.json +73 -0
- package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
- package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
- package/package.json +24 -12
- package/src/badge.js +2 -1
- package/src/dataflow/CLAUDE.md +10 -4
- package/src/dataflow/builtin-summaries.js +1 -1
- package/src/dataflow/cross-service-taint.js +2 -1
- package/src/dataflow/engine.js +324 -60
- package/src/dataflow/ifds-precise.js +6 -4
- package/src/dataflow/implicit-flow.js +68 -36
- package/src/dataflow/incremental.js +25 -8
- package/src/dataflow/index.js +2 -1
- package/src/dataflow/proven-clean.js +41 -0
- package/src/dataflow/sanitizer-gate.js +35 -9
- package/src/dataflow/sanitizer-proof.js +21 -3
- package/src/dataflow/stub-aware-filter.js +36 -13
- package/src/dataflow/summaries.js +21 -2
- package/src/discovery/CLAUDE.md +10 -0
- package/src/discovery/index.js +175 -3
- package/src/discovery/llm-invoke.js +90 -1
- package/src/discovery/memory.js +163 -0
- package/src/engine.js +247 -50
- package/src/integrations/tickets.js +7 -6
- package/src/ir/CLAUDE.md +4 -1
- package/src/ir/balanced-call.js +55 -0
- package/src/ir/ir-stats.js +1 -1
- package/src/ir/parser-cpp.js +1 -1
- package/src/ir/parser-cs.js +62 -9
- package/src/ir/parser-go.js +29 -11
- package/src/ir/parser-java.js +96 -19
- package/src/ir/parser-js.js +151 -20
- package/src/ir/parser-php.js +44 -9
- package/src/ir/parser-rb.js +37 -7
- package/src/ir/ssa.js +6 -1
- package/src/leaderboard.js +3 -2
- package/src/llm-validator/consistency.js +6 -2
- package/src/llm-validator/index.js +1 -2
- package/src/lsp/server.js +28 -2
- package/src/mcp/CLAUDE.md +9 -2
- package/src/mcp/audit.js +2 -1
- package/src/mcp/redact.js +26 -0
- package/src/mcp/tools.js +159 -17
- package/src/posture/CLAUDE.md +45 -8
- package/src/posture/accuracy-scorecard.js +67 -1
- package/src/posture/agents-memory.js +5 -3
- package/src/posture/aibom.js +12 -8
- package/src/posture/auditor-walkthrough.js +111 -10
- package/src/posture/auth-posture-import.js +5 -4
- package/src/posture/autopilot.js +8 -1
- package/src/posture/calibration-drift.js +11 -5
- package/src/posture/calibration.js +24 -2
- package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/src/posture/compliance-frameworks/owasp-asvs-5.json +1 -1
- package/src/posture/compliance-policy.js +40 -10
- package/src/posture/confidence.js +44 -10
- package/src/posture/corpus-enroll.js +9 -5
- package/src/posture/corpus-match.js +19 -0
- package/src/posture/csharp-analysis.js +62 -3
- package/src/posture/custom-rules.js +7 -5
- package/src/posture/cve-alert-daemon.js +6 -5
- package/src/posture/dep-add-guard.js +2 -1
- package/src/posture/deploy-platform.js +4 -1
- package/src/posture/deterministic.js +3 -2
- package/src/posture/drift.js +7 -1
- package/src/posture/epss.js +13 -1
- package/src/posture/evidence-bundle.js +276 -0
- package/src/posture/exploitability-probability.js +15 -2
- package/src/posture/falsification.js +23 -2
- package/src/posture/feature-flags.js +3 -2
- package/src/posture/findings-memory.js +3 -3
- package/src/posture/fix-history.js +5 -2
- package/src/posture/fix-metrics.js +5 -5
- package/src/posture/fix-plan.js +2 -1
- package/src/posture/fix-verify-loop.js +10 -1
- package/src/posture/grader-calibration.js +3 -4
- package/src/posture/iac-reachability.js +14 -8
- package/src/posture/integrity.js +25 -7
- package/src/posture/intent-context.js +2 -1
- package/src/posture/learning.js +4 -3
- package/src/posture/license-attributions.js +5 -7
- package/src/posture/license-graph.js +2 -1
- package/src/posture/license-policy.js +2 -1
- package/src/posture/model-rescan.js +69 -3
- package/src/posture/mttr.js +5 -0
- package/src/posture/network-policy-import.js +3 -2
- package/src/posture/poc-inprocess.js +27 -8
- package/src/posture/pqc-migration-plan.js +7 -5
- package/src/posture/pr-augment.js +8 -5
- package/src/posture/privacy-framework.js +262 -0
- package/src/posture/regression-test-gen.js +23 -8
- package/src/posture/reverse-blast-radius.js +5 -1
- package/src/posture/risk-dollars.js +20 -3
- package/src/posture/router.js +5 -4
- package/src/posture/ruleset-version.js +2 -2
- package/src/posture/runtime-correlation.js +2 -1
- package/src/posture/sbom-diff.js +12 -3
- package/src/posture/sca-policy.js +7 -4
- package/src/posture/scan-checkpoint.js +15 -0
- package/src/posture/secret-history.js +20 -11
- package/src/posture/security-trend.js +7 -1
- package/src/posture/stack-playbook.js +22 -1
- package/src/posture/state-dir.js +34 -0
- package/src/posture/telemetry-ingest.js +4 -3
- package/src/posture/threat-model-auto.js +4 -1
- package/src/posture/threat-model-grounding.js +13 -3
- package/src/posture/time-to-fix.js +3 -2
- package/src/posture/triage-memory.js +3 -2
- package/src/posture/validator-metrics.js +10 -3
- package/src/posture/verifier.js +32 -57
- package/src/posture/waf-ingest.js +6 -5
- package/src/posture/watch-mode.js +4 -3
- package/src/report/index.js +183 -14
- package/src/runScan.js +1 -1
- package/src/sast/_comment-strip.js +15 -4
- package/src/sast/_secret-entropy.js +1 -1
- package/src/sast/authz.js +6 -4
- package/src/sast/bench-shape/index.js +2 -7
- package/src/sast/claude-md-prompt-injection.js +14 -3
- package/src/sast/cloud-iam.js +60 -7
- package/src/sast/code-injection-multilang.js +29 -0
- package/src/sast/cpp-bench-extras.js +1 -1
- package/src/sast/csrf.js +7 -5
- package/src/sast/env-hygiene.js +5 -2
- package/src/sast/iac-terraform.js +25 -0
- package/src/sast/java-bench-extras.js +1 -1
- package/src/sast/java-constant-fold.js +5 -5
- package/src/sast/llm-owasp.js +4 -2
- package/src/sast/mcp-audit.js +7 -0
- package/src/sast/pipeline.js +8 -0
- package/src/sast/prompt-template.js +8 -6
- package/src/sast/prototype-pollution.js +6 -2
- package/src/sast/redos-nfa.js +6 -6
- package/src/sast/secret-concat.js +13 -2
- package/src/sast/ssrf-cloud-metadata.js +6 -3
- package/src/sast/xss-reflected-multilang.js +1 -1
- package/src/sast/xxe.js +1 -1
- package/src/sca/CLAUDE.md +3 -4
- package/src/sca/container.js +35 -3
- package/src/sca/dep-confusion.js +9 -1
- 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
|
|
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 =
|
|
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);
|