@clear-capabilities/agentic-security-scanner 0.175.0 → 0.177.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 +25 -0
- package/bin/agentic-security.js +206 -28
- package/dist/1919.index.js +1 -1
- package/dist/2066.index.js +851 -0
- package/dist/2199.index.js +1 -1
- package/dist/2584.index.js +3350 -0
- package/dist/2923.index.js +13 -0
- package/dist/2946.index.js +81 -0
- package/dist/3196.index.js +27 -0
- package/dist/3727.index.js +292 -0
- package/dist/37d64df006576248d0d8.json +286 -0
- package/dist/4168.index.js +509 -0
- package/dist/4281.index.js +993 -0
- package/dist/5099.index.js +96 -0
- package/dist/5456.index.js +291 -0
- package/dist/5616.index.js +1471 -0
- package/dist/5620.index.js +2494 -0
- package/dist/5817.index.js +714 -0
- package/dist/5937.index.js +672 -0
- package/dist/6333.index.js +98 -0
- package/dist/6655.index.js +3221 -0
- package/dist/7838.index.js +491 -974
- package/dist/818.index.js +4 -3
- package/dist/83.index.js +159 -0
- package/dist/8551.index.js +82 -0
- package/dist/8633.index.js +93 -0
- package/dist/8714.index.js +76 -76
- package/dist/8825.index.js +84 -0
- package/dist/932.index.js +186 -0
- package/dist/9560.index.js +358 -19
- package/dist/9800.index.js +6 -1
- package/dist/agentic-security.mjs +10 -10
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/b4289e728659450b5e19.js +32 -0
- package/package.json +8 -4
- package/src/artifact/CLAUDE.md +4 -0
- package/src/artifact/artifact-evidence.js +63 -0
- package/src/artifact/cosign.js +61 -0
- package/src/artifact/engine-source.js +72 -0
- package/src/artifact/oci-layout.js +47 -0
- package/src/artifact/registry.js +213 -0
- package/src/capabilities/tool-registry.js +2 -0
- package/src/lineage/CLAUDE.md +4 -0
- package/src/lineage/identity-token.js +11 -0
- package/src/lineage/observation-adapters.js +13 -0
- package/src/lineage/runtime-edge-states.js +139 -0
- package/src/lineage/runtime-explorer-view.js +82 -0
- package/src/lineage/runtime-observation.js +11 -0
- package/src/mcp/CLAUDE.md +3 -1
- package/src/mcp/proof-tools.js +132 -0
- package/src/mcp/server.js +1 -1
- package/src/mcp/tools.js +3 -1
- package/src/posture/CLAUDE.md +23 -0
- package/src/posture/artifact-registry.js +4 -0
- package/src/posture/custom-rules.js +54 -8
- package/src/posture/dnr/attack-facts.js +31 -0
- package/src/posture/dnr/cli.js +138 -0
- package/src/posture/dnr/detection-ir.js +123 -0
- package/src/posture/dnr/exploit-correlation.js +25 -0
- package/src/posture/dnr/exposure-window.js +134 -0
- package/src/posture/dnr/hunt.js +84 -0
- package/src/posture/dnr/log-reader.js +189 -0
- package/src/posture/dnr/matchers.js +90 -0
- package/src/posture/dnr/outputs.js +262 -0
- package/src/posture/dnr/payload-classes.js +120 -0
- package/src/posture/dnr/redact.js +25 -0
- package/src/posture/dnr/store.js +57 -0
- package/src/posture/dnr/validate.js +64 -0
- package/src/posture/dnr/view.js +51 -0
- package/src/posture/evaluation/lab/arm-ollama.js +88 -0
- package/src/posture/evaluation/lab/arms.js +142 -0
- package/src/posture/evaluation/lab/cli.js +135 -0
- package/src/posture/evaluation/lab/dataset.js +139 -0
- package/src/posture/evaluation/lab/materialize.js +44 -0
- package/src/posture/evaluation/lab/neutralize.js +144 -0
- package/src/posture/evaluation/lab/report.js +194 -0
- package/src/posture/evaluation/lab/run.js +225 -0
- package/src/posture/evaluation/lab/score.js +135 -0
- package/src/posture/evaluation/lab/scoreboard.js +48 -0
- package/src/posture/evaluation/lab/stats.js +41 -0
- package/src/posture/evaluation/lab/store.js +53 -0
- package/src/posture/fleet/cli.js +260 -63
- package/src/posture/fleet/discover-edges.js +135 -0
- package/src/posture/fleet/enterprise-graph.js +189 -0
- package/src/posture/fleet/fleet-graph.js +9 -4
- package/src/posture/fleet/fleet-manifest.js +50 -5
- package/src/posture/fleet/job-runner.js +153 -0
- package/src/posture/fleet/job-store.js +138 -0
- package/src/posture/fleet/lineage-summary.js +55 -0
- package/src/posture/fleet/org-health.js +56 -0
- package/src/posture/fleet/scan-cache.js +112 -0
- package/src/posture/fleet/scan-child.js +32 -0
- package/src/posture/proof-fabric/cli.js +34 -6
- package/src/posture/proof-fabric/container.js +57 -0
- package/src/posture/proof-fabric/fix-proof.js +2 -1
- package/src/posture/proof-fabric/generate-python.js +129 -0
- package/src/posture/proof-fabric/generate.js +214 -0
- package/src/posture/proof-fabric/reproducer.js +3 -0
- package/src/posture/proof-fabric/runner.js +44 -13
- package/src/posture/proof-fabric/siblings.js +77 -0
- package/src/posture/routing/policy-router.js +103 -0
- package/src/posture/rule-compiler/cli.js +101 -9
- package/src/posture/rule-compiler/corpus.js +13 -9
- package/src/posture/rule-compiler/ir.js +52 -9
- package/src/posture/rule-compiler/mutation.js +72 -1
- package/src/posture/rule-compiler/promotion.js +21 -5
- package/src/posture/rule-compiler/proposal.js +137 -0
- package/src/posture/rule-compiler/regression.js +95 -0
- package/src/posture/rule-compiler/store.js +45 -1
- package/src/posture/rule-pack-signing.js +4 -2
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export const id = 5099;
|
|
2
|
+
export const ids = [5099];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 25099:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ findSiblings: () => (/* binding */ findSiblings)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
|
|
12
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
|
|
13
|
+
/* harmony import */ var _rule_compiler_proposal_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(47838);
|
|
14
|
+
/* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(31174);
|
|
15
|
+
// Sibling-exposure search for a verified fix (PRD 1, requirement 6, step 8: "look for equivalent sibling exposures").
|
|
16
|
+
//
|
|
17
|
+
// A fix that closes one instance of a flaw says nothing about the same shape written somewhere else. This finds candidates two ways,
|
|
18
|
+
// and reports where each came from:
|
|
19
|
+
// * `scan` other findings from the last scan with the same weakness or title (what the engine already believes);
|
|
20
|
+
// * `structural` other places in the repository whose line has the SAME SHAPE as the vulnerable line: the vulnerable line is
|
|
21
|
+
// generalized (call names kept, strings and other identifiers become classes) by the rule compiler's own
|
|
22
|
+
// generalizer and searched across the source files. This finds copies the engine did not flag.
|
|
23
|
+
// The result is a list of CANDIDATES for a person to look at, not a verdict that each one is exploitable. Bounded in files, bytes,
|
|
24
|
+
// matches and time; a bound that is hit is reported (`truncated`), because "no siblings found" must never mean "stopped looking".
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
const SOURCE_EXT = /\.(?:js|mjs|cjs|jsx|ts|tsx|py|rb|php|java|kt|go|rs|cs|c|cc|cpp|h|hpp|sh|swift|scala)$/i;
|
|
31
|
+
const SKIP = new Set(['.git', 'node_modules', 'dist', 'build', 'vendor', _state_dir_js__WEBPACK_IMPORTED_MODULE_3__/* .STATE_DIR_NAME */ .Ky, 'coverage', '__pycache__']);
|
|
32
|
+
const SIBLING_LIMITS = Object.freeze({ maxFiles: 5000, maxFileBytes: 512 * 1024, maxMatches: 100, budgetMs: 8000 });
|
|
33
|
+
|
|
34
|
+
const sameSpot = (a, b) => node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(a) === node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(b);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} o
|
|
38
|
+
* @param {string} o.root repository root
|
|
39
|
+
* @param {{file: string, line: number, id?: string, cwe?: string, vuln?: string}} o.finding
|
|
40
|
+
* @param {object[]} [o.scanFindings] findings from the last scan
|
|
41
|
+
* @returns {{siblings: object[], truncated: string[], pattern: string|null}}
|
|
42
|
+
*/
|
|
43
|
+
function findSiblings({ root, finding, scanFindings = [], limits = SIBLING_LIMITS, now = Date.now }) {
|
|
44
|
+
const out = []; const seen = new Set(); const truncated = [];
|
|
45
|
+
const add = (s) => { const k = `${s.file}:${s.line ?? ''}`; if (!seen.has(k)) { seen.add(k); out.push(s); } };
|
|
46
|
+
for (const x of scanFindings) {
|
|
47
|
+
if (!x || x.id === finding.id || x.stableId === finding.stableId && finding.stableId) continue;
|
|
48
|
+
if (x.file === finding.file && x.line === finding.line) continue;
|
|
49
|
+
if ((finding.cwe && x.cwe === finding.cwe) || (finding.vuln && x.vuln === finding.vuln)) add({ source: 'scan', file: x.file, line: x.line ?? null, id: x.id ?? null });
|
|
50
|
+
}
|
|
51
|
+
let pattern = null;
|
|
52
|
+
let line = '';
|
|
53
|
+
try {
|
|
54
|
+
const abs = node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(root, finding.file);
|
|
55
|
+
line = (node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(abs, 'utf8').split('\n')[finding.line - 1] ?? '').trim();
|
|
56
|
+
} catch { /* the flagged file is gone; only the scan-based siblings are available */ }
|
|
57
|
+
if (line.length >= 8) {
|
|
58
|
+
pattern = (0,_rule_compiler_proposal_js__WEBPACK_IMPORTED_MODULE_2__/* .generalizeLine */ .Ry)(line);
|
|
59
|
+
let rx = null;
|
|
60
|
+
try { rx = new RegExp(pattern); } catch { rx = null; }
|
|
61
|
+
if (rx) {
|
|
62
|
+
const deadline = now() + limits.budgetMs; let files = 0; let matches = 0;
|
|
63
|
+
const base = node_fs__WEBPACK_IMPORTED_MODULE_0__.realpathSync(root);
|
|
64
|
+
const walk = (dir) => {
|
|
65
|
+
if (truncated.length) return;
|
|
66
|
+
let ents = [];
|
|
67
|
+
try { ents = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name)); } catch { return; }
|
|
68
|
+
for (const e of ents) {
|
|
69
|
+
if (truncated.length) return;
|
|
70
|
+
if (SKIP.has(e.name) || e.isSymbolicLink()) continue;
|
|
71
|
+
const p = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, e.name);
|
|
72
|
+
if (e.isDirectory()) { walk(p); continue; }
|
|
73
|
+
if (!e.isFile() || !SOURCE_EXT.test(e.name)) continue;
|
|
74
|
+
if (++files > limits.maxFiles) { truncated.push(`file limit (${limits.maxFiles})`); return; }
|
|
75
|
+
if (now() > deadline) { truncated.push('time budget'); return; }
|
|
76
|
+
let text; try { const st = node_fs__WEBPACK_IMPORTED_MODULE_0__.statSync(p); if (st.size > limits.maxFileBytes) continue; text = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(p, 'utf8'); } catch { continue; }
|
|
77
|
+
const lines = text.split('\n');
|
|
78
|
+
for (let i = 0; i < lines.length; i++) {
|
|
79
|
+
if (lines[i].length > 2000 || !rx.test(lines[i].trim())) continue;
|
|
80
|
+
const rel = node_path__WEBPACK_IMPORTED_MODULE_1__.relative(base, node_fs__WEBPACK_IMPORTED_MODULE_0__.realpathSync(p));
|
|
81
|
+
if (sameSpot(node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(base, rel), node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(base, finding.file)) && i + 1 === finding.line) continue;
|
|
82
|
+
add({ source: 'structural', file: rel, line: i + 1, text: lines[i].trim().slice(0, 160) });
|
|
83
|
+
if (++matches >= limits.maxMatches) { truncated.push(`match limit (${limits.maxMatches})`); return; }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
walk(base);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { siblings: out, truncated, pattern };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
/***/ })
|
|
95
|
+
|
|
96
|
+
};
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
export const id = 5456;
|
|
2
|
+
export const ids = [5456,2199,932,8551];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 35456:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ exploitCorrelationOf: () => (/* binding */ exploitCorrelationOf)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* harmony import */ var _proof_fabric_store_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(62199);
|
|
12
|
+
/* harmony import */ var _proof_fabric_states_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12090);
|
|
13
|
+
/* harmony import */ var _store_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(58551);
|
|
14
|
+
// The finding-level form of the runtime twin's RUNTIME_EXPLOIT_CORRELATED (PRD 2): a finding is exploit-correlated when three independent
|
|
15
|
+
// pieces of evidence, each already recorded by its own subsystem, agree:
|
|
16
|
+
// 1. an execution-backed proof (EXPLOIT_CONFIRMED or beyond), so the flaw is real and exploitable;
|
|
17
|
+
// 2. a historical hunt that OBSERVED exploit-shaped requests in this installation's own logs;
|
|
18
|
+
// 3. (checked by the caller) a runtime trace showing the vulnerable function, line or route was exercised.
|
|
19
|
+
// The graph is destination-shaped and carries no finding locations, so this correlation lives on the FINDING, where all three records
|
|
20
|
+
// are keyed by the same id. A tampered record is never used, and a missing one is "not established", not "no".
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
function exploitCorrelationOf(root, findingId) {
|
|
26
|
+
const proofs = (0,_proof_fabric_store_js__WEBPACK_IMPORTED_MODULE_0__.listProofs)(root, findingId);
|
|
27
|
+
const proof = proofs.find((p) => p.record && !p.tampered)?.record ?? null;
|
|
28
|
+
const confirmed = !!proof && (0,_proof_fabric_states_js__WEBPACK_IMPORTED_MODULE_1__/* .stateRank */ .g3)(proof.state) >= (0,_proof_fabric_states_js__WEBPACK_IMPORTED_MODULE_1__/* .stateRank */ .g3)('EXPLOIT_CONFIRMED');
|
|
29
|
+
const hunts = (0,_store_js__WEBPACK_IMPORTED_MODULE_2__.listRecords)(root, findingId, 'hunt').filter((r) => r.record && !r.tampered).map((r) => r.record);
|
|
30
|
+
const observed = hunts.find((h) => h.verdict === 'OBSERVED') ?? null;
|
|
31
|
+
const correlated = confirmed && !!observed;
|
|
32
|
+
return {
|
|
33
|
+
correlated,
|
|
34
|
+
proof: proof ? { state: proof.state, confirmed } : null,
|
|
35
|
+
hunt: observed ? { verdict: 'OBSERVED', window: observed.window ?? null, matches: observed.matches?.count ?? null } : (hunts[0] ? { verdict: hunts[0].verdict } : null),
|
|
36
|
+
basis: correlated ? 'a confirmed exploit proof and exploit-shaped requests observed in historical logs' : (!proof ? 'no proof record' : !confirmed ? `the proof reached ${proof.state}, not a confirmed exploit` : !hunts.length ? 'no historical hunt has been run' : `the hunt verdict was ${hunts[0].verdict}, which is not an observation`),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
/***/ }),
|
|
42
|
+
|
|
43
|
+
/***/ 58551:
|
|
44
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
45
|
+
|
|
46
|
+
__webpack_require__.r(__webpack_exports__);
|
|
47
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
48
|
+
/* harmony export */ hasTamperedRecords: () => (/* binding */ hasTamperedRecords),
|
|
49
|
+
/* harmony export */ latestRecord: () => (/* binding */ latestRecord),
|
|
50
|
+
/* harmony export */ listRecords: () => (/* binding */ listRecords),
|
|
51
|
+
/* harmony export */ saveRecord: () => (/* binding */ saveRecord)
|
|
52
|
+
/* harmony export */ });
|
|
53
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
|
|
54
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
|
|
55
|
+
/* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31174);
|
|
56
|
+
/* harmony import */ var _encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(39984);
|
|
57
|
+
/* harmony import */ var _proof_fabric_reproducer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(37055);
|
|
58
|
+
/* harmony import */ var _proof_fabric_store_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(62199);
|
|
59
|
+
// Persistence for the detection-and-response loop: `.agentic-security/dnr/<findingKey>/<kind>-<digest>.json`.
|
|
60
|
+
// Same machinery and the same guarantees as the proof store, reused rather than copied where it is exported:
|
|
61
|
+
// safe state-directory writes (no symlinks, no unsafe dirs), the encryption policy, retention as evidence, legal holds, and
|
|
62
|
+
// a recursive secret scrub before anything touches disk. A record that no longer matches its own digest is returned FLAGGED,
|
|
63
|
+
// never trusted and never silently dropped.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
const DIR = 'dnr';
|
|
72
|
+
const MAX_RECORD_BYTES = 1024 * 1024;
|
|
73
|
+
const RECORD_KINDS = Object.freeze(['detection', 'hunt', 'window', 'fix']);
|
|
74
|
+
|
|
75
|
+
const digestOf = (record) => { const { recordDigest: _omit, ...rest } = record; return (0,_proof_fabric_reproducer_js__WEBPACK_IMPORTED_MODULE_4__/* .sha256 */ .sc)((0,_proof_fabric_reproducer_js__WEBPACK_IMPORTED_MODULE_4__/* .canonicalize */ .dw)(rest)); };
|
|
76
|
+
|
|
77
|
+
function saveRecord(root, findingId, kind, record) {
|
|
78
|
+
if (!RECORD_KINDS.includes(kind)) return { ok: false, reason: `unknown record kind: ${kind}` };
|
|
79
|
+
const safe = (0,_proof_fabric_store_js__WEBPACK_IMPORTED_MODULE_5__.scrubDeep)(record);
|
|
80
|
+
safe.schema = `agentic-security/dnr-${kind}-record`;
|
|
81
|
+
safe.kind = kind;
|
|
82
|
+
safe.findingId = String(findingId);
|
|
83
|
+
safe.savedAt = record.savedAt ?? new Date().toISOString();
|
|
84
|
+
safe.recordDigest = digestOf(safe);
|
|
85
|
+
const text = JSON.stringify(safe, null, 2);
|
|
86
|
+
if (Buffer.byteLength(text) > MAX_RECORD_BYTES) return { ok: false, reason: 'record exceeds the size limit' };
|
|
87
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.ensureStateDir)(root)) return { ok: false, reason: 'no safe state directory for this project' };
|
|
88
|
+
const enc = (0,_encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__/* .maybeEncryptForWrite */ .v2)(root, DIR, text);
|
|
89
|
+
if (!enc.ok) return { ok: false, reason: enc.reason };
|
|
90
|
+
const file = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(root, DIR, (0,_proof_fabric_store_js__WEBPACK_IMPORTED_MODULE_5__.findingKey)(findingId), `${kind}-${safe.recordDigest.slice(0, 16)}.json`);
|
|
91
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.safeWriteState)(file, enc.content)) return { ok: false, reason: 'state write refused' };
|
|
92
|
+
return { ok: true, file, digest: safe.recordDigest, encrypted: enc.encrypted };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Newest-first records of one kind for a finding. Tampered records are flagged. */
|
|
96
|
+
function listRecords(root, findingId, kind) {
|
|
97
|
+
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(root, DIR, (0,_proof_fabric_store_js__WEBPACK_IMPORTED_MODULE_5__.findingKey)(findingId));
|
|
98
|
+
let names = [];
|
|
99
|
+
try { names = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(dir).filter((n) => n.startsWith(`${kind}-`) && n.endsWith('.json')); } catch { return []; }
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const n of names) {
|
|
102
|
+
const file = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, n);
|
|
103
|
+
try {
|
|
104
|
+
const rec = JSON.parse((0,_encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__.maybeDecryptForRead)(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(file, 'utf8')));
|
|
105
|
+
out.push({ file, record: rec, tampered: rec.recordDigest !== digestOf(rec), mtime: node_fs__WEBPACK_IMPORTED_MODULE_0__.statSync(file).mtimeMs });
|
|
106
|
+
} catch { out.push({ file, record: null, tampered: true, mtime: 0 }); }
|
|
107
|
+
}
|
|
108
|
+
return out.sort((a, b) => b.mtime - a.mtime);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Newest intact record of a kind, or null. A tampered newest record does NOT fall through silently: the caller can see it via listRecords. */
|
|
112
|
+
function latestRecord(root, findingId, kind) {
|
|
113
|
+
return listRecords(root, findingId, kind).find((r) => r.record && !r.tampered) ?? null;
|
|
114
|
+
}
|
|
115
|
+
const hasTamperedRecords = (root, findingId, kind) => listRecords(root, findingId, kind).some((r) => r.tampered);
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
/***/ }),
|
|
119
|
+
|
|
120
|
+
/***/ 12090:
|
|
121
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
122
|
+
|
|
123
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
124
|
+
/* harmony export */ KF: () => (/* binding */ toProofTier),
|
|
125
|
+
/* harmony export */ g3: () => (/* binding */ stateRank),
|
|
126
|
+
/* harmony export */ jn: () => (/* binding */ fromProofTier),
|
|
127
|
+
/* harmony export */ np: () => (/* binding */ advance)
|
|
128
|
+
/* harmony export */ });
|
|
129
|
+
/* unused harmony exports EVIDENCE_STATES, COMPLETENESS */
|
|
130
|
+
// Evidence state ladder for the Universal Exploit Proof Fabric.
|
|
131
|
+
//
|
|
132
|
+
// Evidence state is SEPARATE from severity and confidence. It extends, never replaces, the vocabulary that already
|
|
133
|
+
// exists: `proof-tier.js` (execution-proven / proof-failed / taint-proven / unproven) and the fix-verify legs
|
|
134
|
+
// (`verifiedFull`, `degradedLegs`). `toProofTier` / `fromProofTier` are the explicit bridges.
|
|
135
|
+
//
|
|
136
|
+
// Transitions are guarded: a state can only be reached from the state directly before it (or by dropping back), so no
|
|
137
|
+
// caller can jump a finding to EXPLOIT_CONFIRMED without passing REPRODUCED, or to FIX_VERIFIED without
|
|
138
|
+
// FIX_REPLAY_BLOCKED. A degraded or incomplete run NEVER advances a state.
|
|
139
|
+
const EVIDENCE_STATES = Object.freeze([
|
|
140
|
+
'CANDIDATE',
|
|
141
|
+
'STATICALLY_CORROBORATED',
|
|
142
|
+
'REPRODUCER_GENERATED',
|
|
143
|
+
'REPRODUCED',
|
|
144
|
+
'EXPLOIT_CONFIRMED',
|
|
145
|
+
'FIX_REPLAY_BLOCKED',
|
|
146
|
+
'FIX_VERIFIED',
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
const stateRank = (s) => EVIDENCE_STATES.indexOf(s);
|
|
150
|
+
|
|
151
|
+
const REQUIRES_PREDECESSOR = Object.freeze(Object.fromEntries(EVIDENCE_STATES.map((s, i) => [s, i === 0 ? null : EVIDENCE_STATES[i - 1]])));
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Advance `from` to `to`. Returns { ok, state, reason }. Moving up requires `to` to be exactly the next rung; moving
|
|
155
|
+
* down or staying is always allowed (evidence can be lost, never invented). Unknown states are rejected.
|
|
156
|
+
*/
|
|
157
|
+
function advance(from, to) {
|
|
158
|
+
if (!EVIDENCE_STATES.includes(from)) return { ok: false, state: 'CANDIDATE', reason: `unknown current state: ${String(from)}` };
|
|
159
|
+
if (!EVIDENCE_STATES.includes(to)) return { ok: false, state: from, reason: `unknown target state: ${String(to)}` };
|
|
160
|
+
if (stateRank(to) <= stateRank(from)) return { ok: true, state: to, reason: null };
|
|
161
|
+
if (REQUIRES_PREDECESSOR[to] !== from) return { ok: false, state: from, reason: `cannot reach ${to} from ${from}: ${REQUIRES_PREDECESSOR[to]} is required first` };
|
|
162
|
+
return { ok: true, state: to, reason: null };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Bridges to the older vocabulary so existing consumers keep working.
|
|
166
|
+
function toProofTier(state) {
|
|
167
|
+
const r = stateRank(state);
|
|
168
|
+
if (r >= stateRank('EXPLOIT_CONFIRMED')) return 'execution-proven';
|
|
169
|
+
if (r >= stateRank('STATICALLY_CORROBORATED')) return 'taint-proven';
|
|
170
|
+
return 'unproven';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function fromProofTier(tier) {
|
|
174
|
+
if (tier === 'taint-proven') return 'STATICALLY_CORROBORATED';
|
|
175
|
+
// execution-proven under the older single-run marker path is one run, not the 3/3 + independent replay the fabric
|
|
176
|
+
// requires, so it maps to REPRODUCED and never to EXPLOIT_CONFIRMED.
|
|
177
|
+
if (tier === 'execution-proven') return 'REPRODUCED';
|
|
178
|
+
return 'CANDIDATE';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Completeness vocabulary shared with fix-verify: FULL only when nothing was skipped.
|
|
182
|
+
const COMPLETENESS = Object.freeze(['FULL', 'DEGRADED', 'INCOMPLETE']);
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
/***/ }),
|
|
186
|
+
|
|
187
|
+
/***/ 62199:
|
|
188
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
189
|
+
|
|
190
|
+
__webpack_require__.r(__webpack_exports__);
|
|
191
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
192
|
+
/* harmony export */ findingKey: () => (/* binding */ findingKey),
|
|
193
|
+
/* harmony export */ latestProof: () => (/* binding */ latestProof),
|
|
194
|
+
/* harmony export */ listProofs: () => (/* binding */ listProofs),
|
|
195
|
+
/* harmony export */ saveProof: () => (/* binding */ saveProof),
|
|
196
|
+
/* harmony export */ scrubDeep: () => (/* binding */ scrubDeep)
|
|
197
|
+
/* harmony export */ });
|
|
198
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
|
|
199
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
|
|
200
|
+
/* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31174);
|
|
201
|
+
/* harmony import */ var _encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(39984);
|
|
202
|
+
/* harmony import */ var _llm_validator_redact_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(65388);
|
|
203
|
+
/* harmony import */ var _reproducer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(37055);
|
|
204
|
+
// Proof artifact store: `.agentic-security/proofs/<findingKey>/<recordDigest>.json`.
|
|
205
|
+
//
|
|
206
|
+
// Reuses the existing state machinery rather than adding another: `safeWriteState` (refuses unsafe dirs and symlinks),
|
|
207
|
+
// `maybeEncryptForWrite` / `maybeDecryptForRead` (the `proofs` artifact is registered `confidential`), retention
|
|
208
|
+
// (evidence class) and legal holds (a hold on `proofs` blocks purge). Nothing persisted contains a plaintext credential:
|
|
209
|
+
// records are passed through a recursive secret filter first, and the runner never returns its per-run nonce or secrets.
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
const PROOF_RECORD_SCHEMA = 'agentic-security/proof-record';
|
|
218
|
+
const PROOFS_DIR = 'proofs';
|
|
219
|
+
const MAX_RECORD_BYTES = 512 * 1024;
|
|
220
|
+
|
|
221
|
+
const findingKey = (id) => `${String(id).replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 80)}-${(0,_reproducer_js__WEBPACK_IMPORTED_MODULE_5__/* .sha256 */ .sc)(String(id)).slice(0, 8)}`;
|
|
222
|
+
|
|
223
|
+
// The shared redactor catches key=value shapes; bare tokens need their own pass, so persisted evidence cannot carry one.
|
|
224
|
+
const BARE_SECRET_RES = [
|
|
225
|
+
/\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}\b/g, /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, /\bsk-[A-Za-z0-9_-]{20,}\b/g,
|
|
226
|
+
/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g, /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
|
|
227
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, /\bSECRET-[0-9a-f]{24}-[a-z0-9_-]+\b/g,
|
|
228
|
+
];
|
|
229
|
+
function scrubString(text) {
|
|
230
|
+
let t = String(text);
|
|
231
|
+
try { t = (0,_llm_validator_redact_js__WEBPACK_IMPORTED_MODULE_4__/* .redactSecrets */ .f)(t).text; } catch { return '[unredactable]'; }
|
|
232
|
+
for (const re of BARE_SECRET_RES) t = t.replace(re, '[REDACTED-SECRET]');
|
|
233
|
+
return t;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Deep-copy `v` with every string passed through the secret redactor. */
|
|
237
|
+
function scrubDeep(v) {
|
|
238
|
+
if (typeof v === 'string') return scrubString(v);
|
|
239
|
+
if (Array.isArray(v)) return v.map(scrubDeep);
|
|
240
|
+
if (v && typeof v === 'object') return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, scrubDeep(x)]));
|
|
241
|
+
return v;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function recordDigest(record) {
|
|
245
|
+
const { recordDigest: _omit, ...rest } = record;
|
|
246
|
+
return (0,_reproducer_js__WEBPACK_IMPORTED_MODULE_5__/* .sha256 */ .sc)((0,_reproducer_js__WEBPACK_IMPORTED_MODULE_5__/* .canonicalize */ .dw)(rest));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function saveProof(scanRoot, record) {
|
|
250
|
+
const safe = scrubDeep(record);
|
|
251
|
+
safe.schema = PROOF_RECORD_SCHEMA;
|
|
252
|
+
safe.recordDigest = recordDigest(safe);
|
|
253
|
+
const text = JSON.stringify(safe, null, 2);
|
|
254
|
+
if (Buffer.byteLength(text) > MAX_RECORD_BYTES) return { ok: false, reason: 'proof record exceeds the size limit' };
|
|
255
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.ensureStateDir)(scanRoot)) return { ok: false, reason: 'no safe state directory for this project' };
|
|
256
|
+
const enc = (0,_encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__/* .maybeEncryptForWrite */ .v2)(scanRoot, PROOFS_DIR, text);
|
|
257
|
+
if (!enc.ok) return { ok: false, reason: enc.reason };
|
|
258
|
+
const file = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, PROOFS_DIR, findingKey(record.findingId), `${safe.recordDigest}.json`);
|
|
259
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.safeWriteState)(file, enc.content)) return { ok: false, reason: 'state write refused' };
|
|
260
|
+
return { ok: true, file, digest: safe.recordDigest, encrypted: enc.encrypted };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function readRecord(file) {
|
|
264
|
+
const text = (0,_encryption_provider_js__WEBPACK_IMPORTED_MODULE_3__.maybeDecryptForRead)(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(file, 'utf8'));
|
|
265
|
+
const rec = JSON.parse(text);
|
|
266
|
+
// A record whose content no longer matches its own digest was modified after it was written.
|
|
267
|
+
if (rec.recordDigest !== recordDigest(rec)) return { tampered: true, record: rec };
|
|
268
|
+
return { tampered: false, record: rec };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Newest-first records for a finding. Tampered records are returned flagged, never silently dropped or trusted. */
|
|
272
|
+
function listProofs(scanRoot, findingId) {
|
|
273
|
+
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, PROOFS_DIR, findingKey(findingId));
|
|
274
|
+
let names = [];
|
|
275
|
+
try { names = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(dir).filter((n) => n.endsWith('.json')); } catch { return []; }
|
|
276
|
+
const out = [];
|
|
277
|
+
for (const n of names) {
|
|
278
|
+
const f = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, n);
|
|
279
|
+
try { const { tampered, record } = readRecord(f); out.push({ file: f, tampered, record, mtime: node_fs__WEBPACK_IMPORTED_MODULE_0__.statSync(f).mtimeMs }); } catch { out.push({ file: f, tampered: true, record: null, mtime: 0 }); }
|
|
280
|
+
}
|
|
281
|
+
return out.sort((a, b) => b.mtime - a.mtime);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function latestProof(scanRoot, findingId) {
|
|
285
|
+
return listProofs(scanRoot, findingId).find((p) => p.record) ?? null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
/***/ })
|
|
290
|
+
|
|
291
|
+
};
|