@clear-capabilities/agentic-security-scanner 0.140.0 → 0.142.0

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 (65) hide show
  1. package/CHANGELOG.md +283 -0
  2. package/dist/113.index.js +79 -3
  3. package/dist/178.index.js +1 -1
  4. package/dist/238.index.js +77 -1
  5. package/dist/384.index.js +1 -1
  6. package/dist/435.index.js +12 -0
  7. package/dist/526.index.js +79 -3
  8. package/dist/637.index.js +1 -1
  9. package/dist/agentic-security.mjs +14 -14
  10. package/dist/agentic-security.mjs.sha256 +1 -1
  11. package/dist/compliance-frameworks/ccpa.json +34 -7
  12. package/dist/compliance-frameworks/eu-ai-act.json +65 -14
  13. package/dist/compliance-frameworks/gdpr.json +56 -12
  14. package/dist/compliance-frameworks/hipaa-security-rule.json +68 -15
  15. package/dist/compliance-frameworks/nist-ai-600-1.json +57 -12
  16. package/dist/compliance-frameworks/nist-csf-2.json +78 -16
  17. package/dist/compliance-frameworks/nist-privacy-1-1.json +3 -0
  18. package/dist/compliance-frameworks/owasp-asvs-5.json +91 -20
  19. package/dist/compliance-frameworks/owasp-llm-top-10.json +89 -20
  20. package/package.json +19 -5
  21. package/src/dataflow/CLAUDE.md +9 -0
  22. package/src/dataflow/catalog.js +61 -0
  23. package/src/dataflow/engine.js +95 -0
  24. package/src/dataflow/sanitizer-gate.js +61 -0
  25. package/src/engine.js +353 -31
  26. package/src/mcp/tools.js +12 -0
  27. package/src/posture/accuracy-scorecard.js +57 -0
  28. package/src/posture/aibom.js +110 -1
  29. package/src/posture/auditor-walkthrough.js +56 -17
  30. package/src/posture/compliance-frameworks/ccpa.json +34 -7
  31. package/src/posture/compliance-frameworks/eu-ai-act.json +65 -14
  32. package/src/posture/compliance-frameworks/gdpr.json +56 -12
  33. package/src/posture/compliance-frameworks/hipaa-security-rule.json +68 -15
  34. package/src/posture/compliance-frameworks/nist-ai-600-1.json +57 -12
  35. package/src/posture/compliance-frameworks/nist-csf-2.json +78 -16
  36. package/src/posture/compliance-frameworks/nist-privacy-1-1.json +3 -0
  37. package/src/posture/compliance-frameworks/owasp-asvs-5.json +91 -20
  38. package/src/posture/compliance-frameworks/owasp-llm-top-10.json +89 -20
  39. package/src/posture/concurrency-checker.js +3 -3
  40. package/src/posture/coverage-strength.js +182 -0
  41. package/src/posture/epss.js +17 -1
  42. package/src/posture/family-registry.js +103 -0
  43. package/src/posture/family-resolve.js +47 -0
  44. package/src/posture/fix-coverage.js +113 -0
  45. package/src/posture/fix-metrics.js +76 -0
  46. package/src/posture/mcp-rug-pull.js +144 -0
  47. package/src/posture/poc-inprocess.js +217 -1
  48. package/src/posture/proof-coverage.js +162 -0
  49. package/src/posture/reachability-filter.js +44 -0
  50. package/src/posture/sbom.js +12 -3
  51. package/src/runScan.js +56 -5
  52. package/src/sast/CLAUDE.md +2 -2
  53. package/src/sast/claude-md-prompt-injection.js +47 -3
  54. package/src/sast/cloud-iam.js +23 -0
  55. package/src/sast/convention-deviation.js +66 -3
  56. package/src/sast/crypto-protocol.js +23 -0
  57. package/src/sast/dapp-frontend.js +20 -0
  58. package/src/sast/iac-cloud-templates.js +346 -0
  59. package/src/sast/k8s-admission.js +27 -0
  60. package/src/sast/ml-supply-chain.js +22 -0
  61. package/src/sast/ruby.js +132 -0
  62. package/src/sast/web3-advanced.js +26 -0
  63. package/src/sca/CLAUDE.md +21 -4
  64. package/src/sca/container.js +18 -1
  65. package/src/sca/dep-confusion.js +69 -3
package/CHANGELOG.md CHANGED
@@ -9,6 +9,289 @@
9
9
  > make the history less accurate, not more.
10
10
 
11
11
 
12
+
13
+
14
+ ## 0.142.0 — The last five PRD items, and three criteria that now fail on evidence
15
+
16
+ 0.141.0 built instruments for the surfaces that had none. This closes the
17
+ remainder of `docs/WORLD_CLASS_HARNESS_PRD.md`: four of the five items found
18
+ live engine bugs, and the fifth re-derived the headline the whole document rests
19
+ on. Three success criteria now **fail** — measured, rather than unknown.
20
+
21
+ ### A sanitizer could be undone and the flow still read as clean
22
+
23
+ `he.decode(escapeHtml(req.query.name))` reaching an HTML sink was reported
24
+ **sanitized**. The decode puts back exactly what the escape removed, so this was
25
+ a missed XSS presented as a clean flow.
26
+
27
+ Nothing modelled reversal: the catalog holds sanitizers, a decoder is the
28
+ opposite, so it was never even recorded on the path. Fixed across three layers —
29
+ the taint walk now collects un-sanitizer callees, the gate maps them to the
30
+ family they reverse (percent-decoding does **not** undo HTML escaping, so a flat
31
+ list would be wrong), and the finding-projection allowlist had to learn the new
32
+ field. That last one is why the fix looked inert through three rounds of
33
+ debugging.
34
+
35
+ Found by the mutation gate, which went from 12 to **34 cases** — the five
36
+ detector families 0.141.0 shipped had owed a metamorphic pair and an adversarial
37
+ near-miss and had none.
38
+
39
+ ### The CloudFormation ingress rule was keyed on YAML key order
40
+
41
+ `- CidrIp:` first and `- IpProtocol:` first are the same template — YAML mappings
42
+ are unordered — and only the second matched. The rule was keyed on the author's
43
+ formatting, which is syntax, not meaning. Also caught by the mutation gate, by
44
+ the metamorphic case that exists for exactly this.
45
+
46
+ ### Every reachability demotion we could adjudicate was wrong
47
+
48
+ Reachability was reported as a demotion *rate*, and that rate was **0 for every
49
+ entry by construction**: `bench/sca-replay` fetched lockfiles, so the analysis
50
+ had no source to walk. A number that is structurally zero looks like a
51
+ measurement and is not one.
52
+
53
+ With source fetched and scored against an import-level oracle: **3 adjudicable
54
+ demotions, 3 false-unreachable — 100% wrong, in the missed-exploit direction.**
55
+ `express`/`cookie`, `express`/`send`, `poetry`/`requests` — each demoted to
56
+ `info` and out of the report, each verified by hand as genuinely imported.
57
+
58
+ Three defects behind it:
59
+
60
+ - **Failure to prove reachability was reported as proof of unreachability.** A
61
+ site the analysis could not reason about became a positive claim. It is now
62
+ `unknown`, a state the code already used elsewhere.
63
+ - **A project with no routes was still asked "reachable from a route?"** For a
64
+ library that question has no answer — its callers are its users, not in the
65
+ tree.
66
+ - **`_enclosingFn` knew two of four declaration forms.** `res.cookie = function
67
+ (…)` — how most of the JS ecosystem defines a public method — was invisible, so
68
+ the scan attributed call sites to unrelated functions further up the file. It
69
+ now also tracks whether the enclosing function is **exported**, because a
70
+ public-API function with no in-tree caller is the normal case, not dead code.
71
+
72
+ **If you use SCA reachability to triage, this affected you**: findings were being
73
+ demoted out of your report on projects that genuinely import the vulnerable
74
+ package.
75
+
76
+ ### The VS Code extension had never been type-checked
77
+
78
+ `typescript` is now a devDependency and `npm run typecheck` runs in CI. It found
79
+ errors immediately: the tsconfig declared no `types` at all, so `process`,
80
+ `setTimeout` and `NodeJS.Timeout` were every one of them unresolved — `@types/node`
81
+ was installed and nothing consumed it — plus two implicit `any` parameters. The
82
+ build is esbuild, which strips types without checking them, so none of this had
83
+ ever surfaced.
84
+
85
+ Neovim and JetBrains are now smoke-tested in CI too. JetBrains is classified
86
+ **informational**: it downloads a full IntelliJ distribution, so red there is more
87
+ often the network than this code, and a habitually-red gate stops being read.
88
+
89
+ ## The population re-measured — and it is worse
90
+
91
+ 991 scored entries at engine 0.141.0 (the code that ships here), against 315 at
92
+ 0.138.0 before:
93
+
94
+ | | before | **now** |
95
+ |---|---:|---:|
96
+ | localized recall | 3.56% | **2.83%** |
97
+ | localized precision | 44.00% | **36.36%** |
98
+ | fix-discrimination | 81.8% | **71.43%** |
99
+ | held-out recall | — | **2.93%** |
100
+
101
+ **The headline fell because the question got harder.** The corpus tripled once
102
+ the advisory miner could page past the first hundred entries per ecosystem, and
103
+ what it pulled in is recent, TypeScript-heavy and dominated by authorization
104
+ classes. Held-out tracks development almost exactly, so nothing is fitted.
105
+
106
+ **Ruby 0% → 3.20%. Go 0% → 1.19%.** Both measured zeros are off zero, on
107
+ populations 8× and 1.2× their old size. **PHP is the new zero, at 0/73.**
108
+
109
+ Two of this release's own fixes are confirmed on real code rather than their own
110
+ fixtures: the Ruby `File.join` rule earns `lsegal/yard`, and `CONVENTION` earns
111
+ its first localized true positive ever on GitPython — the family recorded as
112
+ permanently silent until it turned out to be mislocalized by five lines.
113
+
114
+ ### Three success criteria now fail, on evidence
115
+
116
+ - **Fix-discrimination is 71.43%**, below its 80% floor. 8 of 28 findings still
117
+ fire on the code the fix produced — those detected an API, not a vulnerability.
118
+ - **Taint contributes 1 of 28** localized true positives. It was 1 of 12; the
119
+ count did not move while the population tripled. Deep mode also costs 5 extra
120
+ false positives and **loses** a Go finding that pattern-only makes.
121
+ - **Compliance still has no accuracy instrument** — the last feature measured by
122
+ nothing.
123
+
124
+ ### Measured and deliberately not fixed
125
+
126
+ - **The agent trust-boundary delta is 0 of 0 — undefined, not zero.** The
127
+ population now holds 28 entries of real MCP-server code; the engine produces no
128
+ localized true positives on any of them, so there is nothing for the boundary
129
+ modelling to have contributed to. It is not silent there — 21 findings per
130
+ entry, all on the advisory's own files, none of the labelled class. WRONG-CWE,
131
+ on the most differentiated surface in the product.
132
+ - **Reachability now demotes nothing** on the source-bearing corpus. The false
133
+ demotions are gone; whether the new caution is correct needs applications with
134
+ real routes, which those four entries are not.
135
+
136
+ ## Also
137
+
138
+ - A whole-population benchmark run **wedged** at 0.0% CPU after 4.5 hours, taking
139
+ every already-scored entry with it. `bench/independent/runner.mjs` now takes
140
+ `--offset`/`--limit` and `merge-chunks.mjs` reassembles the slices,
141
+ **recomputing** every aggregate from the per-entry rows and refusing to write
142
+ unless that reproduces each chunk's own numbers exactly. It caught two of its
143
+ own bugs that way.
144
+ - A guard-shaped method NAME on a declaration line (`def check_static_cache(`) no
145
+ longer counts as a containment guard, which had been silently dropping path
146
+ findings inside any method called `check*` / `validate*` / `ensure*`.
147
+
148
+ ## 0.141.0 — Six new instruments, and the seven live bugs they found
149
+
150
+ The previous release fixed five bugs found by measuring. This one builds the
151
+ instruments that do the measuring for the surfaces that had none — SCA, secrets,
152
+ IaC, prompt injection, remediation, and the IDE/MCP surfaces — and then fixes
153
+ what they reported. Every number below is reproducible with a command in
154
+ `bench/*/README.md`.
155
+
156
+ None of the improvements came from tuning. They came from files that were never
157
+ read, patterns that could never fire, and versions that were silently rewritten.
158
+
159
+ ### SCA never read a transitive dependency tree on any real project
160
+
161
+ `readTree` skipped **any file over 500 KB before deciding what kind of file it
162
+ was.** npm/cli's `package-lock.json` is 666 KB, next.js's `pnpm-lock.yaml` is
163
+ 910 KB, magento2's `composer.lock` is 501 KB. On every project big enough for
164
+ supply-chain risk to matter, the lockfile was dropped and SCA fell back to
165
+ whatever exact versions appeared in `package.json` — direct dependencies only,
166
+ while the headline claim of the feature is transitive reachability.
167
+
168
+ Manifests now have their own, much larger cap. The 500 KB cap on **code** files
169
+ is unchanged: that one protects the analysis path and was never the problem.
170
+
171
+ Two more admission gaps in the same area: `go.sum` was never admitted though
172
+ `_parseGoSum` and its dispatch entry had always existed, and only the exact
173
+ basename `requirements.txt` was matched — so `requirements/dev.txt`, which is
174
+ what pallets/flask ships, scored 0 of 11.
175
+
176
+ **Measured effect (`bench/sca-replay`, 13 real repos, 7 ecosystems, labels from
177
+ the advisory database via readers that share no code with the engine): version
178
+ recall 10.89% → 77.92% at 100% precision, held-out 84.29%.**
179
+
180
+ ### Go dependency versions were rewritten into versions that do not exist
181
+
182
+ `v0.0.0-20210903162142-ad29c8ab022f` became `0.0.0` in three separate places —
183
+ not a shorter version but a different one, collapsing every pseudo-versioned
184
+ module in a tree onto a single key. **Go went from 5.28% to 100%** once versions
185
+ survived intact.
186
+
187
+ This also corrupted the emitted SBOM, which is the worse half: an SBOM saying
188
+ `golang.org/x/net@0.0.0` is wrong in a document other people are supposed to
189
+ rely on.
190
+
191
+ ### The typosquat detector reported 166 findings, none of them typosquats
192
+
193
+ Across 13 real repositories, at critical and high severity: `ms ~ ws`,
194
+ `acorn ~ cors`, `ajv ~ ava`, `six ~ tox`, `arg ~ yargs`, `bail ~ babel`. Every
195
+ one is a legitimate, popular package; `ms` is a top-50 npm package.
196
+
197
+ Absolute edit distance is meaningless on short names — two edits on a
198
+ four-character name changes half of it, and every two-character package is one
199
+ edit from every other. Now Damerau-Levenshtein, so a transposition (`lodahs` for
200
+ `lodash`, the commonest real typo) costs 1 rather than 2, gated on
201
+ `distance / min(len) ≤ 0.25`.
202
+
203
+ ### The VS Code extension had never been able to find the scanner
204
+
205
+ It looked for the bundle under a hardcoded
206
+ `…/agentic-security/0.1.0/scanner/dist/…`, and Claude Code caches a plugin under
207
+ its **plugin** version — 0.128.2, 0.136.9, 0.139.1, never 0.1.0. `0.1.0` was the
208
+ extension's own version pasted into the wrong path, so the fallback could not
209
+ resolve on any install and every user got *"scanner not found."*
210
+
211
+ Nothing tested it, because the function read `vscode.workspace` and could not be
212
+ imported outside a VS Code host. The resolver is now a pure function that
213
+ discovers the version instead of hardcoding one, prefers `CLAUDE_PLUGIN_ROOT`,
214
+ and is covered by 15 tests plus a CI job that fails on a stale committed bundle.
215
+
216
+ ### Three IaC formats had no rules at all
217
+
218
+ CloudFormation, Bicep and Helm chart values scored **0** against every control
219
+ tested — not weak rules, no rules, and for CloudFormation no file admission
220
+ either, since a template is a `.yaml` that no path predicate recognises.
221
+
222
+ `bench/iac-coverage` scores **verdict flip**: a control counts only when the
223
+ misconfigured variant fires *and* the hardened one stays silent. That caught
224
+ something a recall-only bench never would — `FROM ubuntu@sha256:…`, the most
225
+ tightly pinned form a Dockerfile can use, was reported as *"ubuntu:latest
226
+ (floating tag)"* because the digest was matched but not captured. A false
227
+ positive on the hardened configuration tells the people who did the right thing
228
+ that they did the wrong one.
229
+
230
+ **Coverage 8/14 → 23/26**, with the three still open marked as needing semantic
231
+ analysis rather than another pattern.
232
+
233
+ ### Credential patterns that could never fire
234
+
235
+ `CRED_PREFILTER` is a whole-file gate: `scanCredentials` returns early unless
236
+ that one regex matches, so a pattern whose trigger token is missing is dead code
237
+ however correct it is. The generic "Password in URL" rule was in exactly that
238
+ state.
239
+
240
+ Also absent entirely: `postgres://user:pass@host/db` and `mongodb+srv://…` —
241
+ among the commonest real leaks there are — plus GitLab, DigitalOcean, Azure
242
+ Storage, Supabase and HubSpot tokens. Only `jdbc:` was covered.
243
+
244
+ **Format coverage 60% → 92.11%, with correct silence at 28/28** on a hard
245
+ negative set of lockfile integrity fields, git SHAs, content digests, Terraform
246
+ state ids and a security rule file that defines key formats.
247
+
248
+ ### Prompt-injection patterns that were correct and far too literal
249
+
250
+ The override rule required the object noun to be one of seven words, so *"Forget
251
+ all previous **tasks**"* and *"Ignore all preceding **orders**"* missed.
252
+ Exfiltration could not match *"show me all your prompt texts"*.
253
+
254
+ **Recall 6.08% → 18.25% against `deepset/prompt-injections` (Apache-2.0, 263
255
+ injections and 399 legitimate prompts), precision and correct-silence at 100%
256
+ throughout.** Reported per technique, because an aggregate hides which one is
257
+ weak: role-play 100%, exfiltration 60%, override 41.86%.
258
+
259
+ And the engine caught its own author — the widened pattern was flagged by this
260
+ project's own ReDoS detector on the self-scan gate, twice, before it was
261
+ rewritten as a flat alternation that scores identically.
262
+
263
+ ## Measured, published, and deliberately not "fixed"
264
+
265
+ - **Fix synthesis produces a patch for 0 of 6** real true positives on
266
+ third-party code (`bench/fix-correctness`, scored against the upstream fix
267
+ commit). The deterministic synthesizer has two rules, both JS/Python; the real
268
+ population is injection and authorization across seven languages. Widening it
269
+ to guess at an authorization check would produce patches that pass
270
+ verification while changing behaviour.
271
+ - **German prompt injection scores 2.30% against English's 28.57%.** Patching it
272
+ because *this corpus* is German would be fitting to the benchmark.
273
+ - **Datadog, Vercel and Algolia keys stay undetected.** They are a bare run of
274
+ hex; a pattern for "32 hex characters" would fire on every content digest and
275
+ checksum in existence.
276
+ - **`CODEGEN` produces nothing on any of the five advisories in its own header.**
277
+ Measured dead, and left for a deliberate retire-or-fix decision rather than
278
+ removed silently.
279
+
280
+ ## Also
281
+
282
+ - The advisory miner **was not paginating** — the API ignores `&page=N` — so the
283
+ evaluation population was capped at ~100 advisories per ecosystem by
284
+ construction. Fixed via the `Link` cursor: **315 → 1004 entries**, Ruby 32 →
285
+ 250, Kotlin 0 → 2.
286
+ - `File.join(<root>, …, <variable>)` reaching a filesystem call with no traversal
287
+ guard is now detected for Ruby (CWE-22), the dominant real-world shape the
288
+ existing rule could not reach.
289
+ - A guard-shaped method **name** on a declaration line (`def check_static_cache(`)
290
+ no longer counts as a containment guard, which was silently dropping path
291
+ findings inside any method called `check*`/`validate*`/`ensure*`.
292
+ - MCP is now smoke-tested end to end over stdio through the shipped binary, with
293
+ both write tools asserted to refuse out-of-tree paths in both directions.
294
+
12
295
  ## 0.140.0 — Five shipped bugs, found by measuring instead of reading
13
296
 
14
297
  Every fix here is a defect that was live in 0.139.1. None came from the feature
package/dist/113.index.js CHANGED
@@ -10,7 +10,7 @@ export const modules = {
10
10
  /* harmony export */ fixDurationReport: () => (/* binding */ fixDurationReport),
11
11
  /* harmony export */ renderFixDurationSummary: () => (/* binding */ renderFixDurationSummary)
12
12
  /* harmony export */ });
13
- /* unused harmony exports FIX_STAGES, loadFixAttempts, bucketOf, summarizeFixDurations, _internals */
13
+ /* unused harmony exports FIX_STAGES, loadFixAttempts, bucketOf, summarizeFixDurations, _internals, summarizeFixAxes, renderFixAxes */
14
14
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
15
15
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
16
16
  /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
@@ -213,6 +213,82 @@ function renderFixDurationSummary(sum) {
213
213
  const _internals = { _dist, _pct, RELIABLE_N };
214
214
 
215
215
 
216
+ // ── PRD F6.1 — score fixes on THREE AXES, not one ──────────────────────────
217
+ //
218
+ // The three axes the PRD names:
219
+ // (a) does the finding disappear — the rescan leg
220
+ // (b) does the project's own suite pass — the tests leg
221
+ // (c) does an independent verifier agree — the PoC re-check leg
222
+ //
223
+ // All three were already computed by verifyFixCore and then collapsed into one
224
+ // boolean, which is the problem: **(a) alone is satisfiable by deleting code.**
225
+ // A patch that removes the vulnerable function passes the rescan, has nothing
226
+ // left to fail, and — on a project with no detectable test suite — reaches
227
+ // ok:true having proven only that the detector went quiet.
228
+ //
229
+ // Reporting the axes separately makes that visible. `aOnly` is the number that
230
+ // matters most and the one nobody was publishing: attempts that satisfied ONLY
231
+ // the disappearance axis. A high aOnly with a high headline is the shape of a
232
+ // remediation feature that is deleting code and calling it a fix.
233
+ function summarizeFixAxes(attempts) {
234
+ const list = Array.isArray(attempts) ? attempts.filter(Boolean) : [];
235
+ const d = list.length;
236
+
237
+ const rate = (pred) => ({ n: list.filter(pred).length, d });
238
+
239
+ // Each axis is judged INDEPENDENTLY of the overall verdict, so a leg that
240
+ // passed inside a failed attempt still counts for its own axis. Reading them
241
+ // off `ok` would make the three axes three copies of the same number.
242
+ const findingDisappeared = rate((a) => a.rescanOk === true || (a.ok === true && a.rescanOk !== false));
243
+ const testsStillPass = rate((a) => a.testsRan === true && a.testsOk !== false);
244
+ const verifierAgrees = rate((a) => a.pocOk === true);
245
+
246
+ const satisfiesAll = rate((a) =>
247
+ (a.rescanOk === true || (a.ok === true && a.rescanOk !== false))
248
+ && a.testsRan === true && a.testsOk !== false
249
+ && a.pocOk === true);
250
+
251
+ // The honesty number: disappearance WITHOUT either corroborating axis.
252
+ const aOnly = rate((a) => {
253
+ const disappeared = a.rescanOk === true || (a.ok === true && a.rescanOk !== false);
254
+ const corroborated = (a.testsRan === true && a.testsOk !== false) || a.pocOk === true;
255
+ return disappeared && !corroborated;
256
+ });
257
+
258
+ return {
259
+ total: d,
260
+ findingDisappeared,
261
+ testsStillPass,
262
+ verifierAgrees,
263
+ satisfiesAll,
264
+ aOnly,
265
+ meaning:
266
+ 'findingDisappeared = the detector went quiet; testsStillPass = the project suite ran AND passed; '
267
+ + 'verifierAgrees = an independent PoC re-check confirmed the hole is shut. '
268
+ + 'aOnly counts attempts that satisfied ONLY disappearance — the shape a code-deleting "fix" produces.',
269
+ caveat: d === 0
270
+ ? 'no attempts recorded; every rate is 0/0 and means nothing'
271
+ : 'rates carry {n,d}; a small d is indicative, not settled',
272
+ };
273
+ }
274
+
275
+ /** Markdown for a report. Denominators always attached. */
276
+ function renderFixAxes(sum) {
277
+ if (!sum || !sum.total) return '_No fix attempts recorded._\n';
278
+ const row = (label, r, note) => `| ${label} | ${r.n}/${r.d} | ${note} |`;
279
+ return [
280
+ '| Axis | Rate | Meaning |',
281
+ '|---|---|---|',
282
+ row('(a) finding disappeared', sum.findingDisappeared, 'the detector went quiet'),
283
+ row('(b) project tests pass', sum.testsStillPass, 'the suite RAN and passed'),
284
+ row('(c) verifier agrees', sum.verifierAgrees, 'an independent PoC re-check confirmed it'),
285
+ row('all three', sum.satisfiesAll, 'the only row that means "fixed"'),
286
+ row('(a) ALONE', sum.aOnly, 'satisfiable by deleting code — watch this number'),
287
+ '',
288
+ ].join('\n');
289
+ }
290
+
291
+
216
292
  /***/ }),
217
293
 
218
294
  /***/ 4113:
@@ -415,8 +491,8 @@ var external_node_child_process_ = __webpack_require__(1421);
415
491
  var external_node_fs_ = __webpack_require__(3024);
416
492
  // EXTERNAL MODULE: external "node:path"
417
493
  var external_node_path_ = __webpack_require__(6760);
418
- // EXTERNAL MODULE: ./src/engine.js + 203 modules
419
- var engine = __webpack_require__(6622);
494
+ // EXTERNAL MODULE: ./src/engine.js + 200 modules
495
+ var engine = __webpack_require__(3474);
420
496
  ;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
421
497
  // Deterministic honesty gates on fix / finding output (#7).
422
498
  //
package/dist/178.index.js CHANGED
@@ -13,7 +13,7 @@ export const modules = {
13
13
  /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
14
14
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
15
15
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
16
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6622);
16
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3474);
17
17
  // Time-travel + counterfactual scanning (v0.68).
18
18
  //
19
19
  // Two new modes that exploit the pure-input shape of runFullScan:
package/dist/238.index.js CHANGED
@@ -10,7 +10,7 @@ export const modules = {
10
10
  /* harmony export */ fixDurationReport: () => (/* binding */ fixDurationReport),
11
11
  /* harmony export */ renderFixDurationSummary: () => (/* binding */ renderFixDurationSummary)
12
12
  /* harmony export */ });
13
- /* unused harmony exports FIX_STAGES, loadFixAttempts, bucketOf, summarizeFixDurations, _internals */
13
+ /* unused harmony exports FIX_STAGES, loadFixAttempts, bucketOf, summarizeFixDurations, _internals, summarizeFixAxes, renderFixAxes */
14
14
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
15
15
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
16
16
  /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
@@ -213,6 +213,82 @@ function renderFixDurationSummary(sum) {
213
213
  const _internals = { _dist, _pct, RELIABLE_N };
214
214
 
215
215
 
216
+ // ── PRD F6.1 — score fixes on THREE AXES, not one ──────────────────────────
217
+ //
218
+ // The three axes the PRD names:
219
+ // (a) does the finding disappear — the rescan leg
220
+ // (b) does the project's own suite pass — the tests leg
221
+ // (c) does an independent verifier agree — the PoC re-check leg
222
+ //
223
+ // All three were already computed by verifyFixCore and then collapsed into one
224
+ // boolean, which is the problem: **(a) alone is satisfiable by deleting code.**
225
+ // A patch that removes the vulnerable function passes the rescan, has nothing
226
+ // left to fail, and — on a project with no detectable test suite — reaches
227
+ // ok:true having proven only that the detector went quiet.
228
+ //
229
+ // Reporting the axes separately makes that visible. `aOnly` is the number that
230
+ // matters most and the one nobody was publishing: attempts that satisfied ONLY
231
+ // the disappearance axis. A high aOnly with a high headline is the shape of a
232
+ // remediation feature that is deleting code and calling it a fix.
233
+ function summarizeFixAxes(attempts) {
234
+ const list = Array.isArray(attempts) ? attempts.filter(Boolean) : [];
235
+ const d = list.length;
236
+
237
+ const rate = (pred) => ({ n: list.filter(pred).length, d });
238
+
239
+ // Each axis is judged INDEPENDENTLY of the overall verdict, so a leg that
240
+ // passed inside a failed attempt still counts for its own axis. Reading them
241
+ // off `ok` would make the three axes three copies of the same number.
242
+ const findingDisappeared = rate((a) => a.rescanOk === true || (a.ok === true && a.rescanOk !== false));
243
+ const testsStillPass = rate((a) => a.testsRan === true && a.testsOk !== false);
244
+ const verifierAgrees = rate((a) => a.pocOk === true);
245
+
246
+ const satisfiesAll = rate((a) =>
247
+ (a.rescanOk === true || (a.ok === true && a.rescanOk !== false))
248
+ && a.testsRan === true && a.testsOk !== false
249
+ && a.pocOk === true);
250
+
251
+ // The honesty number: disappearance WITHOUT either corroborating axis.
252
+ const aOnly = rate((a) => {
253
+ const disappeared = a.rescanOk === true || (a.ok === true && a.rescanOk !== false);
254
+ const corroborated = (a.testsRan === true && a.testsOk !== false) || a.pocOk === true;
255
+ return disappeared && !corroborated;
256
+ });
257
+
258
+ return {
259
+ total: d,
260
+ findingDisappeared,
261
+ testsStillPass,
262
+ verifierAgrees,
263
+ satisfiesAll,
264
+ aOnly,
265
+ meaning:
266
+ 'findingDisappeared = the detector went quiet; testsStillPass = the project suite ran AND passed; '
267
+ + 'verifierAgrees = an independent PoC re-check confirmed the hole is shut. '
268
+ + 'aOnly counts attempts that satisfied ONLY disappearance — the shape a code-deleting "fix" produces.',
269
+ caveat: d === 0
270
+ ? 'no attempts recorded; every rate is 0/0 and means nothing'
271
+ : 'rates carry {n,d}; a small d is indicative, not settled',
272
+ };
273
+ }
274
+
275
+ /** Markdown for a report. Denominators always attached. */
276
+ function renderFixAxes(sum) {
277
+ if (!sum || !sum.total) return '_No fix attempts recorded._\n';
278
+ const row = (label, r, note) => `| ${label} | ${r.n}/${r.d} | ${note} |`;
279
+ return [
280
+ '| Axis | Rate | Meaning |',
281
+ '|---|---|---|',
282
+ row('(a) finding disappeared', sum.findingDisappeared, 'the detector went quiet'),
283
+ row('(b) project tests pass', sum.testsStillPass, 'the suite RAN and passed'),
284
+ row('(c) verifier agrees', sum.verifierAgrees, 'an independent PoC re-check confirmed it'),
285
+ row('all three', sum.satisfiesAll, 'the only row that means "fixed"'),
286
+ row('(a) ALONE', sum.aOnly, 'satisfiable by deleting code — watch this number'),
287
+ '',
288
+ ].join('\n');
289
+ }
290
+
291
+
216
292
  /***/ })
217
293
 
218
294
  };
package/dist/384.index.js CHANGED
@@ -8,7 +8,7 @@ export const modules = {
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
9
  /* harmony export */ scanCredentials: () => (/* reexport safe */ _engine_js__WEBPACK_IMPORTED_MODULE_0__.Sv)
10
10
  /* harmony export */ });
11
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6622);
11
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3474);
12
12
  // Secrets submodule view of the engine — credential + entropy + TODO scanning.
13
13
 
14
14
 
package/dist/435.index.js CHANGED
@@ -806,6 +806,18 @@ function _maybeOffload(sessionRoot, toolName, items) {
806
806
  }
807
807
 
808
808
  // ─── scan_diff ───────────────────────────────────────────────────────────────
809
+ // Test seam for the write boundary (PRD F6.4).
810
+ //
811
+ // `_confine` and `isReservedWrite` ARE the confinement contract in
812
+ // agents/_CONFINEMENT.md. A boundary is only worth what its refusals are worth,
813
+ // and refusals cannot be adversarially tested through the public tools without
814
+ // also exercising a real scan, a real patch and a real filesystem write — so
815
+ // the check would be measuring four things and attributing failure to one.
816
+ //
817
+ // Exported under the `_internals` convention this codebase already uses
818
+ // (see posture/poc-inprocess.js). Not part of the MCP tool surface.
819
+ const tools_internals = { _confine, isReservedWrite: _isReservedWritePath };
820
+
809
821
  const scan_diff = {
810
822
  name: 'scan_diff',
811
823
  description: 'Scan a list of files for security findings. Use BEFORE writing a Write/Edit to disk so the agent can self-correct. Returns findings with severity, file:line, title, remediation. Snippets are redacted of obvious secret patterns. Paths confined to the session root; symlinks are refused.',
package/dist/526.index.js CHANGED
@@ -10,7 +10,7 @@ export const modules = {
10
10
  /* harmony export */ fixDurationReport: () => (/* binding */ fixDurationReport),
11
11
  /* harmony export */ renderFixDurationSummary: () => (/* binding */ renderFixDurationSummary)
12
12
  /* harmony export */ });
13
- /* unused harmony exports FIX_STAGES, loadFixAttempts, bucketOf, summarizeFixDurations, _internals */
13
+ /* unused harmony exports FIX_STAGES, loadFixAttempts, bucketOf, summarizeFixDurations, _internals, summarizeFixAxes, renderFixAxes */
14
14
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
15
15
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
16
16
  /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
@@ -213,6 +213,82 @@ function renderFixDurationSummary(sum) {
213
213
  const _internals = { _dist, _pct, RELIABLE_N };
214
214
 
215
215
 
216
+ // ── PRD F6.1 — score fixes on THREE AXES, not one ──────────────────────────
217
+ //
218
+ // The three axes the PRD names:
219
+ // (a) does the finding disappear — the rescan leg
220
+ // (b) does the project's own suite pass — the tests leg
221
+ // (c) does an independent verifier agree — the PoC re-check leg
222
+ //
223
+ // All three were already computed by verifyFixCore and then collapsed into one
224
+ // boolean, which is the problem: **(a) alone is satisfiable by deleting code.**
225
+ // A patch that removes the vulnerable function passes the rescan, has nothing
226
+ // left to fail, and — on a project with no detectable test suite — reaches
227
+ // ok:true having proven only that the detector went quiet.
228
+ //
229
+ // Reporting the axes separately makes that visible. `aOnly` is the number that
230
+ // matters most and the one nobody was publishing: attempts that satisfied ONLY
231
+ // the disappearance axis. A high aOnly with a high headline is the shape of a
232
+ // remediation feature that is deleting code and calling it a fix.
233
+ function summarizeFixAxes(attempts) {
234
+ const list = Array.isArray(attempts) ? attempts.filter(Boolean) : [];
235
+ const d = list.length;
236
+
237
+ const rate = (pred) => ({ n: list.filter(pred).length, d });
238
+
239
+ // Each axis is judged INDEPENDENTLY of the overall verdict, so a leg that
240
+ // passed inside a failed attempt still counts for its own axis. Reading them
241
+ // off `ok` would make the three axes three copies of the same number.
242
+ const findingDisappeared = rate((a) => a.rescanOk === true || (a.ok === true && a.rescanOk !== false));
243
+ const testsStillPass = rate((a) => a.testsRan === true && a.testsOk !== false);
244
+ const verifierAgrees = rate((a) => a.pocOk === true);
245
+
246
+ const satisfiesAll = rate((a) =>
247
+ (a.rescanOk === true || (a.ok === true && a.rescanOk !== false))
248
+ && a.testsRan === true && a.testsOk !== false
249
+ && a.pocOk === true);
250
+
251
+ // The honesty number: disappearance WITHOUT either corroborating axis.
252
+ const aOnly = rate((a) => {
253
+ const disappeared = a.rescanOk === true || (a.ok === true && a.rescanOk !== false);
254
+ const corroborated = (a.testsRan === true && a.testsOk !== false) || a.pocOk === true;
255
+ return disappeared && !corroborated;
256
+ });
257
+
258
+ return {
259
+ total: d,
260
+ findingDisappeared,
261
+ testsStillPass,
262
+ verifierAgrees,
263
+ satisfiesAll,
264
+ aOnly,
265
+ meaning:
266
+ 'findingDisappeared = the detector went quiet; testsStillPass = the project suite ran AND passed; '
267
+ + 'verifierAgrees = an independent PoC re-check confirmed the hole is shut. '
268
+ + 'aOnly counts attempts that satisfied ONLY disappearance — the shape a code-deleting "fix" produces.',
269
+ caveat: d === 0
270
+ ? 'no attempts recorded; every rate is 0/0 and means nothing'
271
+ : 'rates carry {n,d}; a small d is indicative, not settled',
272
+ };
273
+ }
274
+
275
+ /** Markdown for a report. Denominators always attached. */
276
+ function renderFixAxes(sum) {
277
+ if (!sum || !sum.total) return '_No fix attempts recorded._\n';
278
+ const row = (label, r, note) => `| ${label} | ${r.n}/${r.d} | ${note} |`;
279
+ return [
280
+ '| Axis | Rate | Meaning |',
281
+ '|---|---|---|',
282
+ row('(a) finding disappeared', sum.findingDisappeared, 'the detector went quiet'),
283
+ row('(b) project tests pass', sum.testsStillPass, 'the suite RAN and passed'),
284
+ row('(c) verifier agrees', sum.verifierAgrees, 'an independent PoC re-check confirmed it'),
285
+ row('all three', sum.satisfiesAll, 'the only row that means "fixed"'),
286
+ row('(a) ALONE', sum.aOnly, 'satisfiable by deleting code — watch this number'),
287
+ '',
288
+ ].join('\n');
289
+ }
290
+
291
+
216
292
  /***/ }),
217
293
 
218
294
  /***/ 3526:
@@ -234,8 +310,8 @@ var external_node_child_process_ = __webpack_require__(1421);
234
310
  var external_node_fs_ = __webpack_require__(3024);
235
311
  // EXTERNAL MODULE: external "node:path"
236
312
  var external_node_path_ = __webpack_require__(6760);
237
- // EXTERNAL MODULE: ./src/engine.js + 203 modules
238
- var engine = __webpack_require__(6622);
313
+ // EXTERNAL MODULE: ./src/engine.js + 200 modules
314
+ var engine = __webpack_require__(3474);
239
315
  ;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
240
316
  // Deterministic honesty gates on fix / finding output (#7).
241
317
  //
package/dist/637.index.js CHANGED
@@ -11,7 +11,7 @@ export const modules = {
11
11
  /* harmony export */ renderPrDeltaText: () => (/* binding */ renderPrDeltaText)
12
12
  /* harmony export */ });
13
13
  /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
14
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6622);
14
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3474);
15
15
  // Shadowscan / security-DELTA on PR (v0.72).
16
16
  //
17
17
  // Most SAST PR-comment integrations show absolute counts — "12 findings