@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.
- package/CHANGELOG.md +20 -0
- package/bin/agentic-security.js +11 -2
- package/dist/4113.index.js +59 -8
- package/dist/5613.index.js +500 -0
- package/dist/6239.index.js +469 -0
- package/dist/7838.index.js +38 -2
- package/dist/8798.index.js +1 -1
- package/dist/9207.index.js +5 -0
- package/dist/9560.index.js +36 -22
- package/dist/{1905.index.js → 9999.index.js} +265 -8
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +9 -4
- package/src/discovery/CLAUDE.md +9 -0
- package/src/discovery/index.js +28 -0
- package/src/discovery/memory.js +7 -5
- package/src/fix/apply-fix-service.js +33 -3
- package/src/mcp/CLAUDE.md +9 -1
- package/src/mcp/tools.js +17 -4
- package/src/posture/CLAUDE.md +174 -0
- package/src/posture/assurance/baseline-inventory.js +218 -0
- package/src/posture/assurance/baseline.js +322 -0
- package/src/posture/assurance/bounded-io.js +145 -0
- package/src/posture/assurance/config.js +299 -0
- package/src/posture/assurance/contracts.js +315 -0
- package/src/posture/assurance/identity.js +62 -0
- package/src/posture/assurance/migrations.js +259 -0
- package/src/posture/assurance/schema-kit.js +111 -0
- package/src/posture/assurance/verification-record.js +258 -0
- package/src/posture/attestation.js +5 -0
- package/src/posture/autopilot.js +28 -1
- package/src/posture/evidence-bundle.js +93 -7
- package/src/posture/execution-proof.js +11 -1
- package/src/posture/fix-verify-loop.js +20 -6
- package/src/posture/fix-verify.js +37 -2
- package/src/posture/oracles/adapters.js +360 -0
- package/src/posture/oracles/conformance.js +275 -0
- package/src/posture/oracles/oracle.js +433 -0
- package/src/posture/oracles/registry.js +76 -0
- package/src/posture/oracles/scenario-classes.js +223 -0
- package/src/posture/replay/replay.js +249 -0
- package/src/posture/verification/advisory-state.js +71 -0
- package/src/posture/verification/emit.js +257 -0
- package/src/posture/verification/hypothesis-promotion.js +211 -0
- package/src/posture/verification/patch-negative.js +214 -0
- package/src/posture/verification/patch-promotion.js +105 -0
- package/src/posture/verification/projection.js +183 -0
- package/src/posture/verification/repair-records.js +124 -0
- package/src/posture/verifier.js +16 -0
- package/src/report/index.js +49 -0
- package/src/sandbox/CLAUDE.md +41 -0
- package/src/sandbox/backend-namespace.js +7 -0
- package/src/sandbox/backend-userspace.js +68 -29
- package/src/sandbox/control-probes.js +215 -0
- package/src/sandbox/supervise.js +201 -0
- package/src/sandbox/trust-boundary.js +113 -0
- package/src/sandbox/trust-domains.js +171 -0
package/dist/9560.index.js
CHANGED
|
@@ -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
|
|
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
|
|
643
|
-
/* harmony import */ var
|
|
644
|
-
/* harmony import */ var
|
|
645
|
-
/* harmony import */ var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
2
|
-
export const ids = [
|
|
1
|
+
export const id = 9999;
|
|
2
|
+
export const ids = [9999,3499];
|
|
3
3
|
export const modules = {
|
|
4
4
|
|
|
5
|
-
/***/
|
|
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
|
-
/**
|
|
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
|
-
|
|
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`,
|