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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/CHANGELOG.md +432 -0
  2. package/bin/agentic-security-audit.js +2 -1
  3. package/bin/agentic-security-consistency.js +2 -1
  4. package/bin/agentic-security.js +448 -74
  5. package/dist/113.index.js +16 -7
  6. package/dist/117.index.js +3 -1
  7. package/dist/178.index.js +1 -1
  8. package/dist/207.index.js +5 -4
  9. package/dist/220.index.js +5 -3
  10. package/dist/238.index.js +4 -4
  11. package/dist/317.index.js +300 -0
  12. package/dist/384.index.js +1 -1
  13. package/dist/435.index.js +196 -21
  14. package/dist/444.index.js +20 -11
  15. package/dist/449.index.js +8 -1
  16. package/dist/513.index.js +7 -3
  17. package/dist/526.index.js +6 -6
  18. package/dist/637.index.js +1 -1
  19. package/dist/675.index.js +7 -5
  20. package/dist/839.index.js +4 -3
  21. package/dist/905.index.js +1173 -0
  22. package/dist/agentic-security.mjs +14 -14
  23. package/dist/agentic-security.mjs.sha256 +1 -1
  24. package/dist/compliance-frameworks/ccpa.json +32 -0
  25. package/dist/compliance-frameworks/eu-ai-act.json +51 -0
  26. package/dist/compliance-frameworks/gdpr.json +45 -0
  27. package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
  28. package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
  29. package/dist/compliance-frameworks/nist-csf-2.json +73 -0
  30. package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
  31. package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
  32. package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
  33. package/package.json +24 -12
  34. package/src/badge.js +2 -1
  35. package/src/dataflow/CLAUDE.md +10 -4
  36. package/src/dataflow/builtin-summaries.js +1 -1
  37. package/src/dataflow/cross-service-taint.js +2 -1
  38. package/src/dataflow/engine.js +324 -60
  39. package/src/dataflow/ifds-precise.js +6 -4
  40. package/src/dataflow/implicit-flow.js +68 -36
  41. package/src/dataflow/incremental.js +25 -8
  42. package/src/dataflow/index.js +2 -1
  43. package/src/dataflow/proven-clean.js +41 -0
  44. package/src/dataflow/sanitizer-gate.js +35 -9
  45. package/src/dataflow/sanitizer-proof.js +21 -3
  46. package/src/dataflow/stub-aware-filter.js +36 -13
  47. package/src/dataflow/summaries.js +21 -2
  48. package/src/discovery/CLAUDE.md +10 -0
  49. package/src/discovery/index.js +175 -3
  50. package/src/discovery/llm-invoke.js +90 -1
  51. package/src/discovery/memory.js +163 -0
  52. package/src/engine.js +247 -50
  53. package/src/integrations/tickets.js +7 -6
  54. package/src/ir/CLAUDE.md +4 -1
  55. package/src/ir/balanced-call.js +55 -0
  56. package/src/ir/ir-stats.js +1 -1
  57. package/src/ir/parser-cpp.js +1 -1
  58. package/src/ir/parser-cs.js +62 -9
  59. package/src/ir/parser-go.js +29 -11
  60. package/src/ir/parser-java.js +96 -19
  61. package/src/ir/parser-js.js +151 -20
  62. package/src/ir/parser-php.js +44 -9
  63. package/src/ir/parser-rb.js +37 -7
  64. package/src/ir/ssa.js +6 -1
  65. package/src/leaderboard.js +3 -2
  66. package/src/llm-validator/consistency.js +6 -2
  67. package/src/llm-validator/index.js +1 -2
  68. package/src/lsp/server.js +28 -2
  69. package/src/mcp/CLAUDE.md +9 -2
  70. package/src/mcp/audit.js +2 -1
  71. package/src/mcp/redact.js +26 -0
  72. package/src/mcp/tools.js +159 -17
  73. package/src/posture/CLAUDE.md +45 -8
  74. package/src/posture/accuracy-scorecard.js +67 -1
  75. package/src/posture/agents-memory.js +5 -3
  76. package/src/posture/aibom.js +12 -8
  77. package/src/posture/auditor-walkthrough.js +111 -10
  78. package/src/posture/auth-posture-import.js +5 -4
  79. package/src/posture/autopilot.js +8 -1
  80. package/src/posture/calibration-drift.js +11 -5
  81. package/src/posture/calibration.js +24 -2
  82. package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
  83. package/src/posture/compliance-frameworks/owasp-asvs-5.json +1 -1
  84. package/src/posture/compliance-policy.js +40 -10
  85. package/src/posture/confidence.js +44 -10
  86. package/src/posture/corpus-enroll.js +9 -5
  87. package/src/posture/corpus-match.js +19 -0
  88. package/src/posture/csharp-analysis.js +62 -3
  89. package/src/posture/custom-rules.js +7 -5
  90. package/src/posture/cve-alert-daemon.js +6 -5
  91. package/src/posture/dep-add-guard.js +2 -1
  92. package/src/posture/deploy-platform.js +4 -1
  93. package/src/posture/deterministic.js +3 -2
  94. package/src/posture/drift.js +7 -1
  95. package/src/posture/epss.js +13 -1
  96. package/src/posture/evidence-bundle.js +276 -0
  97. package/src/posture/exploitability-probability.js +15 -2
  98. package/src/posture/falsification.js +23 -2
  99. package/src/posture/feature-flags.js +3 -2
  100. package/src/posture/findings-memory.js +3 -3
  101. package/src/posture/fix-history.js +5 -2
  102. package/src/posture/fix-metrics.js +5 -5
  103. package/src/posture/fix-plan.js +2 -1
  104. package/src/posture/fix-verify-loop.js +10 -1
  105. package/src/posture/grader-calibration.js +3 -4
  106. package/src/posture/iac-reachability.js +14 -8
  107. package/src/posture/integrity.js +25 -7
  108. package/src/posture/intent-context.js +2 -1
  109. package/src/posture/learning.js +4 -3
  110. package/src/posture/license-attributions.js +5 -7
  111. package/src/posture/license-graph.js +2 -1
  112. package/src/posture/license-policy.js +2 -1
  113. package/src/posture/model-rescan.js +69 -3
  114. package/src/posture/mttr.js +5 -0
  115. package/src/posture/network-policy-import.js +3 -2
  116. package/src/posture/poc-inprocess.js +27 -8
  117. package/src/posture/pqc-migration-plan.js +7 -5
  118. package/src/posture/pr-augment.js +8 -5
  119. package/src/posture/privacy-framework.js +262 -0
  120. package/src/posture/regression-test-gen.js +23 -8
  121. package/src/posture/reverse-blast-radius.js +5 -1
  122. package/src/posture/risk-dollars.js +20 -3
  123. package/src/posture/router.js +5 -4
  124. package/src/posture/ruleset-version.js +2 -2
  125. package/src/posture/runtime-correlation.js +2 -1
  126. package/src/posture/sbom-diff.js +12 -3
  127. package/src/posture/sca-policy.js +7 -4
  128. package/src/posture/scan-checkpoint.js +15 -0
  129. package/src/posture/secret-history.js +20 -11
  130. package/src/posture/security-trend.js +7 -1
  131. package/src/posture/stack-playbook.js +22 -1
  132. package/src/posture/state-dir.js +34 -0
  133. package/src/posture/telemetry-ingest.js +4 -3
  134. package/src/posture/threat-model-auto.js +4 -1
  135. package/src/posture/threat-model-grounding.js +13 -3
  136. package/src/posture/time-to-fix.js +3 -2
  137. package/src/posture/triage-memory.js +3 -2
  138. package/src/posture/validator-metrics.js +10 -3
  139. package/src/posture/verifier.js +32 -57
  140. package/src/posture/waf-ingest.js +6 -5
  141. package/src/posture/watch-mode.js +4 -3
  142. package/src/report/index.js +183 -14
  143. package/src/runScan.js +1 -1
  144. package/src/sast/_comment-strip.js +15 -4
  145. package/src/sast/_secret-entropy.js +1 -1
  146. package/src/sast/authz.js +6 -4
  147. package/src/sast/bench-shape/index.js +2 -7
  148. package/src/sast/claude-md-prompt-injection.js +14 -3
  149. package/src/sast/cloud-iam.js +60 -7
  150. package/src/sast/code-injection-multilang.js +29 -0
  151. package/src/sast/cpp-bench-extras.js +1 -1
  152. package/src/sast/csrf.js +7 -5
  153. package/src/sast/env-hygiene.js +5 -2
  154. package/src/sast/iac-terraform.js +25 -0
  155. package/src/sast/java-bench-extras.js +1 -1
  156. package/src/sast/java-constant-fold.js +5 -5
  157. package/src/sast/llm-owasp.js +4 -2
  158. package/src/sast/mcp-audit.js +7 -0
  159. package/src/sast/pipeline.js +8 -0
  160. package/src/sast/prompt-template.js +8 -6
  161. package/src/sast/prototype-pollution.js +6 -2
  162. package/src/sast/redos-nfa.js +6 -6
  163. package/src/sast/secret-concat.js +13 -2
  164. package/src/sast/ssrf-cloud-metadata.js +6 -3
  165. package/src/sast/xss-reflected-multilang.js +1 -1
  166. package/src/sast/xxe.js +1 -1
  167. package/src/sca/CLAUDE.md +3 -4
  168. package/src/sca/container.js +35 -3
  169. package/src/sca/dep-confusion.js +9 -1
  170. package/src/sca/sarif-ingest.js +0 -187
package/dist/113.index.js CHANGED
@@ -52,7 +52,6 @@ export const modules = {
52
52
 
53
53
 
54
54
 
55
- const STATE_DIR = '.agentic-security';
56
55
  const LOG_FILE = 'fix-metrics.jsonl';
57
56
 
58
57
  // Below this many samples a percentile is an artifact of the sample, not a
@@ -65,7 +64,7 @@ const RELIABLE_N = 10;
65
64
  const FIX_STAGES = Object.freeze(['rescan', 'lint', 'tests', 'honesty', 'poc']);
66
65
 
67
66
  function _logPath(scanRoot) {
68
- return node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_DIR, LOG_FILE);
67
+ return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, LOG_FILE);
69
68
  }
70
69
 
71
70
  /**
@@ -77,9 +76,10 @@ function _logPath(scanRoot) {
77
76
  function recordFixAttempt(scanRoot, record) {
78
77
  if (!scanRoot || !record || typeof record !== 'object') return false;
79
78
  try {
80
- const dir = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_DIR);
79
+ const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
81
80
  if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(dir)) return false;
82
- node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
81
+ if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return false;
82
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
83
83
  // One writeSync of one newline-terminated line: a concurrent reader sees
84
84
  // whole records or nothing, and a torn tail is dropped on read.
85
85
  node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(_logPath(scanRoot), JSON.stringify(record) + '\n', 'utf8');
@@ -266,7 +266,16 @@ function _detectRunner(scanRoot) {
266
266
  } catch { return null; }
267
267
  })();
268
268
  if (pkg && pkg.scripts && pkg.scripts.test && !/no test specified/.test(String(pkg.scripts.test))) {
269
- return { runner: 'npm', cmd: 'npm', args: ['test', '--silent', '--', '--passWithNoTests'] };
269
+ // --passWithNoTests is Jest-specific CLI syntax appending it
270
+ // unconditionally broke every non-Jest npm test script (mocha, vitest,
271
+ // ava, tap, or a plain node script) with an "unrecognized option" exit,
272
+ // failing verification for a reason that has nothing to do with
273
+ // whether the patch actually broke anything. Only add it when Jest is
274
+ // actually the configured runner.
275
+ const usesJest = /\bjest\b/.test(String(pkg.scripts.test))
276
+ || Boolean(pkg.devDependencies?.jest) || Boolean(pkg.dependencies?.jest);
277
+ const args = usesJest ? ['test', '--silent', '--', '--passWithNoTests'] : ['test', '--silent'];
278
+ return { runner: 'npm', cmd: 'npm', args };
270
279
  }
271
280
  if (has('pytest.ini') || has('pyproject.toml') || has('setup.cfg')) {
272
281
  return { runner: 'pytest', cmd: 'pytest', args: ['-q', '--no-header', '-x'] };
@@ -406,8 +415,8 @@ var external_node_child_process_ = __webpack_require__(1421);
406
415
  var external_node_fs_ = __webpack_require__(3024);
407
416
  // EXTERNAL MODULE: external "node:path"
408
417
  var external_node_path_ = __webpack_require__(6760);
409
- // EXTERNAL MODULE: ./src/engine.js + 501 modules
410
- var engine = __webpack_require__(5695);
418
+ // EXTERNAL MODULE: ./src/engine.js + 196 modules
419
+ var engine = __webpack_require__(2739);
411
420
  ;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
412
421
  // Deterministic honesty gates on fix / finding output (#7).
413
422
  //
package/dist/117.index.js CHANGED
@@ -12,6 +12,7 @@ export const modules = {
12
12
  /* unused harmony exports badgeFromScanRoot, renderSvg, _internal */
13
13
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
14
14
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
15
+ /* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
15
16
  // Live SVG badge generator (v0.72).
16
17
  //
17
18
  // Every repo can drop a badge in its README pulling from the latest scan:
@@ -42,6 +43,7 @@ export const modules = {
42
43
 
43
44
 
44
45
 
46
+
45
47
  const COLORS = {
46
48
  critical: '#e05d44', // red
47
49
  high: '#fe7d37', // orange
@@ -56,7 +58,7 @@ const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
56
58
 
57
59
  function _readLastScan(scanRoot) {
58
60
  if (!scanRoot) return null;
59
- const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, '.agentic-security', 'last-scan.json');
61
+ const fp = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'last-scan.json');
60
62
  if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
61
63
  try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); }
62
64
  catch { return null; }
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__(5695);
16
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2739);
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/207.index.js CHANGED
@@ -6,9 +6,10 @@ export const modules = {
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ computeRunAttestation: () => (/* binding */ computeRunAttestation)
9
+ /* harmony export */ computeRunAttestation: () => (/* binding */ computeRunAttestation),
10
+ /* harmony export */ verifyRunAttestation: () => (/* binding */ verifyRunAttestation)
10
11
  /* harmony export */ });
11
- /* unused harmony exports ATTESTATION_CANONICALISATION, verifyRunAttestation */
12
+ /* unused harmony export ATTESTATION_CANONICALISATION */
12
13
  /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7598);
13
14
  /* harmony import */ var _integrity_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1130);
14
15
  // R4 — determinism as a contract.
@@ -202,11 +203,11 @@ function verifyRunAttestation(attestation, inputs = {}) {
202
203
  }
203
204
  if (attestation.signature) {
204
205
  let expected;
205
- try { expected = signLastScan(attestation.digest); }
206
+ try { expected = (0,_integrity_js__WEBPACK_IMPORTED_MODULE_1__/* .signLastScan */ .lU)(attestation.digest); }
206
207
  catch { return { ok: false, reason: 'signature present but no key material available to check it' }; }
207
208
  const a = Buffer.from(String(attestation.signature), 'hex');
208
209
  const b = Buffer.from(expected, 'hex');
209
- if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
210
+ if (a.length !== b.length || !node_crypto__WEBPACK_IMPORTED_MODULE_0__.timingSafeEqual(a, b)) {
210
211
  return { ok: false, reason: 'signature does not verify under this install key' };
211
212
  }
212
213
  return { ok: true, reason: 'digest and per-install signature verify' };
package/dist/220.index.js CHANGED
@@ -15,6 +15,7 @@ export const modules = {
15
15
  /* harmony import */ var node_fs_promises__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1455);
16
16
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
17
17
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
18
+ /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1174);
18
19
  // Watch mode — continuous incremental scan as the developer edits.
19
20
  //
20
21
  // Spawns a long-running scan watcher that:
@@ -40,7 +41,7 @@ export const modules = {
40
41
 
41
42
 
42
43
 
43
- const STATE = '.agentic-security';
44
+
44
45
  const STATUS_MD = 'watch-status.md';
45
46
  const STATUS_JSON = 'watch-status.json';
46
47
  const DEBOUNCE_MS = 350;
@@ -102,7 +103,8 @@ function renderStatusLine(delta) {
102
103
  * Persist watch-status.{md,json}. Cheap atomic write (write tmp, rename).
103
104
  */
104
105
  function persistStatus(scanRoot, delta) {
105
- const dir = node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, STATE);
106
+ const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_3__/* .stateDir */ .Pn)(scanRoot);
107
+ if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_3__.stateWritesEnabled)()) return;
106
108
  try { node_fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync(dir, { recursive: true }); } catch {}
107
109
  const status = {
108
110
  ts: new Date().toISOString(),
@@ -139,7 +141,7 @@ function persistStatus(scanRoot, delta) {
139
141
  * Read the latest watch-status (returns null if none).
140
142
  */
141
143
  function readStatus(scanRoot) {
142
- return _readJsonSafe(path.join(scanRoot, STATE, STATUS_JSON));
144
+ return _readJsonSafe(statePath(scanRoot, STATUS_JSON));
143
145
  }
144
146
 
145
147
  /**
package/dist/238.index.js CHANGED
@@ -52,7 +52,6 @@ export const modules = {
52
52
 
53
53
 
54
54
 
55
- const STATE_DIR = '.agentic-security';
56
55
  const LOG_FILE = 'fix-metrics.jsonl';
57
56
 
58
57
  // Below this many samples a percentile is an artifact of the sample, not a
@@ -65,7 +64,7 @@ const RELIABLE_N = 10;
65
64
  const FIX_STAGES = Object.freeze(['rescan', 'lint', 'tests', 'honesty', 'poc']);
66
65
 
67
66
  function _logPath(scanRoot) {
68
- return node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_DIR, LOG_FILE);
67
+ return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, LOG_FILE);
69
68
  }
70
69
 
71
70
  /**
@@ -77,9 +76,10 @@ function _logPath(scanRoot) {
77
76
  function recordFixAttempt(scanRoot, record) {
78
77
  if (!scanRoot || !record || typeof record !== 'object') return false;
79
78
  try {
80
- const dir = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE_DIR);
79
+ const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
81
80
  if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(dir)) return false;
82
- node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
81
+ if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return false;
82
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
83
83
  // One writeSync of one newline-terminated line: a concurrent reader sees
84
84
  // whole records or nothing, and a torn tail is dropped on read.
85
85
  node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(_logPath(scanRoot), JSON.stringify(record) + '\n', 'utf8');
@@ -0,0 +1,300 @@
1
+ export const id = 317;
2
+ export const ids = [317];
3
+ export const modules = {
4
+
5
+ /***/ 8317:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ buildEvidenceBundle: () => (/* binding */ buildEvidenceBundle),
10
+ /* harmony export */ ensureKeyPair: () => (/* binding */ ensureKeyPair),
11
+ /* harmony export */ keyPaths: () => (/* binding */ keyPaths),
12
+ /* harmony export */ signEvidenceBundle: () => (/* binding */ signEvidenceBundle),
13
+ /* harmony export */ verifyEvidenceBundle: () => (/* binding */ verifyEvidenceBundle)
14
+ /* harmony export */ });
15
+ /* unused harmony exports BUNDLE_SCHEMA, canonicalBytes, canonicalJson */
16
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
17
+ /* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8161);
18
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
19
+ /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7598);
20
+ // Per-finding evidence bundles, signed so a THIRD PARTY can verify them.
21
+ // PRD Phase 2 / D2.
22
+ //
23
+ // WHAT WAS MISSING
24
+ // ----------------
25
+ // `attestation.js` already turns a run into a checkable digest, and it is
26
+ // explicit about its own ceiling: the signature is a per-install SYMMETRIC HMAC,
27
+ // so it is "tamper-evidence for the operator, not third-party non-repudiation."
28
+ // Anyone who can verify it can also forge it. That is fine for detecting local
29
+ // corruption and useless for handing a finding to somebody else.
30
+ //
31
+ // This module closes that gap for the artefact that actually travels: a single
32
+ // finding plus the evidence behind it. A buyer, an auditor, or a downstream
33
+ // consumer can check the bundle with the PUBLIC key alone, having never had
34
+ // access to ours.
35
+ //
36
+ // WHY A SEPARATE MODULE RATHER THAN EXTENDING attestation.js
37
+ // ----------------------------------------------------------
38
+ // They answer different questions and must not be conflated. A run attestation
39
+ // says "this set of findings came from this engine, ruleset and bundle". An
40
+ // evidence bundle says "THIS finding is backed by THIS evidence, and here is
41
+ // proof the claim was not edited after the fact." One is about reproducibility,
42
+ // the other about portability. Merging them would produce an artefact that is
43
+ // worse at both.
44
+ //
45
+ // WHAT A BUNDLE PROVES, AND WHAT IT DOES NOT
46
+ // -------------------------------------------
47
+ // PROVES — the bundle's contents are exactly what the holder of the signing key
48
+ // attested, unmodified. Nothing in it has been added, removed, or edited since.
49
+ //
50
+ // DOES NOT PROVE — that the finding is real. A signature is an integrity claim,
51
+ // never a correctness claim. An `unproven` finding in a signed bundle is still
52
+ // an unproven finding; the signature only stops someone silently upgrading the
53
+ // word `unproven` to `execution-proven` in transit. Both statements are carried
54
+ // INSIDE the bundle so they travel with it and cannot be dropped by a
55
+ // summariser.
56
+ //
57
+ // The evidence chain is deliberately the honest one this engine already
58
+ // computes: the proof tier, what the sandbox observed, which backend ran it, the
59
+ // taint path if there is one. A bundle for a regex finding says so plainly
60
+ // rather than dressing it up.
61
+
62
+
63
+
64
+
65
+
66
+
67
+ const BUNDLE_SCHEMA = 'agentic-security/finding-evidence@1';
68
+
69
+ /** Where the Ed25519 signing key lives. Mirrors integrity.js's key handling. */
70
+ function keyDir() {
71
+ const xdg = process.env.XDG_CONFIG_HOME || node_path__WEBPACK_IMPORTED_MODULE_2__.join(node_os__WEBPACK_IMPORTED_MODULE_1__.homedir(), '.config');
72
+ return node_path__WEBPACK_IMPORTED_MODULE_2__.join(xdg, 'agentic-security');
73
+ }
74
+ const PRIVATE_KEY_FILE = 'attest-key.pem';
75
+ const PUBLIC_KEY_FILE = 'attest-key.pub.pem';
76
+
77
+ function keyPaths(dir = keyDir()) {
78
+ return {
79
+ privateKey: node_path__WEBPACK_IMPORTED_MODULE_2__.join(dir, PRIVATE_KEY_FILE),
80
+ publicKey: node_path__WEBPACK_IMPORTED_MODULE_2__.join(dir, PUBLIC_KEY_FILE),
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Load the signing key pair, generating one on first use.
86
+ *
87
+ * Ed25519 from node:crypto — no new dependency, and deliberately not a
88
+ * transparency-log scheme: those need a network round trip, and this project
89
+ * does not make runtime cloud calls. A log-backed option can layer on later
90
+ * without changing the bundle format.
91
+ */
92
+ function ensureKeyPair(dir = keyDir()) {
93
+ const p = keyPaths(dir);
94
+
95
+ // NO existsSync-then-read. This project's own scanner flagged the first
96
+ // version of this function for TOCTOU (CWE-367) and it was right: between an
97
+ // existence check and the read, an attacker who can write this directory can
98
+ // swap the file, and the thing being swapped is a SIGNING KEY. Winning that
99
+ // race means we sign with a key the attacker controls, which defeats the
100
+ // entire point of the module. Read first and treat absence as the exceptional
101
+ // case — there is then no window between the check and the use, because there
102
+ // is no check.
103
+ try {
104
+ const privateKeyPem = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(p.privateKey, 'utf8');
105
+ const publicKeyPem = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(p.publicKey, 'utf8');
106
+ if (privateKeyPem && publicKeyPem) {
107
+ return { privateKeyPem, publicKeyPem, created: false, ...p };
108
+ }
109
+ } catch { /* missing or unreadable — fall through and generate */ }
110
+
111
+ const { privateKey, publicKey } = node_crypto__WEBPACK_IMPORTED_MODULE_3__.generateKeyPairSync('ed25519');
112
+ const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
113
+ const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
114
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true, mode: 0o700 });
115
+
116
+ // `wx` — exclusive create. If another process generated a key between our
117
+ // failed read and this write, we must NOT clobber it: every bundle already
118
+ // signed with that key would silently stop verifying. On collision, re-read
119
+ // and use the winner's key.
120
+ try {
121
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(p.privateKey, privateKeyPem, { mode: 0o600, flag: 'wx' });
122
+ node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(p.publicKey, publicKeyPem, { mode: 0o644, flag: 'w' });
123
+ return { privateKeyPem, publicKeyPem, created: true, ...p };
124
+ } catch (e) {
125
+ if (e.code !== 'EEXIST') throw e;
126
+ // We lost the race on the private key — someone else's 'wx' won.
127
+ // Their public-key write follows immediately after their private-key
128
+ // write but is not itself atomic with it, so it may not have landed
129
+ // yet: a bare read here would throw an uncaught ENOENT on a genuinely
130
+ // transient state, not a real error. Retry briefly rather than crash.
131
+ const sleepBuf = new Int32Array(new SharedArrayBuffer(4));
132
+ for (let attempt = 0; ; attempt++) {
133
+ try {
134
+ return {
135
+ privateKeyPem: node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(p.privateKey, 'utf8'),
136
+ publicKeyPem: node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(p.publicKey, 'utf8'),
137
+ created: false,
138
+ ...p,
139
+ };
140
+ } catch (readErr) {
141
+ if (readErr.code !== 'ENOENT' || attempt >= 50) throw readErr;
142
+ Atomics.wait(sleepBuf, 0, 0, 10); // 10ms; ~500ms total budget
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Canonical bytes for signing.
150
+ *
151
+ * An ALLOWLIST, exactly as attestation.js canonicalises: a field that is not
152
+ * named here is not signed, so a future addition cannot silently enter the
153
+ * signed surface. Sorted keys, so serialisation order cannot change the
154
+ * signature.
155
+ */
156
+ function canonicalBytes(bundle) {
157
+ const signed = {
158
+ schema: bundle.schema,
159
+ finding: bundle.finding,
160
+ evidence: bundle.evidence,
161
+ engine: bundle.engine,
162
+ proves: bundle.proves,
163
+ doesNotProve: bundle.doesNotProve,
164
+ };
165
+ return Buffer.from(canonicalJson(signed), 'utf8');
166
+ }
167
+
168
+ /**
169
+ * Deterministic JSON: keys sorted at EVERY level, arrays order-preserving.
170
+ *
171
+ * Written by hand rather than using JSON.stringify's replacer argument, and the
172
+ * reason matters. The first version of this function did
173
+ * `JSON.stringify(signed, Object.keys(signed).sort())`, believing the array
174
+ * argument was a top-level field allowlist. It is not — a replacer ARRAY is a
175
+ * key filter applied at every nesting depth, so every nested object serialised
176
+ * as `{}` and the signature covered nothing but the two prose strings. A bundle
177
+ * whose severity was edited from `high` to `critical` verified as authentic.
178
+ *
179
+ * That is the worst possible failure for this module: a security feature that
180
+ * appears to work, produces a valid-looking signature, and protects nothing. It
181
+ * was caught by tampering with a bundle and expecting verification to fail —
182
+ * which is why the tamper cases in the test file are not optional extras.
183
+ */
184
+ function canonicalJson(value) {
185
+ if (value === null || typeof value !== 'object') return JSON.stringify(value ?? null);
186
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
187
+ const keys = Object.keys(value).sort();
188
+ return `{${keys.map(k => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(',')}}`;
189
+ }
190
+
191
+ const PROVES =
192
+ 'The contents of this bundle are exactly what the signer attested, unmodified.';
193
+ const DOES_NOT_PROVE =
194
+ 'That the finding is real. A signature is an integrity claim, never a correctness ' +
195
+ 'claim — read `evidence.proofTier` for how well-supported the finding actually is. ' +
196
+ 'An unproven finding in a signed bundle is still an unproven finding.';
197
+
198
+ /**
199
+ * Build an unsigned bundle from a finding.
200
+ *
201
+ * Everything here is copied from what the engine already computed. Nothing is
202
+ * inferred, and a missing field stays missing rather than being defaulted into
203
+ * something more confident-looking than the truth.
204
+ */
205
+ function buildEvidenceBundle(finding, { engineVersion, rulesetVersion, bundleSha, commit } = {}) {
206
+ if (!finding || typeof finding !== 'object') return null;
207
+ return {
208
+ schema: BUNDLE_SCHEMA,
209
+ finding: {
210
+ id: finding.id ?? null,
211
+ stableId: finding.stableId ?? null,
212
+ severity: finding.severity ?? null,
213
+ file: finding.file ?? null,
214
+ line: finding.line ?? null,
215
+ vuln: finding.vuln ?? null,
216
+ cwe: finding.cwe ?? null,
217
+ family: finding.family ?? null,
218
+ parser: finding.parser ?? null,
219
+ },
220
+ evidence: {
221
+ // The honest chain, in the vocabulary the engine already uses.
222
+ proofTier: finding.proofTier ?? null,
223
+ proofEvidence: finding.proofEvidence ?? null,
224
+ confidence: finding.confidence ?? null,
225
+ exploitability: finding.exploitability ?? null,
226
+ unreachable: finding.unreachable ?? null,
227
+ // Present only for the layers that produce them; absent means absent.
228
+ taintPath: finding.pathSteps ?? finding.exampleFlows ?? null,
229
+ discovery: finding.discovery ?? null,
230
+ },
231
+ engine: {
232
+ engineVersion: engineVersion ?? null,
233
+ rulesetVersion: rulesetVersion ?? null,
234
+ bundleSha: bundleSha ?? null,
235
+ commit: commit ?? null,
236
+ },
237
+ proves: PROVES,
238
+ doesNotProve: DOES_NOT_PROVE,
239
+ };
240
+ }
241
+
242
+ /** Sign a bundle. Returns a new object; the input is not mutated. */
243
+ function signEvidenceBundle(bundle, privateKeyPem) {
244
+ const sig = node_crypto__WEBPACK_IMPORTED_MODULE_3__.sign(null, canonicalBytes(bundle), privateKeyPem);
245
+ return {
246
+ ...bundle,
247
+ signature: {
248
+ algorithm: 'ed25519',
249
+ canonicalisation: BUNDLE_SCHEMA,
250
+ value: sig.toString('base64'),
251
+ },
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Verify a bundle with a PUBLIC key. This is the whole point: the verifier
257
+ * never needs, and never had, the signing key.
258
+ *
259
+ * Returns {ok, reason}. Never throws — a malformed bundle from an untrusted
260
+ * source is an expected input, not an exceptional one.
261
+ */
262
+ // The complete set of top-level keys a legitimately-built, signed bundle can
263
+ // carry — buildEvidenceBundle's six plus signEvidenceBundle's `signature`.
264
+ // EA-03 (Stage-0 audit, 2026): canonicalBytes SIGNS an allowlist of fields;
265
+ // verifyEvidenceBundle never checked for keys OUTSIDE that allowlist, so a
266
+ // bundle with a fabricated `verdict`/`proofLevel`/anything-else stapled on
267
+ // after signing verified as authentic — the signature simply never covered
268
+ // those bytes. Rejecting unknown keys here closes that; it must exactly match
269
+ // what buildEvidenceBundle+signEvidenceBundle actually produce, or a
270
+ // legitimate bundle would start failing verification.
271
+ const BUNDLE_TOP_LEVEL_KEYS = new Set([
272
+ 'schema', 'finding', 'evidence', 'engine', 'proves', 'doesNotProve', 'signature',
273
+ ]);
274
+
275
+ function verifyEvidenceBundle(bundle, publicKeyPem) {
276
+ if (!bundle || typeof bundle !== 'object') return { ok: false, reason: 'bundle is not an object' };
277
+ if (bundle.schema !== BUNDLE_SCHEMA) return { ok: false, reason: `unrecognised schema: ${bundle.schema}` };
278
+ const unknownKeys = Object.keys(bundle).filter(k => !BUNDLE_TOP_LEVEL_KEYS.has(k));
279
+ if (unknownKeys.length) {
280
+ return { ok: false, reason: `unrecognised top-level key(s) not covered by the signature: ${unknownKeys.join(', ')}` };
281
+ }
282
+ const sig = bundle.signature;
283
+ if (!sig?.value) return { ok: false, reason: 'bundle is unsigned' };
284
+ if (sig.algorithm !== 'ed25519') return { ok: false, reason: `unsupported algorithm: ${sig.algorithm}` };
285
+ if (!publicKeyPem) return { ok: false, reason: 'no public key supplied' };
286
+ let ok = false;
287
+ try {
288
+ ok = node_crypto__WEBPACK_IMPORTED_MODULE_3__.verify(null, canonicalBytes(bundle), publicKeyPem, Buffer.from(sig.value, 'base64'));
289
+ } catch (e) {
290
+ return { ok: false, reason: `verification error: ${e.message}` };
291
+ }
292
+ return ok
293
+ ? { ok: true, reason: null }
294
+ : { ok: false, reason: 'signature does not match the bundle contents — it was modified after signing' };
295
+ }
296
+
297
+
298
+ /***/ })
299
+
300
+ };
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__(5695);
11
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2739);
12
12
  // Secrets submodule view of the engine — credential + entropy + TODO scanning.
13
13
 
14
14