@clear-capabilities/agentic-security-scanner 0.140.0 → 0.141.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 (62) hide show
  1. package/CHANGELOG.md +148 -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 +16 -5
  21. package/src/dataflow/catalog.js +61 -0
  22. package/src/engine.js +262 -22
  23. package/src/mcp/tools.js +12 -0
  24. package/src/posture/accuracy-scorecard.js +57 -0
  25. package/src/posture/aibom.js +110 -1
  26. package/src/posture/auditor-walkthrough.js +56 -17
  27. package/src/posture/compliance-frameworks/ccpa.json +34 -7
  28. package/src/posture/compliance-frameworks/eu-ai-act.json +65 -14
  29. package/src/posture/compliance-frameworks/gdpr.json +56 -12
  30. package/src/posture/compliance-frameworks/hipaa-security-rule.json +68 -15
  31. package/src/posture/compliance-frameworks/nist-ai-600-1.json +57 -12
  32. package/src/posture/compliance-frameworks/nist-csf-2.json +78 -16
  33. package/src/posture/compliance-frameworks/nist-privacy-1-1.json +3 -0
  34. package/src/posture/compliance-frameworks/owasp-asvs-5.json +91 -20
  35. package/src/posture/compliance-frameworks/owasp-llm-top-10.json +89 -20
  36. package/src/posture/concurrency-checker.js +3 -3
  37. package/src/posture/coverage-strength.js +182 -0
  38. package/src/posture/epss.js +17 -1
  39. package/src/posture/family-registry.js +103 -0
  40. package/src/posture/family-resolve.js +47 -0
  41. package/src/posture/fix-coverage.js +113 -0
  42. package/src/posture/fix-metrics.js +76 -0
  43. package/src/posture/mcp-rug-pull.js +144 -0
  44. package/src/posture/poc-inprocess.js +217 -1
  45. package/src/posture/proof-coverage.js +162 -0
  46. package/src/posture/reachability-filter.js +44 -0
  47. package/src/posture/sbom.js +12 -3
  48. package/src/runScan.js +56 -5
  49. package/src/sast/CLAUDE.md +2 -2
  50. package/src/sast/claude-md-prompt-injection.js +47 -3
  51. package/src/sast/cloud-iam.js +23 -0
  52. package/src/sast/convention-deviation.js +66 -3
  53. package/src/sast/crypto-protocol.js +23 -0
  54. package/src/sast/dapp-frontend.js +20 -0
  55. package/src/sast/iac-cloud-templates.js +337 -0
  56. package/src/sast/k8s-admission.js +27 -0
  57. package/src/sast/ml-supply-chain.js +22 -0
  58. package/src/sast/ruby.js +132 -0
  59. package/src/sast/web3-advanced.js +26 -0
  60. package/src/sca/CLAUDE.md +21 -4
  61. package/src/sca/container.js +18 -1
  62. package/src/sca/dep-confusion.js +69 -3
package/CHANGELOG.md CHANGED
@@ -9,6 +9,154 @@
9
9
  > make the history less accurate, not more.
10
10
 
11
11
 
12
+
13
+ ## 0.141.0 — Six new instruments, and the seven live bugs they found
14
+
15
+ The previous release fixed five bugs found by measuring. This one builds the
16
+ instruments that do the measuring for the surfaces that had none — SCA, secrets,
17
+ IaC, prompt injection, remediation, and the IDE/MCP surfaces — and then fixes
18
+ what they reported. Every number below is reproducible with a command in
19
+ `bench/*/README.md`.
20
+
21
+ None of the improvements came from tuning. They came from files that were never
22
+ read, patterns that could never fire, and versions that were silently rewritten.
23
+
24
+ ### SCA never read a transitive dependency tree on any real project
25
+
26
+ `readTree` skipped **any file over 500 KB before deciding what kind of file it
27
+ was.** npm/cli's `package-lock.json` is 666 KB, next.js's `pnpm-lock.yaml` is
28
+ 910 KB, magento2's `composer.lock` is 501 KB. On every project big enough for
29
+ supply-chain risk to matter, the lockfile was dropped and SCA fell back to
30
+ whatever exact versions appeared in `package.json` — direct dependencies only,
31
+ while the headline claim of the feature is transitive reachability.
32
+
33
+ Manifests now have their own, much larger cap. The 500 KB cap on **code** files
34
+ is unchanged: that one protects the analysis path and was never the problem.
35
+
36
+ Two more admission gaps in the same area: `go.sum` was never admitted though
37
+ `_parseGoSum` and its dispatch entry had always existed, and only the exact
38
+ basename `requirements.txt` was matched — so `requirements/dev.txt`, which is
39
+ what pallets/flask ships, scored 0 of 11.
40
+
41
+ **Measured effect (`bench/sca-replay`, 13 real repos, 7 ecosystems, labels from
42
+ the advisory database via readers that share no code with the engine): version
43
+ recall 10.89% → 77.92% at 100% precision, held-out 84.29%.**
44
+
45
+ ### Go dependency versions were rewritten into versions that do not exist
46
+
47
+ `v0.0.0-20210903162142-ad29c8ab022f` became `0.0.0` in three separate places —
48
+ not a shorter version but a different one, collapsing every pseudo-versioned
49
+ module in a tree onto a single key. **Go went from 5.28% to 100%** once versions
50
+ survived intact.
51
+
52
+ This also corrupted the emitted SBOM, which is the worse half: an SBOM saying
53
+ `golang.org/x/net@0.0.0` is wrong in a document other people are supposed to
54
+ rely on.
55
+
56
+ ### The typosquat detector reported 166 findings, none of them typosquats
57
+
58
+ Across 13 real repositories, at critical and high severity: `ms ~ ws`,
59
+ `acorn ~ cors`, `ajv ~ ava`, `six ~ tox`, `arg ~ yargs`, `bail ~ babel`. Every
60
+ one is a legitimate, popular package; `ms` is a top-50 npm package.
61
+
62
+ Absolute edit distance is meaningless on short names — two edits on a
63
+ four-character name changes half of it, and every two-character package is one
64
+ edit from every other. Now Damerau-Levenshtein, so a transposition (`lodahs` for
65
+ `lodash`, the commonest real typo) costs 1 rather than 2, gated on
66
+ `distance / min(len) ≤ 0.25`.
67
+
68
+ ### The VS Code extension had never been able to find the scanner
69
+
70
+ It looked for the bundle under a hardcoded
71
+ `…/agentic-security/0.1.0/scanner/dist/…`, and Claude Code caches a plugin under
72
+ its **plugin** version — 0.128.2, 0.136.9, 0.139.1, never 0.1.0. `0.1.0` was the
73
+ extension's own version pasted into the wrong path, so the fallback could not
74
+ resolve on any install and every user got *"scanner not found."*
75
+
76
+ Nothing tested it, because the function read `vscode.workspace` and could not be
77
+ imported outside a VS Code host. The resolver is now a pure function that
78
+ discovers the version instead of hardcoding one, prefers `CLAUDE_PLUGIN_ROOT`,
79
+ and is covered by 15 tests plus a CI job that fails on a stale committed bundle.
80
+
81
+ ### Three IaC formats had no rules at all
82
+
83
+ CloudFormation, Bicep and Helm chart values scored **0** against every control
84
+ tested — not weak rules, no rules, and for CloudFormation no file admission
85
+ either, since a template is a `.yaml` that no path predicate recognises.
86
+
87
+ `bench/iac-coverage` scores **verdict flip**: a control counts only when the
88
+ misconfigured variant fires *and* the hardened one stays silent. That caught
89
+ something a recall-only bench never would — `FROM ubuntu@sha256:…`, the most
90
+ tightly pinned form a Dockerfile can use, was reported as *"ubuntu:latest
91
+ (floating tag)"* because the digest was matched but not captured. A false
92
+ positive on the hardened configuration tells the people who did the right thing
93
+ that they did the wrong one.
94
+
95
+ **Coverage 8/14 → 23/26**, with the three still open marked as needing semantic
96
+ analysis rather than another pattern.
97
+
98
+ ### Credential patterns that could never fire
99
+
100
+ `CRED_PREFILTER` is a whole-file gate: `scanCredentials` returns early unless
101
+ that one regex matches, so a pattern whose trigger token is missing is dead code
102
+ however correct it is. The generic "Password in URL" rule was in exactly that
103
+ state.
104
+
105
+ Also absent entirely: `postgres://user:pass@host/db` and `mongodb+srv://…` —
106
+ among the commonest real leaks there are — plus GitLab, DigitalOcean, Azure
107
+ Storage, Supabase and HubSpot tokens. Only `jdbc:` was covered.
108
+
109
+ **Format coverage 60% → 92.11%, with correct silence at 28/28** on a hard
110
+ negative set of lockfile integrity fields, git SHAs, content digests, Terraform
111
+ state ids and a security rule file that defines key formats.
112
+
113
+ ### Prompt-injection patterns that were correct and far too literal
114
+
115
+ The override rule required the object noun to be one of seven words, so *"Forget
116
+ all previous **tasks**"* and *"Ignore all preceding **orders**"* missed.
117
+ Exfiltration could not match *"show me all your prompt texts"*.
118
+
119
+ **Recall 6.08% → 18.25% against `deepset/prompt-injections` (Apache-2.0, 263
120
+ injections and 399 legitimate prompts), precision and correct-silence at 100%
121
+ throughout.** Reported per technique, because an aggregate hides which one is
122
+ weak: role-play 100%, exfiltration 60%, override 41.86%.
123
+
124
+ And the engine caught its own author — the widened pattern was flagged by this
125
+ project's own ReDoS detector on the self-scan gate, twice, before it was
126
+ rewritten as a flat alternation that scores identically.
127
+
128
+ ## Measured, published, and deliberately not "fixed"
129
+
130
+ - **Fix synthesis produces a patch for 0 of 6** real true positives on
131
+ third-party code (`bench/fix-correctness`, scored against the upstream fix
132
+ commit). The deterministic synthesizer has two rules, both JS/Python; the real
133
+ population is injection and authorization across seven languages. Widening it
134
+ to guess at an authorization check would produce patches that pass
135
+ verification while changing behaviour.
136
+ - **German prompt injection scores 2.30% against English's 28.57%.** Patching it
137
+ because *this corpus* is German would be fitting to the benchmark.
138
+ - **Datadog, Vercel and Algolia keys stay undetected.** They are a bare run of
139
+ hex; a pattern for "32 hex characters" would fire on every content digest and
140
+ checksum in existence.
141
+ - **`CODEGEN` produces nothing on any of the five advisories in its own header.**
142
+ Measured dead, and left for a deliberate retire-or-fix decision rather than
143
+ removed silently.
144
+
145
+ ## Also
146
+
147
+ - The advisory miner **was not paginating** — the API ignores `&page=N` — so the
148
+ evaluation population was capped at ~100 advisories per ecosystem by
149
+ construction. Fixed via the `Link` cursor: **315 → 1004 entries**, Ruby 32 →
150
+ 250, Kotlin 0 → 2.
151
+ - `File.join(<root>, …, <variable>)` reaching a filesystem call with no traversal
152
+ guard is now detected for Ruby (CWE-22), the dominant real-world shape the
153
+ existing rule could not reach.
154
+ - A guard-shaped method **name** on a declaration line (`def check_static_cache(`)
155
+ no longer counts as a containment guard, which was silently dropping path
156
+ findings inside any method called `check*`/`validate*`/`ensure*`.
157
+ - MCP is now smoke-tested end to end over stdio through the shipped binary, with
158
+ both write tools asserted to refuse out-of-tree paths in both directions.
159
+
12
160
  ## 0.140.0 — Five shipped bugs, found by measuring instead of reading
13
161
 
14
162
  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