@clear-capabilities/agentic-security-scanner 0.127.0 → 0.130.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 (88) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/bin/agentic-security.js +33 -0
  3. package/dist/11.index.js +353 -0
  4. package/dist/113.index.js +727 -0
  5. package/dist/178.index.js +1 -1
  6. package/dist/207.index.js +217 -0
  7. package/dist/384.index.js +1 -1
  8. package/dist/415.index.js +1 -1
  9. package/dist/435.index.js +19 -8
  10. package/dist/526.index.js +555 -0
  11. package/dist/637.index.js +1 -1
  12. package/dist/826.index.js +4 -1
  13. package/dist/830.index.js +1 -1
  14. package/dist/agentic-security.mjs +113 -163
  15. package/dist/agentic-security.mjs.sha256 +1 -1
  16. package/package.json +23 -15
  17. package/src/dataflow/CLAUDE.md +4 -1
  18. package/src/dataflow/async-sequencing.js +8 -3
  19. package/src/dataflow/catalog.js +278 -11
  20. package/src/dataflow/cross-repo.js +1 -1
  21. package/src/dataflow/cross-service-taint.js +1 -1
  22. package/src/dataflow/engine.js +182 -61
  23. package/src/dataflow/ifds.js +10 -5
  24. package/src/dataflow/index.js +15 -3
  25. package/src/dataflow/points-to.js +8 -2
  26. package/src/dataflow/proof-gate.js +7 -0
  27. package/src/dataflow/sanitizer-gate.js +89 -0
  28. package/src/dataflow/tabulation.js +14 -3
  29. package/src/engine.js +181 -8
  30. package/src/integrations/index.js +1 -1
  31. package/src/integrations/tickets.js +9 -3
  32. package/src/ir/CLAUDE.md +49 -4
  33. package/src/ir/call-sites.js +66 -0
  34. package/src/ir/callgraph.js +174 -7
  35. package/src/ir/class-hierarchy.js +22 -2
  36. package/src/ir/index.js +138 -51
  37. package/src/ir/ir-stats.js +126 -0
  38. package/src/ir/parser-cpp.js +829 -0
  39. package/src/ir/parser-cs.js +4 -1
  40. package/src/ir/parser-go.js +4 -1
  41. package/src/ir/parser-js.js +5 -1
  42. package/src/ir/parser-kt.js +4 -1
  43. package/src/ir/parser-php.js +10 -3
  44. package/src/ir/parser-py-cst.js +62 -10
  45. package/src/ir/tree-sitter-loader.js +13 -1
  46. package/src/llm-validator/index.js +9 -2
  47. package/src/llm-validator/redact.js +157 -0
  48. package/src/mcp/tools.js +17 -6
  49. package/src/posture/CLAUDE.md +122 -0
  50. package/src/posture/accuracy-scorecard.js +317 -0
  51. package/src/posture/api-contract.js +1 -1
  52. package/src/posture/attestation.js +199 -0
  53. package/src/posture/auditor-walkthrough.js +12 -3
  54. package/src/posture/compliance-policy.js +1 -1
  55. package/src/posture/cross-lang-openapi.js +1 -1
  56. package/src/posture/custom-rules.js +1 -1
  57. package/src/posture/entrypoint-inventory.js +248 -0
  58. package/src/posture/execution-proof.js +52 -0
  59. package/src/posture/exploitability-probability.js +1 -1
  60. package/src/posture/falsification.js +165 -0
  61. package/src/posture/fix-honesty-gate.js +175 -0
  62. package/src/posture/fix-verify.js +71 -3
  63. package/src/posture/license-policy.js +1 -1
  64. package/src/posture/model-routing.js +126 -0
  65. package/src/posture/profile.js +1 -1
  66. package/src/posture/proof-tier.js +33 -0
  67. package/src/posture/relevance.js +379 -0
  68. package/src/posture/root-cause-sweep.js +262 -0
  69. package/src/posture/rule-overrides.js +1 -1
  70. package/src/posture/sca-policy.js +1 -1
  71. package/src/posture/scan-checkpoint.js +277 -0
  72. package/src/posture/suppressions.js +1 -1
  73. package/src/posture/test-runner.js +147 -0
  74. package/src/posture/verification-separation.js +131 -0
  75. package/src/pr-comment.js +3 -1
  76. package/src/report/index.js +11 -0
  77. package/src/runScan.js +3 -1
  78. package/src/sandbox/CLAUDE.md +218 -0
  79. package/src/sandbox/backend-disabled.js +14 -0
  80. package/src/sandbox/backend-namespace.js +83 -0
  81. package/src/sandbox/backend-userspace.js +100 -0
  82. package/src/sandbox/capabilities.js +53 -0
  83. package/src/sandbox/index.js +30 -0
  84. package/src/sandbox/limits.js +42 -0
  85. package/src/sandbox/result.js +104 -0
  86. package/src/sca/dep-confusion.js +1 -1
  87. package/src/util/untrusted.js +148 -0
  88. package/src/util/yaml.js +24 -0
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__(3793);
16
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4660);
17
17
  // Time-travel + counterfactual scanning (v0.68).
18
18
  //
19
19
  // Two new modes that exploit the pure-input shape of runFullScan:
@@ -0,0 +1,217 @@
1
+ export const id = 207;
2
+ export const ids = [207];
3
+ export const modules = {
4
+
5
+ /***/ 9207:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ computeRunAttestation: () => (/* binding */ computeRunAttestation)
10
+ /* harmony export */ });
11
+ /* unused harmony exports ATTESTATION_CANONICALISATION, verifyRunAttestation */
12
+ /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7598);
13
+ /* harmony import */ var _integrity_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1130);
14
+ // R4 — determinism as a contract.
15
+ //
16
+ // Determinism has been an implementation property of this engine (stable sorts,
17
+ // no Date.now() in ids, `--deterministic`) with no way for a third party to
18
+ // VERIFY it. This module turns it into something checkable: a stable digest
19
+ // over the finding set, bound to the engine version, ruleset version, and
20
+ // bundle hash that produced it.
21
+ //
22
+ // WHAT THE DIGEST IS INDEPENDENT OF (by construction):
23
+ // - the ORDER findings were emitted in (entries are canonicalised, then sorted)
24
+ // - run ids, timestamps, durations, and every other field not on the
25
+ // identity allowlist below — canonicalisation is an ALLOWLIST, not a
26
+ // denylist, so a new non-deterministic field cannot silently leak in
27
+ // - path separator style, and the absolute prefix when `root` is supplied
28
+ //
29
+ // WHAT IT IS NOT INDEPENDENT OF (all of these are real differences):
30
+ // - a changed severity, file, line, rule id, cwe, or vuln title
31
+ // - a finding appearing or disappearing (including a duplicate — multiplicity
32
+ // is preserved; two identical findings are not collapsed into one)
33
+ // - the engine version, ruleset version, or bundle sha
34
+ //
35
+ // WHAT THIS PROVES, AND WHAT IT DOES NOT (read before quoting it at anyone):
36
+ // PROVES — two finding sets carrying the same digest under the same
37
+ // canonicalisation are the same set of findings, modulo emission order and
38
+ // the excluded volatile fields; and (when signed) that the attestation was
39
+ // produced on an install holding this HMAC key.
40
+ // DOES NOT PROVE — cross-machine reproducibility. Nothing here demonstrates
41
+ // that a different host, OS, Node version, or toolchain produces the same
42
+ // finding set; several detectors are environment-sensitive (e.g. the Python
43
+ // AST path is used when python3 is available and falls back to regex when it
44
+ // is not, which can change what is found). Two runs on two machines agreeing
45
+ // would be evidence FOR reproducibility, but that experiment has not been
46
+ // run here and this module does not assert its outcome. The signature is a
47
+ // symmetric per-install HMAC, so it is tamper-evidence for the operator, not
48
+ // third-party non-repudiation.
49
+ //
50
+ // Identity fields deliberately EXCLUDE `parser` and `family`: `parser` records
51
+ // which analysis engine fired, which is environment-sensitive (see the python3
52
+ // case above), so including it would make the digest report an environment
53
+ // difference as a findings difference. The finding's identity — where it is
54
+ // and what it is — is fully captured without it.
55
+ //
56
+ // SIGNING: reuses `integrity.js`'s per-install HMAC key handling verbatim
57
+ // (`signLastScan`, keyed from $AGENTIC_SECURITY_HMAC_KEY or the 0600 key file
58
+ // at $XDG_CONFIG_HOME/agentic-security/scan-key). No second key mechanism is
59
+ // introduced. `integrity.verifyLastScan` is NOT reused because it verifies a
60
+ // body against a sibling `.sig` FILE; an attestation carries its signature
61
+ // inline, so verification re-signs and compares in constant time here.
62
+ //
63
+ // NO THROWING (posture/CLAUDE.md convention): malformed input yields an empty
64
+ // canonical set or an `{ok:false, reason}` refusal, never an exception.
65
+
66
+
67
+
68
+
69
+ const ATTESTATION_CANONICALISATION = 'agentic-security/run-attestation-canon-v1';
70
+
71
+ const PROVES =
72
+ 'Two finding sets with this digest, under this canonicalisation, are the same findings ' +
73
+ '(same rule id, severity, file, line, cwe, vuln, and multiplicity) produced by the same ' +
74
+ 'engine version, ruleset version, and bundle — regardless of emission order.';
75
+ const DOES_NOT_PROVE =
76
+ 'It does not prove cross-machine reproducibility: no run on a second machine, OS, or Node ' +
77
+ 'version is compared here, and some detectors are environment-sensitive. A signature, when ' +
78
+ 'present, is a symmetric per-install HMAC — tamper-evidence for this install, not ' +
79
+ 'third-party non-repudiation.';
80
+
81
+ function _str(v) { return v === undefined || v === null ? '' : String(v); }
82
+
83
+ // Normalise a file path: separators to '/', drop a supplied absolute root,
84
+ // drop a leading './'. Everything else is left alone — guessing at a root we
85
+ // were not given would make two genuinely different files collide.
86
+ function _normPath(file, root) {
87
+ let p = _str(file).replace(/\\/g, '/');
88
+ if (root) {
89
+ let r = String(root).replace(/\\/g, '/').replace(/\/+$/, '');
90
+ if (r && p.startsWith(r + '/')) p = p.slice(r.length + 1);
91
+ else if (r && p === r) p = '';
92
+ }
93
+ return p.replace(/^\.\//, '');
94
+ }
95
+
96
+ /**
97
+ * The canonical, order-independent representation of a finding set: one
98
+ * tab-joined record per finding over the identity allowlist, sorted.
99
+ * Multiplicity is preserved (duplicates are NOT deduped).
100
+ */
101
+ function canonicaliseFindings(findings, { root } = {}) {
102
+ const list = Array.isArray(findings) ? findings : [];
103
+ const rows = [];
104
+ for (const f of list) {
105
+ if (!f || typeof f !== 'object') continue;
106
+ rows.push([
107
+ _str(f.id),
108
+ _str(f.severity),
109
+ _normPath(f.file, root),
110
+ _str(f.line),
111
+ _str(f.cwe),
112
+ _str(f.vuln),
113
+ ].join('\t'));
114
+ }
115
+ return rows.sort();
116
+ }
117
+
118
+ /**
119
+ * Compute the run attestation.
120
+ *
121
+ * @param {object[]} findings the finding set (any order)
122
+ * @param {string} engineVersion
123
+ * @param {string} rulesetVersion
124
+ * @param {string} bundleSha
125
+ * @param {string} [root] scan root, used to relativise absolute paths
126
+ * @param {boolean} [sign] attach a per-install HMAC over the digest
127
+ */
128
+ function computeRunAttestation({
129
+ findings, engineVersion, rulesetVersion, bundleSha, root, sign = false,
130
+ } = {}) {
131
+ const entries = canonicaliseFindings(findings, { root });
132
+ const payload = JSON.stringify({
133
+ canonicalisation: ATTESTATION_CANONICALISATION,
134
+ engineVersion: _str(engineVersion),
135
+ rulesetVersion: _str(rulesetVersion),
136
+ bundleSha: _str(bundleSha),
137
+ findingCount: entries.length,
138
+ entries,
139
+ });
140
+ const digest = node_crypto__WEBPACK_IMPORTED_MODULE_0__.createHash('sha256').update(payload).digest('hex');
141
+ const att = {
142
+ digest,
143
+ algorithm: 'sha256',
144
+ findingCount: entries.length,
145
+ engineVersion: _str(engineVersion),
146
+ rulesetVersion: _str(rulesetVersion),
147
+ bundleSha: _str(bundleSha),
148
+ canonicalisation: ATTESTATION_CANONICALISATION,
149
+ proves: PROVES,
150
+ doesNotProve: DOES_NOT_PROVE,
151
+ };
152
+ if (sign) {
153
+ try {
154
+ att.signature = (0,_integrity_js__WEBPACK_IMPORTED_MODULE_1__/* .signLastScan */ .lU)(digest);
155
+ att.signatureScope = 'per-install-hmac';
156
+ } catch { /* signing is best-effort; an unsigned attestation is still valid */ }
157
+ }
158
+ return att;
159
+ }
160
+
161
+ /**
162
+ * Re-derive the attestation from a finding set and compare.
163
+ * @returns {{ok: boolean, reason: string}}
164
+ */
165
+ function verifyRunAttestation(attestation, inputs = {}) {
166
+ if (!attestation || typeof attestation !== 'object') {
167
+ return { ok: false, reason: 'no attestation supplied' };
168
+ }
169
+ if (attestation.canonicalisation !== ATTESTATION_CANONICALISATION) {
170
+ return {
171
+ ok: false,
172
+ reason: `unknown canonicalisation "${_str(attestation.canonicalisation)}" — ` +
173
+ `this build verifies "${ATTESTATION_CANONICALISATION}" only`,
174
+ };
175
+ }
176
+ if (typeof attestation.digest !== 'string' || !/^[0-9a-f]{64}$/.test(attestation.digest)) {
177
+ return { ok: false, reason: 'attestation carries no sha256 digest' };
178
+ }
179
+ for (const k of ['engineVersion', 'rulesetVersion', 'bundleSha']) {
180
+ if (_str(inputs[k]) !== _str(attestation[k])) {
181
+ return { ok: false, reason: `${k} mismatch: attested "${_str(attestation[k])}", got "${_str(inputs[k])}"` };
182
+ }
183
+ }
184
+ const recomputed = computeRunAttestation({
185
+ findings: inputs.findings,
186
+ engineVersion: inputs.engineVersion,
187
+ rulesetVersion: inputs.rulesetVersion,
188
+ bundleSha: inputs.bundleSha,
189
+ root: inputs.root,
190
+ });
191
+ if (recomputed.findingCount !== attestation.findingCount) {
192
+ return {
193
+ ok: false,
194
+ reason: `finding count mismatch: attested ${attestation.findingCount}, got ${recomputed.findingCount}`,
195
+ };
196
+ }
197
+ if (recomputed.digest !== attestation.digest) {
198
+ return { ok: false, reason: 'digest mismatch — the finding set is not the one attested' };
199
+ }
200
+ if (attestation.signature) {
201
+ let expected;
202
+ try { expected = signLastScan(attestation.digest); }
203
+ catch { return { ok: false, reason: 'signature present but no key material available to check it' }; }
204
+ const a = Buffer.from(String(attestation.signature), 'hex');
205
+ const b = Buffer.from(expected, 'hex');
206
+ if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
207
+ return { ok: false, reason: 'signature does not verify under this install key' };
208
+ }
209
+ return { ok: true, reason: 'digest and per-install signature verify' };
210
+ }
211
+ return { ok: true, reason: 'digest verifies (unsigned attestation)' };
212
+ }
213
+
214
+
215
+ /***/ })
216
+
217
+ };
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__(3793);
11
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4660);
12
12
  // Secrets submodule view of the engine — credential + entropy + TODO scanning.
13
13
 
14
14
 
package/dist/415.index.js CHANGED
@@ -2,7 +2,7 @@ export const id = 415;
2
2
  export const ids = [415];
3
3
  export const modules = {
4
4
 
5
- /***/ 5796:
5
+ /***/ 3415:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
package/dist/435.index.js CHANGED
@@ -472,12 +472,12 @@ const cve_lookup_internals = { CACHE_DIR, CVE_RE, _stalenessTier };
472
472
  // the import only runs when a tool that needs them is actually called.
473
473
  let _runScan;
474
474
  async function getRunScan() {
475
- if (!_runScan) _runScan = (await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 9099))).runScan;
475
+ if (!_runScan) _runScan = (await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 454))).runScan;
476
476
  return _runScan;
477
477
  }
478
478
  let _verifyFixCore;
479
479
  async function getVerifyFixCore() {
480
- if (!_verifyFixCore) _verifyFixCore = (await __webpack_require__.e(/* import() */ 838).then(__webpack_require__.bind(__webpack_require__, 7838))).verifyFix;
480
+ if (!_verifyFixCore) _verifyFixCore = (await __webpack_require__.e(/* import() */ 526).then(__webpack_require__.bind(__webpack_require__, 3526))).verifyFix;
481
481
  return _verifyFixCore;
482
482
  }
483
483
 
@@ -993,12 +993,23 @@ const apply_fix = {
993
993
  // Inline re-verify — the load-bearing gate. Must pass to write.
994
994
  let verdict;
995
995
  try {
996
- const verifyFixCore = await getVerifyFixCore();
997
- verdict = await verifyFixCore({
998
- scanRoot: ctx.sessionRoot,
999
- originalFindingStableId: f.stableId,
1000
- files: Object.fromEntries(Object.entries(confinedAbs).map(([rel, v]) => [rel, v.content])),
1001
- });
996
+ const _files = Object.fromEntries(Object.entries(confinedAbs).map(([rel, v]) => [rel, v.content]));
997
+ if (process.env.AGENTIC_SECURITY_FIX_RUN_TESTS === '1') {
998
+ // Addition #7 — connect the closed-loop verifier: add the project test
999
+ // suite as a fourth verification leg (scan + lint + tests). Opt-in
1000
+ // because many repos have no runner and we must not fail-closed by
1001
+ // default. Normalized to the scan+lint verdict shape used below.
1002
+ const { verifyFixWithTests } = await __webpack_require__.e(/* import() */ 113).then(__webpack_require__.bind(__webpack_require__, 4113));
1003
+ const t = await verifyFixWithTests({ scanRoot: ctx.sessionRoot, originalFindingStableId: f.stableId, files: _files });
1004
+ verdict = { ok: t.ok, summary: t.summary, rescan: t.legs?.scan?.detail, lint: t.legs?.lint?.detail, tests: t.legs?.tests, testVerdict: t.verdict };
1005
+ } else {
1006
+ const verifyFixCore = await getVerifyFixCore();
1007
+ verdict = await verifyFixCore({
1008
+ scanRoot: ctx.sessionRoot,
1009
+ originalFindingStableId: f.stableId,
1010
+ files: _files,
1011
+ });
1012
+ }
1002
1013
  } catch (e) {
1003
1014
  return { _meta: META, applied: false, reason: `patch verification failed: ${e.message}` };
1004
1015
  }