@clear-capabilities/agentic-security-scanner 0.161.0 → 0.162.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 (57) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/bin/agentic-security.js +11 -2
  3. package/dist/4113.index.js +59 -8
  4. package/dist/5613.index.js +500 -0
  5. package/dist/6239.index.js +469 -0
  6. package/dist/7838.index.js +38 -2
  7. package/dist/8798.index.js +1 -1
  8. package/dist/9207.index.js +5 -0
  9. package/dist/9560.index.js +36 -22
  10. package/dist/{1905.index.js → 9999.index.js} +265 -8
  11. package/dist/agentic-security.mjs +2 -2
  12. package/dist/agentic-security.mjs.sha256 +1 -1
  13. package/package.json +9 -4
  14. package/src/discovery/CLAUDE.md +9 -0
  15. package/src/discovery/index.js +28 -0
  16. package/src/discovery/memory.js +7 -5
  17. package/src/fix/apply-fix-service.js +33 -3
  18. package/src/mcp/CLAUDE.md +9 -1
  19. package/src/mcp/tools.js +17 -4
  20. package/src/posture/CLAUDE.md +174 -0
  21. package/src/posture/assurance/baseline-inventory.js +218 -0
  22. package/src/posture/assurance/baseline.js +322 -0
  23. package/src/posture/assurance/bounded-io.js +145 -0
  24. package/src/posture/assurance/config.js +299 -0
  25. package/src/posture/assurance/contracts.js +315 -0
  26. package/src/posture/assurance/identity.js +62 -0
  27. package/src/posture/assurance/migrations.js +259 -0
  28. package/src/posture/assurance/schema-kit.js +111 -0
  29. package/src/posture/assurance/verification-record.js +258 -0
  30. package/src/posture/attestation.js +5 -0
  31. package/src/posture/autopilot.js +28 -1
  32. package/src/posture/evidence-bundle.js +93 -7
  33. package/src/posture/execution-proof.js +11 -1
  34. package/src/posture/fix-verify-loop.js +20 -6
  35. package/src/posture/fix-verify.js +37 -2
  36. package/src/posture/oracles/adapters.js +360 -0
  37. package/src/posture/oracles/conformance.js +275 -0
  38. package/src/posture/oracles/oracle.js +433 -0
  39. package/src/posture/oracles/registry.js +76 -0
  40. package/src/posture/oracles/scenario-classes.js +223 -0
  41. package/src/posture/replay/replay.js +249 -0
  42. package/src/posture/verification/advisory-state.js +71 -0
  43. package/src/posture/verification/emit.js +257 -0
  44. package/src/posture/verification/hypothesis-promotion.js +211 -0
  45. package/src/posture/verification/patch-negative.js +214 -0
  46. package/src/posture/verification/patch-promotion.js +105 -0
  47. package/src/posture/verification/projection.js +183 -0
  48. package/src/posture/verification/repair-records.js +124 -0
  49. package/src/posture/verifier.js +16 -0
  50. package/src/report/index.js +49 -0
  51. package/src/sandbox/CLAUDE.md +41 -0
  52. package/src/sandbox/backend-namespace.js +7 -0
  53. package/src/sandbox/backend-userspace.js +68 -29
  54. package/src/sandbox/control-probes.js +215 -0
  55. package/src/sandbox/supervise.js +201 -0
  56. package/src/sandbox/trust-boundary.js +113 -0
  57. package/src/sandbox/trust-domains.js +171 -0
@@ -637,12 +637,13 @@ __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependen
637
637
  /* harmony import */ var _posture_integrity_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(71130);
638
638
  /* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(31174);
639
639
  /* harmony import */ var _posture_cache_economics_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(58752);
640
- /* harmony import */ var _redact_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(83468);
640
+ /* harmony import */ var _redact_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(83468);
641
641
  /* harmony import */ var _report_index_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(75917);
642
- /* harmony import */ var _posture_provenance_schema_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(34594);
643
- /* harmony import */ var _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(20095);
644
- /* harmony import */ var _posture_agents_memory_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(79907);
645
- /* harmony import */ var _posture_cve_lookup_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(71364);
642
+ /* harmony import */ var _posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(51864);
643
+ /* harmony import */ var _posture_provenance_schema_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(34594);
644
+ /* harmony import */ var _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(20095);
645
+ /* harmony import */ var _posture_agents_memory_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(79907);
646
+ /* harmony import */ var _posture_cve_lookup_js__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(71364);
646
647
  var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([_report_index_js__WEBPACK_IMPORTED_MODULE_12__]);
647
648
  _report_index_js__WEBPACK_IMPORTED_MODULE_12__ = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[0];
648
649
  // MCP tool implementations — PRD Feature 2, hardened against the OWASP MCP
@@ -673,6 +674,8 @@ _report_index_js__WEBPACK_IMPORTED_MODULE_12__ = (__webpack_async_dependencies__
673
674
 
674
675
 
675
676
 
677
+ // X-206: the one projection of the verification record, shared with the JSON report, the text report and the autopilot response.
678
+
676
679
  // Git-origin provenance (Finding Provenance M0/M1). Distinct from
677
680
  // `finding.provenance` (AI-authorship) and from an SCA entry's `provenance`
678
681
  // (Sigstore/SLSA attestation) — see report/index.js's import comment.
@@ -1085,7 +1088,7 @@ const scan_diff = {
1085
1088
  // scanning), left as-is rather than silently claimed fixed here.
1086
1089
  const findings = (0,_report_index_js__WEBPACK_IMPORTED_MODULE_12__.normalizeFindings)(result.scan)
1087
1090
  .filter(f => wantSet.has(String(f.file || '').replace(/\\/g, '/')) && (sevRank[f.severity] ?? 0) >= min)
1088
- .map(f => (0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactFinding */ .lE)({
1091
+ .map(f => (0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactFinding */ .lE)({
1089
1092
  id: f.id, severity: f.severity, file: f.file, line: f.line,
1090
1093
  title: f.vuln, cwe: f.cwe,
1091
1094
  description: f.description, remediation: f.remediation,
@@ -1148,7 +1151,7 @@ const query_taint = {
1148
1151
  const hay = [f.description, f.title, f.vuln, f.snippet, JSON.stringify(f.trace || '')].join(' ').toLowerCase();
1149
1152
  return hay.includes(srcL) && hay.includes(sinkL);
1150
1153
  });
1151
- const page = all.slice(off, off + lim).map(f => (0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactFinding */ .lE)({
1154
+ const page = all.slice(off, off + lim).map(f => (0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactFinding */ .lE)({
1152
1155
  id: f.id, severity: f.severity, file: f.file, line: f.line,
1153
1156
  title: f.title || f.vuln, description: f.description,
1154
1157
  trace: f.trace || null,
@@ -1186,7 +1189,7 @@ const explain_finding = {
1186
1189
  if (!scan) throw new Error(`No usable scan state (${status}).`);
1187
1190
  const f = _findById(scan, finding_id);
1188
1191
  if (!f) throw new Error(`Finding not found: ${finding_id}`);
1189
- const redacted = (0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactFinding */ .lE)({
1192
+ const redacted = (0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactFinding */ .lE)({
1190
1193
  id: f.id, severity: f.severity, file: f.file, line: f.line,
1191
1194
  title: f.title || f.vuln, cwe: f.cwe,
1192
1195
  description: f.description, remediation: f.remediation,
@@ -1243,9 +1246,13 @@ const explain_finding = {
1243
1246
  // logins/CODEOWNERS lines) through this MCP surface because this call
1244
1247
  // passed no options object at all, silently defeating their policy at
1245
1248
  // this one output boundary while report/index.js honoured it.
1246
- findingProvenance: f.findingProvenance ? (0,_posture_provenance_schema_js__WEBPACK_IMPORTED_MODULE_13__/* .redactFindingProvenance */ .As)(f.findingProvenance, {
1249
+ findingProvenance: f.findingProvenance ? (0,_posture_provenance_schema_js__WEBPACK_IMPORTED_MODULE_14__/* .redactFindingProvenance */ .As)(f.findingProvenance, {
1247
1250
  pseudonymize: process.env.AGENTIC_SECURITY_PSEUDONYMIZE_AUTHORS === '1',
1248
1251
  }) : null,
1252
+ // X-206: the same verification projection the JSON and text reports carry; absent when the finding has no record.
1253
+ ...(f.verificationRecord && typeof f.verificationRecord === 'object'
1254
+ ? { verificationRecord: f.verificationRecord, ...(0,_posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__/* .verificationFields */ .ze)(f.verificationRecord, { replay: f.verificationReplay }) }
1255
+ : {}),
1249
1256
  };
1250
1257
  },
1251
1258
  };
@@ -1323,7 +1330,7 @@ const _scrubRoot = (sessionRoot, text) => {
1323
1330
  const roots = new Set([node_path__WEBPACK_IMPORTED_MODULE_2__.resolve(sessionRoot)]);
1324
1331
  try { roots.add(node_fs__WEBPACK_IMPORTED_MODULE_0__.realpathSync(node_path__WEBPACK_IMPORTED_MODULE_2__.resolve(sessionRoot))); } catch { /* root vanished: the plain path is still scrubbed */ }
1325
1332
  for (const r of roots) out = out.split(r).join('<root>');
1326
- return (0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactString */ .rd)(out);
1333
+ return (0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactString */ .rd)(out);
1327
1334
  };
1328
1335
  const _gateSummary = (g) => (g ? {
1329
1336
  path: g.path ? g.path.ok : undefined, syntax: g.syntax ? g.syntax.ok : undefined,
@@ -1522,7 +1529,7 @@ const apply_fix = {
1522
1529
  // default. Normalized to the scan+lint verdict shape used below.
1523
1530
  const { verifyFixWithTests } = await __webpack_require__.e(/* import() */ 4113).then(__webpack_require__.bind(__webpack_require__, 64113));
1524
1531
  const t = await verifyFixWithTests({ scanRoot: ctx.sessionRoot, originalFindingStableId: f.stableId, files: _files });
1525
- verdict = { ok: t.ok, summary: t.summary, rescan: t.legs?.scan?.detail, lint: t.legs?.lint?.detail, tests: t.legs?.tests, testVerdict: t.verdict };
1532
+ verdict = { ok: t.ok, summary: t.summary, rescan: t.legs?.scan?.detail, lint: t.legs?.lint?.detail, tests: t.legs?.tests, testVerdict: t.verdict, verificationRecord: t.verificationRecord ?? null };
1526
1533
  } else {
1527
1534
  const verifyFixCore = await getVerifyFixCore();
1528
1535
  verdict = await verifyFixCore({
@@ -1540,6 +1547,9 @@ const apply_fix = {
1540
1547
  _meta: META, applied: false,
1541
1548
  reason: `patch rejected by verifier: ${verdict.summary || verdict.rescan?.reason || 'did not verify'}`,
1542
1549
  verify: { rescan: verdict.rescan, lint: { runner: verdict.lint?.runner, ok: verdict.lint?.ok }, honesty: verdict.honesty || null },
1550
+ // X-201: the same version-1 verification record every surface emits (additive; null when none could be formed).
1551
+ verificationRecord: verdict.verificationRecord ?? null,
1552
+ ...(0,_posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__/* .verificationFields */ .ze)(verdict.verificationRecord),
1543
1553
  };
1544
1554
  }
1545
1555
  // FR-307/FR-1002/D-0024: this caller-supplied-patch branch writes via
@@ -1560,7 +1570,7 @@ const apply_fix = {
1560
1570
  }
1561
1571
  const materialClassification = (0,_posture_material_change_js__WEBPACK_IMPORTED_MODULE_6__/* .classifyFixMaterialRisk */ .kz)(filesForMaterialClassification);
1562
1572
  if (dry_run) {
1563
- return { _meta: META, applied: false, dryRun: true, verified: true, files: Object.keys(confinedAbs), summary: verdict.summary, materialClassification };
1573
+ return { _meta: META, applied: false, dryRun: true, verified: true, files: Object.keys(confinedAbs), summary: verdict.summary, verificationRecord: verdict.verificationRecord ?? null, ...(0,_posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__/* .verificationFields */ .ze)(verdict.verificationRecord), materialClassification };
1564
1574
  }
1565
1575
  const approvalRefusal = _highImpactApprovalRefusal(ctx, materialClassification, fixMeta);
1566
1576
  if (approvalRefusal) return approvalRefusal;
@@ -1591,7 +1601,7 @@ const apply_fix = {
1591
1601
  }
1592
1602
  let acceptance = null;
1593
1603
  try { acceptance = (0,_posture_fix_history_js__WEBPACK_IMPORTED_MODULE_4__/* .fixAcceptanceRate */ .XR)(ctx.sessionRoot); } catch { /* best-effort */ }
1594
- return { _meta: META, applied: true, verified: true, patched: written, integrity: status, verify: { summary: verdict.summary }, acceptance, materialClassification };
1604
+ return { _meta: META, applied: true, verified: true, patched: written, integrity: status, verify: { summary: verdict.summary, verificationRecord: verdict.verificationRecord ?? null, ...(0,_posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__/* .verificationFields */ .ze)(verdict.verificationRecord) }, acceptance, materialClassification };
1595
1605
  }
1596
1606
 
1597
1607
  if (typeof f.fix?.replacement !== 'string') {
@@ -1680,7 +1690,7 @@ const apply_fix = {
1680
1690
  // proceed with apply_fix.
1681
1691
  const verify_fix = {
1682
1692
  name: 'verify_fix',
1683
- description: 'Verify a proposed patch before applying. Re-scans the patched files in memory, runs the project linter, runs the project test suite, checks fix honesty (FULL/MITIGATION/WORKAROUND) when fixMeta is supplied, and re-runs the PoC when one exists. Returns { ok, rescan, lint, tests, honesty, poc, summary }. Does not write to the target project’s own files, but DOES append one record per attempt to .agentic-security/fix-metrics.jsonl for the measured fix-loop.',
1693
+ description: 'Verify a proposed patch before applying. Re-scans the patched files in memory, runs the project linter, runs the project test suite, checks fix honesty (FULL/MITIGATION/WORKAROUND) when fixMeta is supplied, and re-runs the PoC when one exists. Returns { ok, rescan, lint, tests, honesty, poc, summary, verificationRecord } (verificationRecord is the shared version-1 record: outcome is one of not-run, unsupported, inconclusive, error, refuted, confirmed, and a static re-scan alone is never reported as confirmed; verificationView is the shared projection every interface shows for it, with state, scope, evidence ids, replay prerequisites and what was and was not verified). Does not write to the target project’s own files, but DOES append one record per attempt to .agentic-security/fix-metrics.jsonl for the measured fix-loop.',
1684
1694
  inputSchema: {
1685
1695
  type: 'object',
1686
1696
  additionalProperties: false,
@@ -1768,7 +1778,7 @@ const verify_fix = {
1768
1778
  _meta: META,
1769
1779
  ok: r.ok,
1770
1780
  rescan: { ok: r.rescan.ok, reason: r.rescan.reason, introduced: r.rescan.introduced || [] },
1771
- lint: { runner: r.lint.runner, ok: r.lint.ok, skipped: r.lint.skipped || false, output: (0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactString */ .rd)(r.lint.output || '').slice(0, 1500) },
1781
+ lint: { runner: r.lint.runner, ok: r.lint.ok, skipped: r.lint.skipped || false, output: (0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactString */ .rd)(r.lint.output || '').slice(0, 1500) },
1772
1782
  // verifyFix computes five legs, not two — tests/honesty/poc were
1773
1783
  // being silently dropped here, leaving an agent with no structured
1774
1784
  // way to see WHY verification failed when the failure was in one
@@ -1779,8 +1789,12 @@ const verify_fix = {
1779
1789
  // it can echo proof-harness detail derived from scanned source.
1780
1790
  tests: r.tests,
1781
1791
  honesty: r.honesty,
1782
- poc: r.poc ? { ...r.poc, reason: r.poc.reason ? (0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactString */ .rd)(r.poc.reason) : r.poc.reason } : r.poc,
1792
+ poc: r.poc ? { ...r.poc, reason: r.poc.reason ? (0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactString */ .rd)(r.poc.reason) : r.poc.reason } : r.poc,
1783
1793
  summary: r.summary,
1794
+ // X-201: the one version-1 verification record (additive). It states what was actually established, e.g.
1795
+ // `not-run` when no exploit oracle executed against the patch, never a bare pass/fail.
1796
+ verificationRecord: r.verificationRecord ?? null,
1797
+ ...(0,_posture_verification_projection_js__WEBPACK_IMPORTED_MODULE_13__/* .verificationFields */ .ze)(r.verificationRecord),
1784
1798
  };
1785
1799
  } catch (e) {
1786
1800
  return { _meta: META, ok: false, reason: `verify_fix failed: ${e.message}` };
@@ -2123,7 +2137,7 @@ const append_agents_memory = {
2123
2137
  required: ['agent', 'body'],
2124
2138
  },
2125
2139
  async handler({ agent, body }, ctx) {
2126
- const r = (0,_posture_agents_memory_js__WEBPACK_IMPORTED_MODULE_16__/* .appendAgentsMemory */ .eu)(ctx.sessionRoot, { agent, body });
2140
+ const r = (0,_posture_agents_memory_js__WEBPACK_IMPORTED_MODULE_17__/* .appendAgentsMemory */ .eu)(ctx.sessionRoot, { agent, body });
2127
2141
  return { _meta: META, ...r };
2128
2142
  },
2129
2143
  };
@@ -2139,7 +2153,7 @@ const read_agents_memory = {
2139
2153
  },
2140
2154
  },
2141
2155
  async handler({ full }, ctx) {
2142
- const body = (0,_posture_agents_memory_js__WEBPACK_IMPORTED_MODULE_16__/* .readAgentsMemory */ .ox)(ctx.sessionRoot);
2156
+ const body = (0,_posture_agents_memory_js__WEBPACK_IMPORTED_MODULE_17__/* .readAgentsMemory */ .ox)(ctx.sessionRoot);
2143
2157
  if (!body) return { _meta: META, present: false };
2144
2158
  if (full) return { _meta: META, present: true, length: body.length, content: body };
2145
2159
  // Tail-only — same logic as summarizeForSession but inlined to avoid a
@@ -2183,7 +2197,7 @@ const query_triage_memory = {
2183
2197
  // they landed in, rather than hardcoding a field allowlist that could
2184
2198
  // miss one.
2185
2199
  let results;
2186
- try { results = JSON.parse((0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactString */ .rd)(JSON.stringify(raw))); }
2200
+ try { results = JSON.parse((0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactString */ .rd)(JSON.stringify(raw))); }
2187
2201
  catch { results = raw; }
2188
2202
  return {
2189
2203
  _meta: META,
@@ -2219,7 +2233,7 @@ const query_findings_memory = {
2219
2233
  // whole-structure redactString round-trip is applied rather than a
2220
2234
  // per-field allowlist that could miss one of the four shapes.
2221
2235
  let body;
2222
- try { body = JSON.parse((0,_redact_js__WEBPACK_IMPORTED_MODULE_15__/* .redactString */ .rd)(JSON.stringify(raw))); }
2236
+ try { body = JSON.parse((0,_redact_js__WEBPACK_IMPORTED_MODULE_16__/* .redactString */ .rd)(JSON.stringify(raw))); }
2223
2237
  catch { body = raw; }
2224
2238
  return { _meta: META, ...body };
2225
2239
  },
@@ -2241,7 +2255,7 @@ const lookup_cve = {
2241
2255
  required: ['cve'],
2242
2256
  },
2243
2257
  async handler({ cve }, _ctx) {
2244
- const r = (0,_posture_cve_lookup_js__WEBPACK_IMPORTED_MODULE_17__/* .lookupCve */ .x)(cve);
2258
+ const r = (0,_posture_cve_lookup_js__WEBPACK_IMPORTED_MODULE_18__/* .lookupCve */ .x)(cve);
2245
2259
  return { _meta: META, ...r };
2246
2260
  },
2247
2261
  };
@@ -2346,7 +2360,7 @@ const apply_sca_upgrade = {
2346
2360
  },
2347
2361
  };
2348
2362
 
2349
- const ALL_TOOLS = [scan_diff, query_taint, explain_finding, apply_fix, verify_fix, synthesize_fix, find_rule_module, append_scratchpad, read_scratchpad, append_agents_memory, read_agents_memory, lookup_cve, synthesize_sca_upgrade, apply_sca_upgrade, query_triage_memory, query_findings_memory, query_cache_telemetry, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_14__/* .dataflow_get_graph */ .HX, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_14__/* .dataflow_get_node */ .HI, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_14__/* .dataflow_get_edge */ .gC, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_14__/* .dataflow_get_flow */ .ri];
2363
+ const ALL_TOOLS = [scan_diff, query_taint, explain_finding, apply_fix, verify_fix, synthesize_fix, find_rule_module, append_scratchpad, read_scratchpad, append_agents_memory, read_agents_memory, lookup_cve, synthesize_sca_upgrade, apply_sca_upgrade, query_triage_memory, query_findings_memory, query_cache_telemetry, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_15__/* .dataflow_get_graph */ .HX, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_15__/* .dataflow_get_node */ .HI, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_15__/* .dataflow_get_edge */ .gC, _dataflow_tools_js__WEBPACK_IMPORTED_MODULE_15__/* .dataflow_get_flow */ .ri];
2350
2364
 
2351
2365
  __webpack_async_result__();
2352
2366
  } catch(e) { __webpack_async_result__(e); } });
@@ -1,8 +1,8 @@
1
- export const id = 1905;
2
- export const ids = [1905,3499];
1
+ export const id = 9999;
2
+ export const ids = [9999,3499];
3
3
  export const modules = {
4
4
 
5
- /***/ 11905:
5
+ /***/ 49999:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
 
@@ -788,6 +788,8 @@ var external_node_fs_ = __webpack_require__(73024);
788
788
  var external_node_path_ = __webpack_require__(76760);
789
789
  // EXTERNAL MODULE: ./src/posture/state-dir.js
790
790
  var state_dir = __webpack_require__(31174);
791
+ // EXTERNAL MODULE: ./src/posture/verification/advisory-state.js
792
+ var advisory_state = __webpack_require__(73826);
791
793
  ;// CONCATENATED MODULE: ./src/discovery/memory.js
792
794
  // Cross-run discovery memory — PRD Phase 3 / C4.
793
795
  //
@@ -831,6 +833,7 @@ var state_dir = __webpack_require__(31174);
831
833
 
832
834
 
833
835
 
836
+
834
837
  const MEMORY_SCHEMA = 'agentic-security/discovery-memory@1';
835
838
  const MEMORY_FILE = external_node_path_.join('.agentic-security', 'discovery-memory.json');
836
839
 
@@ -864,14 +867,15 @@ function loadMemory(scanRoot) {
864
867
  }
865
868
  }
866
869
 
867
- /** Persist. Failure is non-fatal — the run still produced its report. */
870
+ /**
871
+ * Persist. Failure is non-fatal -- the run still produced its report. X-207: the write goes through the advisory-state writer
872
+ * (closed file-name allowlist, atomic rename, never through a link), so hunt output cannot reach `last-scan.json` or its
873
+ * signature even when a hostile tree planted a link at the memory file.
874
+ */
868
875
  function saveMemory(scanRoot, memory) {
869
876
  try {
870
- const p = external_node_path_.join(scanRoot, MEMORY_FILE);
871
877
  if (!(0,state_dir.stateWritesEnabled)()) return;
872
- external_node_fs_.mkdirSync(external_node_path_.dirname(p), { recursive: true });
873
- external_node_fs_.writeFileSync(p, JSON.stringify(memory, null, 2) + '\n');
874
- return true;
878
+ return (0,advisory_state/* writeAdvisoryState */.iS)(scanRoot, external_node_path_.basename(MEMORY_FILE), JSON.stringify(memory, null, 2) + '\n').ok;
875
879
  } catch {
876
880
  return false;
877
881
  }
@@ -953,6 +957,231 @@ function forgetRefuted(memory) {
953
957
  return { ...emptyMemory(), ...memory, candidates };
954
958
  }
955
959
 
960
+ // EXTERNAL MODULE: ./src/posture/verification/emit.js
961
+ var emit = __webpack_require__(85578);
962
+ // EXTERNAL MODULE: ./src/posture/oracles/oracle.js + 4 modules
963
+ var oracle = __webpack_require__(80219);
964
+ // EXTERNAL MODULE: ./src/posture/assurance/verification-record.js
965
+ var verification_record = __webpack_require__(29668);
966
+ // EXTERNAL MODULE: ./src/posture/assurance/identity.js
967
+ var identity = __webpack_require__(41877);
968
+ // EXTERNAL MODULE: ./src/posture/assurance/schema-kit.js
969
+ var schema_kit = __webpack_require__(53353);
970
+ // EXTERNAL MODULE: ./src/posture/verification-separation.js
971
+ var verification_separation = __webpack_require__(3107);
972
+ ;// CONCATENATED MODULE: ./src/posture/verification/hypothesis-promotion.js
973
+ // Promoting an advisory hypothesis into a finding (X-207.AC02, AC03).
974
+ //
975
+ // A hunt hypothesis is advisory. It becomes a FINDING only through `promoteHypothesis`, which needs all three of:
976
+ //
977
+ // 1. a trusted verifier receipt, VERIFIED here (not merely present): issued by `runOracle` in this process
978
+ // (`isIssuedReceipt`: a copy, a JSON round trip or a hand-built object is not), bound to this hypothesis and one exact
979
+ // commit, carrying a valid version-1 record whose id is the receipt's `recordId`, settled `confirmed` at the
980
+ // `runtime-confirmed` level with trusted-runtime-proof evidence from the trusted runner;
981
+ // 2. an explicit policy evaluation. The caller passes a policy; the result carries every rule that was evaluated with its
982
+ // outcome and a digest of the policy. The mandatory rules cannot be removed by a policy, and a policy key this module does
983
+ // not know is refused (fail closed);
984
+ // 3. an audit record, returned for EVERY decision (promoted or not), that links the source hypothesis to the promoted finding
985
+ // (or to none) and names the receipt, the record and the policy evaluation. It carries no clock, so identical inputs give
986
+ // an identical record.
987
+ //
988
+ // What can never satisfy the policy: a confidence score, a repeated or unanimous model agreement, a refutation panel's votes,
989
+ // a taint-probe tier, or a severity. None of them is an input to any rule except the optional `minConfidence` / `minAgreement`
990
+ // rules, which can only ADD a requirement: the receipt rules are always evaluated, so a hypothesis with perfect confidence and
991
+ // no receipt is rejected, and a policy made only of those rules still rejects it.
992
+ //
993
+ // The producer of a hunt hypothesis and the verifier that confirms it must be different parties (`verification-separation.js`
994
+ // is reused for that check). The audit record is returned, not persisted: persisting an audit trail needs an artifact-registry
995
+ // classification and a signing domain, which are not decided here.
996
+
997
+
998
+
999
+
1000
+
1001
+
1002
+
1003
+ const AUDIT_SCHEMA = 'agentic-security/promotion-audit';
1004
+ const AUDIT_VERSION = '1.0.0';
1005
+ const POLICY_SCHEMA = 'agentic-security/promotion-policy';
1006
+
1007
+ const DECISIONS = Object.freeze(['promoted', 'rejected', 'unsupported', 'inconclusive']);
1008
+ const SEVERITY_ORDER = ['info', 'low', 'medium', 'high', 'critical'];
1009
+
1010
+ /** The default policy: runtime-confirmed by a class oracle other than the functional-regression one, severity capped at high. */
1011
+ const DEFAULT_PROMOTION_POLICY = Object.freeze({
1012
+ schema: POLICY_SCHEMA, id: 'default', version: '1',
1013
+ allowedOracleClasses: Object.freeze(oracle/* ORACLE_CLASSES */.aI.filter((c) => c !== 'functional-regression')),
1014
+ maxSeverity: 'high',
1015
+ });
1016
+
1017
+ const POLICY_KEYS = ['schema', 'id', 'version', 'allowedOracleClasses', 'maxSeverity', 'expectedCommit', 'minConfidence', 'minAgreement'];
1018
+
1019
+ const PROMOTED = new WeakSet();
1020
+ /** True only for a finding `promoteHypothesis` itself returned. */
1021
+ function isPromotedFinding(f) { return isPlainObject(f) && PROMOTED.has(f); }
1022
+
1023
+ function validatePolicy(policy) {
1024
+ if (!(0,schema_kit/* isPlainObject */.Qd)(policy)) return ['no policy was supplied: promotion needs an explicit policy evaluation'];
1025
+ const errs = [];
1026
+ for (const k of Object.keys(policy)) if (!POLICY_KEYS.includes(k)) errs.push(`unknown policy key '${k}'`);
1027
+ if (policy.schema !== POLICY_SCHEMA) errs.push(`policy.schema must be '${POLICY_SCHEMA}'`);
1028
+ if (typeof policy.id !== 'string' || !policy.id) errs.push('policy.id is required');
1029
+ if (typeof policy.version !== 'string' || !policy.version) errs.push('policy.version is required');
1030
+ if (!Array.isArray(policy.allowedOracleClasses) || policy.allowedOracleClasses.length === 0 || !policy.allowedOracleClasses.every((c) => oracle/* ORACLE_CLASSES */.aI.includes(c))) errs.push('policy.allowedOracleClasses must list known oracle classes');
1031
+ if (!SEVERITY_ORDER.includes(policy.maxSeverity)) errs.push('policy.maxSeverity must be a severity');
1032
+ if (policy.expectedCommit !== undefined && !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(policy.expectedCommit))) errs.push('policy.expectedCommit must be an exact commit');
1033
+ for (const k of ['minConfidence', 'minAgreement']) if (policy[k] !== undefined && !(typeof policy[k] === 'number' && policy[k] >= 0 && policy[k] <= 1)) errs.push(`policy.${k} must be a number between 0 and 1`);
1034
+ return errs;
1035
+ }
1036
+
1037
+ function agreementOf(h) {
1038
+ const votes = h?.discovery?.refutation?.votes;
1039
+ if (!Array.isArray(votes) || votes.length === 0) return null;
1040
+ const upheld = votes.filter((v) => v && (v.verdict === 'upheld' || v.upheld === true)).length;
1041
+ return upheld / votes.length;
1042
+ }
1043
+
1044
+ /**
1045
+ * Evaluate the policy for one hypothesis and one oracle result. Every rule is evaluated and listed.
1046
+ * @returns {{ ok: boolean, rules: {id: string, pass: boolean, detail: string}[], status: string, policyDigest: string|null, record: object|null }}
1047
+ */
1048
+ function evaluatePromotionPolicy({ hypothesis, result, policy } = {}) {
1049
+ const rules = [];
1050
+ const rule = (id, pass, detail) => rules.push({ id, pass: !!pass, detail });
1051
+ const bad = validatePolicy(policy);
1052
+ if (bad.length) {
1053
+ rule('policy-valid', false, bad.join('; '));
1054
+ return { ok: false, rules, status: 'rejected', policyDigest: null, record: null };
1055
+ }
1056
+ rule('policy-valid', true, `policy '${policy.id}' v${policy.version}`);
1057
+ const policyDigest = (0,identity/* digestOf */.ol)(policy);
1058
+
1059
+ const receipt = result?.receipt ?? null;
1060
+ const record = result?.record ?? null;
1061
+ const hypothesisId = (0,schema_kit/* isPlainObject */.Qd)(hypothesis) ? (0,identity/* hypothesisIdFromFinding */.XC)(hypothesis) : null;
1062
+
1063
+ rule('hypothesis-advisory', (0,advisory_state/* isAdvisoryHypothesis */.Si)(hypothesis) && !!hypothesisId, 'the source must be an unpromoted hunt hypothesis with a stable id');
1064
+ const issued = (0,oracle/* isIssuedReceipt */.AH)(receipt) && receipt.issuedBy === 'verifier' && receipt.schema === 'agentic-security/oracle-receipt';
1065
+ rule('receipt-issued', issued, issued ? 'the receipt was issued by the verifier domain in this process' : 'no receipt issued by the verifier domain was supplied (a copy, a parsed file or a constructed object is not valid)');
1066
+
1067
+ const recordOk = (0,schema_kit/* isPlainObject */.Qd)(record) && (0,verification_record/* validateVerificationRecord */.LL)(record).ok;
1068
+ rule('record-valid', recordOk, recordOk ? 'the verification record validates' : 'the verification record is missing or invalid');
1069
+ const bound = issued && recordOk && receipt.recordId === record.id && receipt.request.hypothesisId === hypothesisId && record.hypothesisId === hypothesisId;
1070
+ rule('receipt-binds-hypothesis', bound, bound ? 'the receipt, the record and the hypothesis name one another' : 'the receipt or record is for a different hypothesis, or they do not reference each other');
1071
+ const commit = issued ? receipt.request.commit : null;
1072
+ rule('commit-bound', !!commit && (!policy.expectedCommit || policy.expectedCommit === commit) && recordOk && record.commit === commit,
1073
+ commit ? (policy.expectedCommit && policy.expectedCommit !== commit ? `the receipt is for a different commit than the policy expects` : 'the receipt is bound to one exact commit') : 'the receipt is not bound to an exact commit');
1074
+
1075
+ const outcome = bound ? receipt.settled.outcome : null;
1076
+ rule('outcome-confirmed', outcome === 'confirmed', `the verifier settled '${outcome ?? 'nothing'}'`);
1077
+ const runtime = bound && record.confirmationLevel === 'runtime-confirmed'
1078
+ && record.evidence.some((e) => e.kind === 'trusted-runtime-proof' && e.producer === 'trusted-runner' && record.evidenceRefs.includes(e.id));
1079
+ rule('runtime-confirmed', runtime, runtime ? 'trusted runtime proof from the trusted runner is referenced' : 'no referenced trusted runtime proof at the runtime-confirmed level');
1080
+ const classOk = bound && policy.allowedOracleClasses.includes(receipt.oracle.class);
1081
+ rule('class-allowed', classOk, classOk ? `oracle class '${receipt.oracle.class}' is allowed` : 'the oracle class is not allowed by the policy');
1082
+
1083
+ // producer and verifier must be different parties
1084
+ const probe = { ...hypothesis };
1085
+ let sep = { ok: false, reason: 'no receipt' };
1086
+ if (bound) {
1087
+ const stamped = (0,verification_separation/* recordProducer */.Ms)(probe, `hunter:${hypothesis?.discovery?.lens ?? 'unknown'}`);
1088
+ sep = stamped.ok ? (0,verification_separation/* assertSeparation */.jm)(probe, `verifier:oracle:${receipt.oracle.id}`) : stamped;
1089
+ }
1090
+ rule('producer-verifier-separation', sep.ok, sep.ok ? 'the hunter and the verifying oracle are different parties' : String(sep.reason));
1091
+
1092
+ // additive-only rules: they can add a requirement, never satisfy one
1093
+ if (policy.minConfidence !== undefined) {
1094
+ const c = typeof hypothesis?.confidence === 'number' ? hypothesis.confidence : null;
1095
+ rule('min-confidence', c !== null && c >= policy.minConfidence, `an additional requirement only: confidence ${c ?? 'absent'} against ${policy.minConfidence}`);
1096
+ }
1097
+ if (policy.minAgreement !== undefined) {
1098
+ const a = agreementOf(hypothesis);
1099
+ rule('min-agreement', a !== null && a >= policy.minAgreement, `an additional requirement only: panel agreement ${a ?? 'absent'} against ${policy.minAgreement}`);
1100
+ }
1101
+
1102
+ const ok = rules.every((r) => r.pass);
1103
+ let status = 'promoted';
1104
+ if (!ok) {
1105
+ const settled = bound ? outcome : ((0,schema_kit/* isPlainObject */.Qd)(record) && typeof record.outcome === 'string' ? record.outcome : null);
1106
+ if (settled === 'unsupported') status = 'unsupported';
1107
+ else if (['inconclusive', 'not-run', 'error'].includes(settled)) status = 'inconclusive';
1108
+ else status = 'rejected';
1109
+ }
1110
+ return { ok, rules, status, policyDigest, record: recordOk ? record : null };
1111
+ }
1112
+
1113
+ const capSeverity = (sev, max) => (SEVERITY_ORDER.indexOf(sev) > SEVERITY_ORDER.indexOf(max) ? max : (SEVERITY_ORDER.includes(sev) ? sev : max));
1114
+
1115
+ /**
1116
+ * Promote one hypothesis, or decline to. Never throws.
1117
+ *
1118
+ * @param {object} args
1119
+ * @param {object} args.hypothesis a hunt finding (`discovery/judge.js` shape)
1120
+ * @param {object} args.result the `runOracle` result for it (`{ record, receipt, outcome }`)
1121
+ * @param {object} args.policy an explicit policy (see DEFAULT_PROMOTION_POLICY)
1122
+ * @returns {{ ok: boolean, decision: string, finding: object|null, audit: object, evaluation: object }}
1123
+ */
1124
+ function promoteHypothesis({ hypothesis, result, policy } = {}) {
1125
+ let evaluation;
1126
+ try { evaluation = evaluatePromotionPolicy({ hypothesis, result, policy }); }
1127
+ catch (e) { evaluation = { ok: false, status: 'rejected', policyDigest: null, record: null, rules: [{ id: 'evaluation', pass: false, detail: `the policy evaluation failed: ${String(e?.message || e).slice(0, 120)}` }] }; }
1128
+ const hypothesisId = (0,schema_kit/* isPlainObject */.Qd)(hypothesis) ? (0,identity/* hypothesisIdFromFinding */.XC)(hypothesis) : null;
1129
+ const decision = evaluation.ok ? 'promoted' : evaluation.status;
1130
+
1131
+ let finding = null;
1132
+ if (evaluation.ok) {
1133
+ const h = hypothesis;
1134
+ const base = {
1135
+ id: `promoted-${hypothesisId}`, stableId: hypothesisId,
1136
+ severity: capSeverity(h.severity, policy.maxSeverity),
1137
+ file: h.file, line: h.line, vuln: h.vuln, cwe: h.cwe, description: h.description, remediation: h.remediation,
1138
+ // A promoted finding is no longer a hunt hypothesis: it carries neither the hunt parser nor the `discovery` stamp, so the
1139
+ // advisory exclusion does not apply to it, and its origin lives in `promotedFrom`.
1140
+ parser: 'PROMOTED-HYPOTHESIS', family: h.family ?? 'other', ruleId: h.ruleId ?? 'promoted-hypothesis', snippet: h.snippet ?? '',
1141
+ };
1142
+ finding = { ...base, verificationRecord: JSON.parse(JSON.stringify(evaluation.record)) };
1143
+ }
1144
+
1145
+ const audit = {
1146
+ schema: AUDIT_SCHEMA, schemaVersion: AUDIT_VERSION, decision,
1147
+ sourceHypothesis: {
1148
+ id: hypothesisId, digest: (0,identity/* digestOf */.ol)((0,schema_kit/* isPlainObject */.Qd)(hypothesis) ? hypothesis : null),
1149
+ origin: { lens: hypothesis?.discovery?.lens ?? null, parser: hypothesis?.parser ?? null, family: hypothesis?.family ?? null },
1150
+ },
1151
+ promotedFinding: finding ? { stableId: finding.stableId, id: finding.id } : null,
1152
+ verification: {
1153
+ recordId: evaluation.record?.id ?? null, receiptDigest: (0,oracle/* isIssuedReceipt */.AH)(result?.receipt) ? result.receipt.receiptDigest : null,
1154
+ oracle: evaluation.record?.oracle ?? null, outcome: evaluation.record?.outcome ?? null,
1155
+ confirmationLevel: evaluation.record?.confirmationLevel ?? null, commit: evaluation.record?.commit ?? null,
1156
+ },
1157
+ policy: { id: (0,schema_kit/* isPlainObject */.Qd)(policy) ? policy.id ?? null : null, version: (0,schema_kit/* isPlainObject */.Qd)(policy) ? policy.version ?? null : null, digest: evaluation.policyDigest, rules: evaluation.rules },
1158
+ };
1159
+ audit.id = (0,identity/* semanticId */.YN)('padt', audit, ['decision', 'sourceHypothesis', 'promotedFinding', 'verification', 'policy']);
1160
+ deepFreeze(audit);
1161
+ if (finding) {
1162
+ finding.promotedFrom = { hypothesisId, auditId: audit.id, lens: hypothesis?.discovery?.lens ?? null };
1163
+ deepFreeze(finding);
1164
+ PROMOTED.add(finding);
1165
+ }
1166
+ return { ok: !!finding, decision, finding, audit, evaluation };
1167
+ }
1168
+
1169
+ /** Do an audit record, the hypothesis it names and the promoted finding it names agree? (The link, checked from both ends.) */
1170
+ function auditLinksHold({ audit, hypothesis, finding }) {
1171
+ if (!isPlainObject(audit) || audit.schema !== AUDIT_SCHEMA) return false;
1172
+ const hid = isPlainObject(hypothesis) ? hypothesisIdFromFinding(hypothesis) : null;
1173
+ if (!hid || audit.sourceHypothesis?.id !== hid || audit.sourceHypothesis?.digest !== digestOf(hypothesis)) return false;
1174
+ if (audit.id !== semanticId('padt', audit, ['decision', 'sourceHypothesis', 'promotedFinding', 'verification', 'policy'])) return false;
1175
+ if (audit.decision !== 'promoted') return finding == null && audit.promotedFinding === null;
1176
+ return isPromotedFinding(finding) && finding.promotedFrom?.auditId === audit.id && finding.promotedFrom?.hypothesisId === hid
1177
+ && audit.promotedFinding?.stableId === finding.stableId && finding.verificationRecord?.id === audit.verification.recordId;
1178
+ }
1179
+
1180
+ function deepFreeze(o) {
1181
+ for (const v of Object.values(o)) if (v && typeof v === 'object' && !Object.isFrozen(v)) deepFreeze(v);
1182
+ return Object.freeze(o);
1183
+ }
1184
+
956
1185
  ;// CONCATENATED MODULE: ./src/discovery/index.js
957
1186
  //
958
1187
  // Compose the discovery pipeline:
@@ -971,6 +1200,8 @@ function forgetRefuted(memory) {
971
1200
 
972
1201
 
973
1202
 
1203
+
1204
+
974
1205
  // Bridge a candidate to the deterministic layer. A taint finding at or within
975
1206
  // two lines of the candidate corroborates it; a modelled sink on the line
976
1207
  // without a full path is weaker corroboration ("sink-adjacent").
@@ -1239,11 +1470,37 @@ async function runDiscovery(ctx = {}, opts = {}) {
1239
1470
  }));
1240
1471
  }
1241
1472
 
1473
+ // X-201: one version-1 verification record per fresh hypothesis, ADDITIVE and advisory (hunt output never enters
1474
+ // last-scan.json). A taint-corroborated candidate is `inconclusive` (a static probe is corroboration, not
1475
+ // execution) and an uncorroborated one is `not-run`; a model refutation panel is recorded as inference only.
1476
+ const hypothesisCommit = (0,emit.headCommit)(opts.scanRoot);
1477
+ const verificationRecords = [];
1478
+ for (const f of fresh) {
1479
+ const e = (0,emit/* emitVerification */.T)('hunt', f, { finding: f, commit: hypothesisCommit });
1480
+ if (e.ok) verificationRecords.push(e.record);
1481
+ }
1482
+
1483
+ // X-207: opt-in promotion. A hypothesis crosses into a finding ONLY through `promoteHypothesis` (trusted verifier receipt,
1484
+ // explicit policy, audit record). `fresh` is unchanged; promoted findings and every decision's audit record are reported
1485
+ // separately, and nothing here writes `last-scan.json`. `opts.promote = { policy, verify(hypothesis) -> runOracle result }`.
1486
+ let promotions = null;
1487
+ if (opts.promote && typeof opts.promote.verify === 'function') {
1488
+ promotions = [];
1489
+ for (const f of fresh) {
1490
+ let result = null;
1491
+ try { result = await opts.promote.verify(f); } catch { result = null; }
1492
+ const p = promoteHypothesis({ hypothesis: f, result, policy: opts.promote.policy });
1493
+ promotions.push({ hypothesisId: p.audit.sourceHypothesis.id, decision: p.decision, audit: p.audit, finding: p.finding });
1494
+ }
1495
+ }
1496
+
1242
1497
  return {
1243
1498
  schema: 'agentic-security/discovery@1',
1244
1499
  focusAreas: areas.map(a => ({ id: a.id, label: a.label, files: a.files.length, size: a.size })),
1245
1500
  runs,
1246
1501
  fresh,
1502
+ verificationRecords,
1503
+ ...(promotions ? { promotions } : {}),
1247
1504
  duplicates,
1248
1505
  suppressed,
1249
1506
  // `refutedCandidates` holds RAW candidates straight from `disprovePanel`,