@clear-capabilities/agentic-security-scanner 0.160.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 +38 -0
- package/bin/agentic-security.js +11 -2
- package/dist/1919.index.js +1 -1
- package/dist/2432.index.js +10 -1
- package/dist/4113.index.js +60 -9
- package/dist/5613.index.js +500 -0
- package/dist/6239.index.js +469 -0
- package/dist/7033.index.js +4 -1
- package/dist/7838.index.js +39 -3
- package/dist/8714.index.js +1 -1
- package/dist/8798.index.js +2 -2
- package/dist/9207.index.js +5 -0
- package/dist/9560.index.js +205 -62
- package/dist/{1905.index.js → 9999.index.js} +265 -8
- package/dist/agentic-security.mjs +12 -12
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +14 -9
- package/src/dataflow/engine.js +20 -4
- package/src/discovery/CLAUDE.md +9 -0
- package/src/discovery/index.js +28 -0
- package/src/discovery/memory.js +7 -5
- package/src/engine.js +29 -8
- package/src/fix/apply-fix-service.js +33 -3
- package/src/language/CLAUDE.md +6 -1
- package/src/language/aibom.js +4 -1
- package/src/language/fix-lifecycle.js +9 -0
- package/src/language/haskell-ir.js +47 -7
- package/src/language/haskell-sca.js +87 -7
- package/src/language/haskell-supply.js +175 -6
- package/src/language/haskell-web.js +200 -18
- package/src/language/nix-fix.js +1 -1
- package/src/language/resolved-pass.js +4 -1
- package/src/language/witness.js +1 -1
- package/src/mcp/CLAUDE.md +43 -4
- package/src/mcp/tools.js +185 -43
- 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/blast-radius.js +15 -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/remediation-ledger.js +10 -1
- 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/src/sast/pipeline.js +51 -4
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
export const id = 6239;
|
|
2
|
+
export const ids = [6239];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 56239:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
// ESM COMPAT FLAG
|
|
9
|
+
__webpack_require__.r(__webpack_exports__);
|
|
10
|
+
|
|
11
|
+
// EXPORTS
|
|
12
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
13
|
+
MANIFEST_SCHEMA: () => (/* binding */ MANIFEST_SCHEMA),
|
|
14
|
+
getOracle: () => (/* binding */ getOracle),
|
|
15
|
+
listOracles: () => (/* binding */ listOracles),
|
|
16
|
+
manifestEntry: () => (/* binding */ manifestEntry),
|
|
17
|
+
oracleManifest: () => (/* binding */ oracleManifest)
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// EXTERNAL MODULE: ./src/posture/oracles/oracle.js + 4 modules
|
|
21
|
+
var oracle = __webpack_require__(80219);
|
|
22
|
+
// EXTERNAL MODULE: ./src/posture/assurance/identity.js
|
|
23
|
+
var identity = __webpack_require__(41877);
|
|
24
|
+
;// CONCATENATED MODULE: ./src/posture/oracles/adapters.js
|
|
25
|
+
// The five class-specific oracle adapters (X-202.AC01).
|
|
26
|
+
//
|
|
27
|
+
// Each adapter is data plus three small functions, all verifier-authored:
|
|
28
|
+
// harnessSource the script the boundary runs. It imports the target, drives it
|
|
29
|
+
// with a benign CONTROL input first and the hostile input second,
|
|
30
|
+
// and writes a result document. It never decides a verdict.
|
|
31
|
+
// prepare builds the harness input from the request inputs plus a
|
|
32
|
+
// per-run token the target cannot know in advance.
|
|
33
|
+
// interpret forms `{ satisfied, preconditionsHeld, observed, reason }` from
|
|
34
|
+
// what the run left behind, read by the verifier after the whole
|
|
35
|
+
// process tree is dead. `satisfied: true` means the hypothesised
|
|
36
|
+
// effect was observed; `false` with `preconditionsHeld: true`
|
|
37
|
+
// means it was not observed although the control proved the
|
|
38
|
+
// target was exercised (a refutation); anything else leaves the
|
|
39
|
+
// verdict open.
|
|
40
|
+
//
|
|
41
|
+
// Changing what an adapter asserts means bumping its `version`: the version and
|
|
42
|
+
// the harness text feed `logicDigest`, which replay manifests pin.
|
|
43
|
+
//
|
|
44
|
+
// Platform statements are about what this repository's tests have executed. The
|
|
45
|
+
// development host is macOS, so Linux is declared `unverified` everywhere: on
|
|
46
|
+
// Linux the boundary currently refuses to run (read denial and supervised
|
|
47
|
+
// tree termination are not implemented on the namespace backend), which the
|
|
48
|
+
// runner reports as `unsupported`, and no Linux outcome is claimed.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
const PLATFORMS = Object.freeze({
|
|
53
|
+
darwin: { status: 'supported', note: 'executed by this repository\'s tests on the macOS development host (userspace confinement backend)' },
|
|
54
|
+
linux: { status: 'unverified', note: 'no Linux outcome is claimed: the boundary refuses to run on the namespace backend until read denial and supervised tree termination are implemented and executed there; the run reports unsupported' },
|
|
55
|
+
win32: { status: 'unsupported', note: 'no confinement backend exists for Windows' },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const COMMON_BUDGETS = Object.freeze({ timeoutMs: 8000, graceMs: 1000, maxOutputBytes: 65536, maxFileBytes: 262144, maxFiles: 32 });
|
|
59
|
+
|
|
60
|
+
const PROLOGUE = `import fs from 'node:fs';
|
|
61
|
+
const INPUT = __INPUT__;
|
|
62
|
+
const ENTRY = __ENTRY__;
|
|
63
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
64
|
+
const text = (e) => String((e && e.message) || e).slice(0, 160);
|
|
65
|
+
const out = {};
|
|
66
|
+
function finish(extra) {
|
|
67
|
+
Object.assign(out, extra);
|
|
68
|
+
try { fs.writeFileSync('__oracle_result.json', JSON.stringify(out)); } catch {}
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
let mod;
|
|
72
|
+
try { mod = await import('./' + ENTRY); } catch (e) { finish({ loadFailed: true }); }
|
|
73
|
+
const fn = mod && mod[INPUT.export];
|
|
74
|
+
if (typeof fn !== 'function') finish({ loadFailed: true });
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const isId = (s) => typeof s === 'string' && /^[A-Za-z_$][\w$]{0,63}$/.test(s);
|
|
78
|
+
const isStr = (s, max = 2000) => typeof s === 'string' && s.length > 0 && s.length <= max;
|
|
79
|
+
|
|
80
|
+
// ------------------------------------------------------------------ injection execution
|
|
81
|
+
|
|
82
|
+
const injection = (0,oracle/* defineOracle */.IL)({
|
|
83
|
+
id: 'injection-execution', class: 'injection-execution', version: '1',
|
|
84
|
+
description: 'Does attacker-controlled input reach a command interpreter and run? The harness calls the target with a benign control, then with a payload whose effect is a token-named file; the verifier looks for that file after the process tree is dead.',
|
|
85
|
+
prerequisites: ['node-runtime', 'confinement-backend', 'posix-shell'],
|
|
86
|
+
platforms: PLATFORMS, budgets: COMMON_BUDGETS,
|
|
87
|
+
negativeControls: [{ id: 'argv-array-call', description: 'the same handler passing the input as a separate argv element (no shell) must not produce the effect', expectedOutcome: 'refuted' }],
|
|
88
|
+
fixtures: { dir: 'test/fixtures/oracles/injection-execution', positive: 'positive', negative: 'negative', inconclusive: 'inconclusive' },
|
|
89
|
+
limitations: ['covers command injection through a shell only; SQL, template and code injection are not asserted here', 'the target is imported into the harness process, so it runs with the harness; a target written to recognise the payload and forge the effect file is not distinguishable from a real injection', 'asynchronous handlers that spawn without being awaited are given settleMs to finish, not guaranteed to'],
|
|
90
|
+
harnessSource: `${PROLOGUE}
|
|
91
|
+
try { await Promise.resolve(fn(INPUT.benign)); out.controlOk = true; } catch (e) { out.controlOk = false; }
|
|
92
|
+
if (out.controlOk) {
|
|
93
|
+
try { await Promise.resolve(fn(INPUT.attack)); out.attackReturned = true; } catch (e) { out.attackReturned = false; }
|
|
94
|
+
await sleep(INPUT.settleMs);
|
|
95
|
+
}
|
|
96
|
+
finish({});
|
|
97
|
+
`,
|
|
98
|
+
validateInputs(i) {
|
|
99
|
+
if (!isId(i.export)) return 'export must be an identifier';
|
|
100
|
+
if (!isStr(i.benign, 500)) return 'benign must be a short string';
|
|
101
|
+
if (!isStr(i.attackTemplate, 500) || !i.attackTemplate.includes('{TOKEN}')) return 'attackTemplate must be a short string containing {TOKEN}';
|
|
102
|
+
if (i.settleMs !== undefined && !(Number.isInteger(i.settleMs) && i.settleMs >= 0 && i.settleMs <= 2000)) return 'settleMs must be 0 to 2000';
|
|
103
|
+
return null;
|
|
104
|
+
},
|
|
105
|
+
prepare: (i, token) => ({ export: i.export, benign: i.benign, attack: i.attackTemplate.split('{TOKEN}').join(token), settleMs: i.settleMs ?? 300 }),
|
|
106
|
+
interpret({ token, result, read }) {
|
|
107
|
+
const effect = read(`effect-${token}`, 256) === token;
|
|
108
|
+
const controlOk = result?.controlOk === true;
|
|
109
|
+
const observed = { effectObserved: effect, controlCompleted: controlOk, targetLoaded: result ? result.loadFailed !== true : null };
|
|
110
|
+
if (effect) return { satisfied: true, preconditionsHeld: true, observed, reason: 'the injected command ran: its token-named effect file exists' };
|
|
111
|
+
if (controlOk) return { satisfied: false, preconditionsHeld: true, observed, reason: 'the handler ran its benign control and the injected command left no effect' };
|
|
112
|
+
return { satisfied: null, preconditionsHeld: false, observed, reason: result?.loadFailed ? 'the target failed to load, so the payload was never delivered' : 'the benign control did not complete, so the payload was never delivered' };
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ------------------------------------------------------------------ authorization decision
|
|
117
|
+
|
|
118
|
+
const DECISIONS = ['allow', 'deny', 'error', 'indeterminate'];
|
|
119
|
+
|
|
120
|
+
const authorization = (0,oracle/* defineOracle */.IL)({
|
|
121
|
+
id: 'authorization-decision', class: 'authorization-decision', version: '1',
|
|
122
|
+
description: 'Does an authorization function allow an actor it should deny? The harness evaluates declared control cases (the owner, expected allow) and attack cases (another tenant or a lower role, expected deny); the verifier recomputes the verdict from the raw decisions.',
|
|
123
|
+
prerequisites: ['node-runtime', 'confinement-backend'],
|
|
124
|
+
platforms: PLATFORMS, budgets: COMMON_BUDGETS,
|
|
125
|
+
negativeControls: [{ id: 'tenant-checked', description: 'the same function comparing tenant ids must deny the cross-tenant attack case', expectedOutcome: 'refuted' }],
|
|
126
|
+
fixtures: { dir: 'test/fixtures/oracles/authorization-decision', positive: 'positive', negative: 'negative', inconclusive: 'inconclusive' },
|
|
127
|
+
limitations: ['asserts the decision function in isolation; a route or middleware that enforces authorization elsewhere is not exercised', 'the cases describe the scenario; the oracle does not infer which actors should be denied', 'a thrown error is not read as a denial'],
|
|
128
|
+
harnessSource: `${PROLOGUE}
|
|
129
|
+
const norm = (r) => (r === true || r === 'allow' || (r && r.allowed === true)) ? 'allow'
|
|
130
|
+
: (r === false || r === 'deny' || (r && r.allowed === false)) ? 'deny' : 'indeterminate';
|
|
131
|
+
const decisions = {};
|
|
132
|
+
for (const c of INPUT.cases) {
|
|
133
|
+
try { decisions[c.id] = norm(await Promise.resolve(fn(...c.args))); } catch (e) { decisions[c.id] = 'error'; }
|
|
134
|
+
}
|
|
135
|
+
finish({ decisions });
|
|
136
|
+
`,
|
|
137
|
+
validateInputs(i) {
|
|
138
|
+
if (!isId(i.export)) return 'export must be an identifier';
|
|
139
|
+
if (!Array.isArray(i.cases) || i.cases.length < 2 || i.cases.length > 16) return 'cases must hold 2 to 16 entries';
|
|
140
|
+
const ids = new Set();
|
|
141
|
+
for (const c of i.cases) {
|
|
142
|
+
if (!c || !isId(String(c.id).replace(/-/g, '_')) || ids.has(c.id)) return 'every case needs a unique id';
|
|
143
|
+
ids.add(c.id);
|
|
144
|
+
if (!['control', 'attack'].includes(c.role)) return 'case role must be control or attack';
|
|
145
|
+
if (!Array.isArray(c.args) || c.args.length > 6) return 'case args must be an array of at most 6';
|
|
146
|
+
}
|
|
147
|
+
if (!i.cases.some((c) => c.role === 'control') || !i.cases.some((c) => c.role === 'attack')) return 'at least one control and one attack case are required';
|
|
148
|
+
return null;
|
|
149
|
+
},
|
|
150
|
+
prepare: (i) => ({ export: i.export, cases: i.cases.map((c) => ({ id: c.id, args: c.args })) }),
|
|
151
|
+
interpret({ inputs, result }) {
|
|
152
|
+
const got = result && result.decisions && typeof result.decisions === 'object' ? result.decisions : null;
|
|
153
|
+
if (!got || result.loadFailed) return { satisfied: null, preconditionsHeld: false, observed: { targetLoaded: false }, reason: 'the target did not report decisions' };
|
|
154
|
+
const decisions = {};
|
|
155
|
+
for (const c of inputs.cases) decisions[c.id] = DECISIONS.includes(got[c.id]) ? got[c.id] : 'indeterminate';
|
|
156
|
+
const controls = inputs.cases.filter((c) => c.role === 'control');
|
|
157
|
+
const attacks = inputs.cases.filter((c) => c.role === 'attack');
|
|
158
|
+
const controlsHeld = controls.every((c) => decisions[c.id] === 'allow');
|
|
159
|
+
const allowed = attacks.filter((c) => decisions[c.id] === 'allow').map((c) => c.id);
|
|
160
|
+
const allDenied = attacks.every((c) => decisions[c.id] === 'deny');
|
|
161
|
+
const observed = { decisions, controlsAllowed: controlsHeld, attackCasesAllowed: allowed };
|
|
162
|
+
if (allowed.length) return { satisfied: true, preconditionsHeld: controlsHeld, observed, reason: `the function allowed attack case(s): ${allowed.join(', ')}` };
|
|
163
|
+
if (allDenied && controlsHeld) return { satisfied: false, preconditionsHeld: true, observed, reason: 'every attack case was denied and every control case was allowed' };
|
|
164
|
+
return { satisfied: null, preconditionsHeld: false, observed, reason: controlsHeld ? 'an attack case returned neither allow nor deny' : 'a control case was not allowed, so the function was not shown to be deciding' };
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ------------------------------------------------------------------ state transition
|
|
169
|
+
|
|
170
|
+
const stateTransition = (0,oracle/* defineOracle */.IL)({
|
|
171
|
+
id: 'state-transition', class: 'state-transition', version: '1',
|
|
172
|
+
description: 'Does a state machine accept an event sequence that must be refused? The harness drives a fresh machine through the legal sequence (control) and a fresh one through the attack sequence; the verifier compares the final states.',
|
|
173
|
+
prerequisites: ['node-runtime', 'confinement-backend'],
|
|
174
|
+
platforms: PLATFORMS, budgets: COMMON_BUDGETS,
|
|
175
|
+
negativeControls: [{ id: 'payment-required', description: 'the same machine refusing shipment before payment must not reach the forbidden state', expectedOutcome: 'refuted' }],
|
|
176
|
+
fixtures: { dir: 'test/fixtures/oracles/state-transition', positive: 'positive', negative: 'negative', inconclusive: 'inconclusive' },
|
|
177
|
+
limitations: ['covers one factory and finite event lists; it is not a model checker', 'durable effects outside the machine object (a database row, a queued job) are not observed', 'the forbidden state is stated by the scenario; the oracle does not infer business rules'],
|
|
178
|
+
harnessSource: `${PROLOGUE}
|
|
179
|
+
async function drive(events) {
|
|
180
|
+
const m = await Promise.resolve(fn());
|
|
181
|
+
const accepted = [];
|
|
182
|
+
for (const e of events) {
|
|
183
|
+
try { const r = await Promise.resolve(m.send(e)); accepted.push(r !== false); } catch (err) { accepted.push(false); }
|
|
184
|
+
}
|
|
185
|
+
return { state: String(m.state).slice(0, 80), accepted };
|
|
186
|
+
}
|
|
187
|
+
try { const c = await drive(INPUT.legal); out.controlState = c.state; } catch (e) { out.controlState = null; }
|
|
188
|
+
try { const a = await drive(INPUT.attack); out.attackState = a.state; out.attackAccepted = a.accepted; } catch (e) { out.attackState = null; }
|
|
189
|
+
finish({});
|
|
190
|
+
`,
|
|
191
|
+
validateInputs(i) {
|
|
192
|
+
if (!isId(i.factory)) return 'factory must be an identifier';
|
|
193
|
+
const evs = (a) => Array.isArray(a) && a.length >= 1 && a.length <= 16 && a.every((e) => isStr(e, 64));
|
|
194
|
+
if (!evs(i.legal) || !evs(i.attack)) return 'legal and attack must be lists of 1 to 16 short event names';
|
|
195
|
+
if (!isStr(i.expectedFinal, 64) || !isStr(i.forbiddenState, 64)) return 'expectedFinal and forbiddenState are required';
|
|
196
|
+
return null;
|
|
197
|
+
},
|
|
198
|
+
prepare: (i) => ({ export: i.factory, legal: i.legal, attack: i.attack }),
|
|
199
|
+
interpret({ inputs, result }) {
|
|
200
|
+
if (!result || result.loadFailed) return { satisfied: null, preconditionsHeld: false, observed: { targetLoaded: false }, reason: 'the target failed to load' };
|
|
201
|
+
const controlHeld = result.controlState === inputs.expectedFinal;
|
|
202
|
+
const reached = result.attackState === inputs.forbiddenState;
|
|
203
|
+
const observed = { controlState: result.controlState ?? null, controlReachedExpected: controlHeld, attackState: result.attackState ?? null, attackAccepted: Array.isArray(result.attackAccepted) ? result.attackAccepted.slice(0, 16).map((b) => b === true) : [] };
|
|
204
|
+
if (reached) return { satisfied: true, preconditionsHeld: controlHeld, observed, reason: `the attack sequence reached the forbidden state '${inputs.forbiddenState}'` };
|
|
205
|
+
if (controlHeld) return { satisfied: false, preconditionsHeld: true, observed, reason: 'the legal sequence reached its expected state and the attack sequence did not reach the forbidden one' };
|
|
206
|
+
return { satisfied: null, preconditionsHeld: false, observed, reason: 'the legal sequence did not reach its expected state, so the machine was not shown to be working' };
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// ------------------------------------------------------------------ side-effect reachability
|
|
211
|
+
|
|
212
|
+
const sideEffect = (0,oracle/* defineOracle */.IL)({
|
|
213
|
+
id: 'side-effect-reachability', class: 'side-effect-reachability', version: '1',
|
|
214
|
+
description: 'Does attacker-chosen input reach a privileged side-effecting operation? The harness injects a recording stand-in for the sink, calls the handler with a benign value (control) and an attacker value, and the verifier checks which values reached the sink.',
|
|
215
|
+
prerequisites: ['node-runtime', 'confinement-backend'],
|
|
216
|
+
platforms: PLATFORMS, budgets: COMMON_BUDGETS,
|
|
217
|
+
negativeControls: [{ id: 'allowlisted-target', description: 'the same handler with a destination allowlist must not pass the attacker value to the sink', expectedOutcome: 'refuted' }],
|
|
218
|
+
fixtures: { dir: 'test/fixtures/oracles/side-effect-reachability', positive: 'positive', negative: 'negative', inconclusive: 'inconclusive' },
|
|
219
|
+
limitations: ['the sink is a recording stand-in injected as the handler\'s second argument; handlers that import their sink directly are not covered', 'reaching the sink shows the operation would be invoked with the attacker value, not that the real operation would succeed', 'no network or privileged call is ever made'],
|
|
220
|
+
harnessSource: `${PROLOGUE}
|
|
221
|
+
const calls = [];
|
|
222
|
+
const deps = { [INPUT.sink]: (...args) => { try { calls.push(JSON.stringify(args).slice(0, 600)); } catch {} return Promise.resolve({ ok: true, status: 200, text: async () => '' }); } };
|
|
223
|
+
const reached = (v) => calls.some((c) => c.includes(JSON.stringify(v).slice(1, -1)));
|
|
224
|
+
try { await Promise.resolve(fn(INPUT.benignValue, deps)); } catch (e) {}
|
|
225
|
+
out.benignReached = reached(INPUT.benignValue);
|
|
226
|
+
calls.length = 0;
|
|
227
|
+
try { await Promise.resolve(fn(INPUT.attackerValue, deps)); } catch (e) {}
|
|
228
|
+
out.attackReached = reached(INPUT.attackerValue);
|
|
229
|
+
finish({});
|
|
230
|
+
`,
|
|
231
|
+
validateInputs(i) {
|
|
232
|
+
if (!isId(i.export) || !isId(i.sink)) return 'export and sink must be identifiers';
|
|
233
|
+
if (!isStr(i.benignValue, 500) || !isStr(i.attackerValue, 500) || i.benignValue === i.attackerValue) return 'benignValue and attackerValue must be distinct short strings';
|
|
234
|
+
return null;
|
|
235
|
+
},
|
|
236
|
+
prepare: (i) => ({ export: i.export, sink: i.sink, benignValue: i.benignValue, attackerValue: i.attackerValue }),
|
|
237
|
+
interpret({ result }) {
|
|
238
|
+
if (!result || result.loadFailed) return { satisfied: null, preconditionsHeld: false, observed: { targetLoaded: false }, reason: 'the target failed to load' };
|
|
239
|
+
const benign = result.benignReached === true;
|
|
240
|
+
const attack = result.attackReached === true;
|
|
241
|
+
const observed = { benignReachedSink: benign, attackerValueReachedSink: attack };
|
|
242
|
+
if (attack) return { satisfied: true, preconditionsHeld: benign, observed, reason: 'the attacker-chosen value reached the side-effecting sink' };
|
|
243
|
+
if (benign) return { satisfied: false, preconditionsHeld: true, observed, reason: 'the benign value reached the sink and the attacker value did not' };
|
|
244
|
+
return { satisfied: null, preconditionsHeld: false, observed, reason: 'even the benign value never reached the sink, so the handler was not shown to use it' };
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ------------------------------------------------------------------ parser / resource behaviour
|
|
249
|
+
|
|
250
|
+
const parserResource = (0,oracle/* defineOracle */.IL)({
|
|
251
|
+
id: 'parser-resource', class: 'parser-resource', version: '1',
|
|
252
|
+
description: 'Does a parser or matcher exhaust its budget on a bounded hostile input? The harness completes a benign control (marker file), then feeds the hostile input; the verifier\'s own supervisor deadline is the measurement, so no self-reported timing is trusted.',
|
|
253
|
+
prerequisites: ['node-runtime', 'confinement-backend'],
|
|
254
|
+
platforms: PLATFORMS, budgets: { ...COMMON_BUDGETS, timeoutMs: 5000 },
|
|
255
|
+
negativeControls: [{ id: 'linear-pattern', description: 'the same matcher written without nested quantifiers must finish the hostile input inside the budget', expectedOutcome: 'refuted' }],
|
|
256
|
+
fixtures: { dir: 'test/fixtures/oracles/parser-resource', positive: 'positive', negative: 'negative', inconclusive: 'inconclusive' },
|
|
257
|
+
limitations: ['the hostile input is bounded (at most 64 KiB) and the run is cut at the supervisor deadline; this shows a budget was exceeded, not how large the blow-up is', 'a hard crash without a deadline or output-cap hit is left inconclusive rather than called exhaustion', 'memory is not measured: address-space limits are not enforceable on the macOS backend'],
|
|
258
|
+
harnessSource: `${PROLOGUE}
|
|
259
|
+
const mark = (name) => { try { fs.writeFileSync(name, INPUT.token); } catch {} };
|
|
260
|
+
try { await Promise.resolve(fn(INPUT.benign)); mark('__oracle_benign_done'); } catch (e) { finish({ benignFailed: true }); }
|
|
261
|
+
try { await Promise.resolve(fn(INPUT.hostile)); } catch (e) {}
|
|
262
|
+
mark('__oracle_hostile_done');
|
|
263
|
+
finish({});
|
|
264
|
+
`,
|
|
265
|
+
validateInputs(i) {
|
|
266
|
+
if (!isId(i.export)) return 'export must be an identifier';
|
|
267
|
+
if (!isStr(i.benign, 500)) return 'benign must be a short string';
|
|
268
|
+
const h = i.hostile;
|
|
269
|
+
if (!h || !isStr(h.unit, 16) || !Number.isInteger(h.count) || h.count < 1 || h.count > 65536 || (h.suffix !== undefined && !isStr(h.suffix, 16))) return 'hostile needs unit (short), count (1 to 65536) and an optional short suffix';
|
|
270
|
+
return null;
|
|
271
|
+
},
|
|
272
|
+
prepare: (i, token) => ({ export: i.export, benign: i.benign, hostile: i.hostile.unit.repeat(i.hostile.count) + (i.hostile.suffix || ''), token }),
|
|
273
|
+
interpret({ token, run, read }) {
|
|
274
|
+
const benignDone = read('__oracle_benign_done', 128) === token;
|
|
275
|
+
const hostileDone = read('__oracle_hostile_done', 128) === token;
|
|
276
|
+
const observed = { benignCompleted: benignDone, hostileCompleted: hostileDone, deadlineHit: run.timedOut, outputCapHit: run.outputCapped };
|
|
277
|
+
if (!benignDone) return { satisfied: null, preconditionsHeld: false, observed, reason: 'the benign control did not complete, so the target was not shown to be working' };
|
|
278
|
+
if (hostileDone) return { satisfied: false, preconditionsHeld: true, observed, reason: 'the hostile input was handled inside the budget' };
|
|
279
|
+
if (run.timedOut || run.outputCapped) return { satisfied: true, preconditionsHeld: true, observed, reason: run.timedOut ? 'the hostile input exhausted the time budget (the supervisor deadline fired)' : 'the hostile input exhausted the output budget' };
|
|
280
|
+
return { satisfied: null, preconditionsHeld: true, observed, reason: 'the run ended without finishing the hostile input and without hitting a budget, so the cause is unknown' };
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// ------------------------------------------------------------------ replay / idempotency (X-205)
|
|
285
|
+
|
|
286
|
+
const replayIdempotency = (0,oracle/* defineOracle */.IL)({
|
|
287
|
+
id: 'replay-idempotency', class: 'replay-idempotency', version: '1',
|
|
288
|
+
description: 'Does delivering the same request again repeat its side effect? The harness builds a fresh handler around a recording stand-in for the effect, delivers a request and then a DISTINCT request (control: one effect each), then on a second fresh handler delivers the same request repeatedly; the verifier counts the effects.',
|
|
289
|
+
prerequisites: ['node-runtime', 'confinement-backend'],
|
|
290
|
+
platforms: PLATFORMS, budgets: COMMON_BUDGETS,
|
|
291
|
+
negativeControls: [{ id: 'idempotency-key-honoured', description: 'the same handler remembering the idempotency key must produce exactly one effect for the repeated request', expectedOutcome: 'refuted' }],
|
|
292
|
+
fixtures: { dir: 'test/fixtures/oracles/replay-idempotency', positive: 'positive', negative: 'negative', inconclusive: 'inconclusive' },
|
|
293
|
+
limitations: ['the target is a factory that receives a recording stand-in for its effect and returns a handler; code that imports its effect directly is not covered', 'deliveries are sequential: a race between concurrent deliveries is not exercised', 'durable deduplication in an external store is not observed, only the handler instance the factory returns', 'reaching the effect twice shows the handler would repeat it, not that the real operation would succeed'],
|
|
294
|
+
harnessSource: `${PROLOGUE}
|
|
295
|
+
const mk = () => { const calls = []; return { calls, deps: { [INPUT.sink]: (...a) => { calls.push(1); return Promise.resolve({ ok: true }); } } }; };
|
|
296
|
+
async function build() { const m = mk(); const h = await Promise.resolve(fn(m.deps)); if (typeof h !== 'function') throw new Error('not a handler'); return { m, h }; }
|
|
297
|
+
try {
|
|
298
|
+
const c = await build();
|
|
299
|
+
try { await Promise.resolve(c.h(INPUT.request)); } catch (e) {}
|
|
300
|
+
out.firstEffects = c.m.calls.length;
|
|
301
|
+
try { await Promise.resolve(c.h(INPUT.distinctRequest)); } catch (e) {}
|
|
302
|
+
out.distinctEffects = c.m.calls.length;
|
|
303
|
+
} catch (e) { out.controlFailed = true; }
|
|
304
|
+
try {
|
|
305
|
+
const a = await build();
|
|
306
|
+
for (let i = 0; i < INPUT.deliveries; i++) { try { await Promise.resolve(a.h(INPUT.request)); } catch (e) {} }
|
|
307
|
+
out.replayEffects = a.m.calls.length;
|
|
308
|
+
} catch (e) { out.replayFailed = true; }
|
|
309
|
+
finish({});
|
|
310
|
+
`,
|
|
311
|
+
validateInputs(i) {
|
|
312
|
+
if (!isId(i.export) || !isId(i.sink)) return 'export and sink must be identifiers';
|
|
313
|
+
const small = (v) => { try { const s = JSON.stringify(v); return typeof s === 'string' && s.length <= 2000 && v !== null && typeof v === 'object' && !Array.isArray(v); } catch { return false; } };
|
|
314
|
+
if (!small(i.request) || !small(i.distinctRequest)) return 'request and distinctRequest must be small JSON objects';
|
|
315
|
+
if ((0,identity/* digestOf */.ol)(i.request) === (0,identity/* digestOf */.ol)(i.distinctRequest)) return 'distinctRequest must differ from request';
|
|
316
|
+
if (!(Number.isInteger(i.deliveries) && i.deliveries >= 2 && i.deliveries <= 6)) return 'deliveries must be 2 to 6';
|
|
317
|
+
return null;
|
|
318
|
+
},
|
|
319
|
+
prepare: (i) => ({ export: i.export, sink: i.sink, request: i.request, distinctRequest: i.distinctRequest, deliveries: i.deliveries }),
|
|
320
|
+
interpret({ result }) {
|
|
321
|
+
if (!result || result.loadFailed) return { satisfied: null, preconditionsHeld: false, observed: { targetLoaded: false }, reason: 'the target failed to load' };
|
|
322
|
+
const first = Number.isInteger(result.firstEffects) ? result.firstEffects : null;
|
|
323
|
+
const distinct = Number.isInteger(result.distinctEffects) ? result.distinctEffects : null;
|
|
324
|
+
const replay = Number.isInteger(result.replayEffects) ? result.replayEffects : null;
|
|
325
|
+
const controlHeld = first === 1 && distinct === 2;
|
|
326
|
+
const observed = { targetLoaded: true, firstDeliveryEffects: first, distinctDeliveryTotalEffects: distinct, repeatedDeliveryTotalEffects: replay, controlHeld };
|
|
327
|
+
if (replay !== null && replay > 1) return { satisfied: true, preconditionsHeld: controlHeld, observed, reason: `the same request delivered repeatedly produced ${replay} effects` };
|
|
328
|
+
if (controlHeld && replay === 1) return { satisfied: false, preconditionsHeld: true, observed, reason: 'a distinct request produced its own effect and the repeated request produced exactly one' };
|
|
329
|
+
return { satisfied: null, preconditionsHeld: false, observed, reason: controlHeld ? 'the repeated deliveries did not report' : 'the handler did not produce exactly one effect per first delivery, so it was not shown to be working' };
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// ------------------------------------------------------------------ functional regression (X-204)
|
|
334
|
+
|
|
335
|
+
const functionalRegression = (0,oracle/* defineOracle */.IL)({
|
|
336
|
+
id: 'functional-regression', class: 'functional-regression', version: '1',
|
|
337
|
+
description: 'Does a revision still behave as intended? The harness calls the exported function with declared cases and records each result; the verifier compares them with the declared expectations. The hypothesis is that the behaviour REGRESSED, so a patch is clean when this is refuted.',
|
|
338
|
+
prerequisites: ['node-runtime', 'confinement-backend'],
|
|
339
|
+
platforms: PLATFORMS, budgets: COMMON_BUDGETS,
|
|
340
|
+
negativeControls: [{ id: 'behaviour-preserved', description: 'a revision that returns the expected value for every declared case must show no regression', expectedOutcome: 'refuted' }],
|
|
341
|
+
fixtures: { dir: 'test/fixtures/oracles/functional-regression', positive: 'positive', negative: 'negative', inconclusive: 'inconclusive' },
|
|
342
|
+
limitations: ['only the declared cases are checked: behaviour outside them is not observed, and a patch that breaks an undeclared path is not detected', 'results are compared as JSON values, so identity, ordering of unordered data and timing are not compared', 'the expectations are supplied by the requester and are not inferred; expectations that are wrong for the ORIGINAL revision are reported by the baseline check, not hidden'],
|
|
343
|
+
harnessSource: `${PROLOGUE}
|
|
344
|
+
const results = {};
|
|
345
|
+
for (const c of INPUT.cases) {
|
|
346
|
+
try { const v = await Promise.resolve(fn(...c.args)); results[c.id] = { ok: true, value: v === undefined ? null : JSON.parse(JSON.stringify(v)) }; }
|
|
347
|
+
catch (e) { results[c.id] = { ok: false }; }
|
|
348
|
+
}
|
|
349
|
+
finish({ results });
|
|
350
|
+
`,
|
|
351
|
+
validateInputs(i) {
|
|
352
|
+
if (!isId(i.export)) return 'export must be an identifier';
|
|
353
|
+
if (!Array.isArray(i.cases) || i.cases.length < 1 || i.cases.length > 16) return 'cases must hold 1 to 16 entries';
|
|
354
|
+
const ids = new Set();
|
|
355
|
+
for (const c of i.cases) {
|
|
356
|
+
if (!c || typeof c.id !== 'string' || !/^[a-z0-9-]{1,40}$/.test(c.id) || ids.has(c.id)) return 'every case needs a unique slug id';
|
|
357
|
+
ids.add(c.id);
|
|
358
|
+
if (!Array.isArray(c.args) || c.args.length > 6) return 'case args must be an array of at most 6';
|
|
359
|
+
if (!('expected' in c)) return 'every case needs an expected value';
|
|
360
|
+
try { if (JSON.stringify(c.expected) === undefined) return 'expected must be JSON'; } catch { return 'expected must be JSON'; }
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
},
|
|
364
|
+
prepare: (i) => ({ export: i.export, cases: i.cases.map((c) => ({ id: c.id, args: c.args })) }),
|
|
365
|
+
interpret({ inputs, result }) {
|
|
366
|
+
const got = result && result.results && typeof result.results === 'object' ? result.results : null;
|
|
367
|
+
if (!got || result.loadFailed) return { satisfied: null, preconditionsHeld: false, observed: { targetLoaded: false }, reason: 'the target did not report results' };
|
|
368
|
+
const regressions = [];
|
|
369
|
+
let reported = 0;
|
|
370
|
+
for (const c of inputs.cases) {
|
|
371
|
+
const r = got[c.id];
|
|
372
|
+
if (!r || typeof r !== 'object') continue;
|
|
373
|
+
reported++;
|
|
374
|
+
if (r.ok !== true || (0,identity/* digestOf */.ol)(r.value) !== (0,identity/* digestOf */.ol)(c.expected)) regressions.push(c.id);
|
|
375
|
+
}
|
|
376
|
+
const complete = reported === inputs.cases.length;
|
|
377
|
+
const observed = { targetLoaded: true, casesDeclared: inputs.cases.length, casesReported: reported, regressedCases: regressions };
|
|
378
|
+
if (regressions.length) return { satisfied: true, preconditionsHeld: complete, observed, reason: `behaviour differs from the declared expectation in case(s): ${regressions.join(', ')}` };
|
|
379
|
+
if (complete) return { satisfied: false, preconditionsHeld: true, observed, reason: 'every declared case returned its expected value' };
|
|
380
|
+
return { satisfied: null, preconditionsHeld: false, observed, reason: 'not every declared case reported a result' };
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const ADAPTERS = Object.freeze([injection, authorization, stateTransition, sideEffect, parserResource, replayIdempotency, functionalRegression]);
|
|
385
|
+
|
|
386
|
+
// EXTERNAL MODULE: ./src/posture/oracles/scenario-classes.js
|
|
387
|
+
var scenario_classes = __webpack_require__(57430);
|
|
388
|
+
;// CONCATENATED MODULE: ./src/posture/oracles/registry.js
|
|
389
|
+
// The oracle registry and its manifest (X-202).
|
|
390
|
+
//
|
|
391
|
+
// The registry is built once, at module load, from the static adapter list and
|
|
392
|
+
// frozen. There is no registration function: a candidate worker (or anything at
|
|
393
|
+
// run time) cannot add, replace or edit an adapter, which is half of "workers
|
|
394
|
+
// cannot alter adapter logic". The other half is the harness digest check in
|
|
395
|
+
// `runOracle`.
|
|
396
|
+
//
|
|
397
|
+
// `oracleManifest()` is the machine-readable description X-208 gates a release on:
|
|
398
|
+
// per adapter, its class, declared prerequisites, platforms (with an honest status
|
|
399
|
+
// per platform), budgets, negative controls, fixture locations, limitations and
|
|
400
|
+
// the logic digest a replay manifest pins. It contains no function bodies and no
|
|
401
|
+
// host details, and its order is fixed, so it is byte-stable across runs.
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
const BY_ID = new Map(ADAPTERS.map((a) => [a.id, a]));
|
|
407
|
+
if (BY_ID.size !== ADAPTERS.length) throw new Error('duplicate oracle adapter id');
|
|
408
|
+
|
|
409
|
+
function getOracle(id) {
|
|
410
|
+
return typeof id === 'string' && BY_ID.has(id) ? BY_ID.get(id) : null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function listOracles() {
|
|
414
|
+
return [...ADAPTERS].sort((a, b) => a.id.localeCompare(b.id));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const MANIFEST_SCHEMA = 'agentic-security/oracle-manifest';
|
|
418
|
+
|
|
419
|
+
/** The manifest entry for one adapter: data only. */
|
|
420
|
+
function manifestEntry(a) {
|
|
421
|
+
return {
|
|
422
|
+
id: a.id, class: a.class, version: a.version, logicDigest: a.logicDigest, description: a.description,
|
|
423
|
+
prerequisites: a.prerequisites.map((id) => ({ id, description: oracle/* PREREQUISITES */.sD[id].description })),
|
|
424
|
+
platforms: a.platforms, budgets: a.budgets,
|
|
425
|
+
negativeControls: a.negativeControls, fixtures: a.fixtures, limitations: a.limitations,
|
|
426
|
+
receipts: 'issued by the verifier domain only (runOracle); a caller-supplied receipt is not valid',
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// X-205: the supported-class manifest for non-taint vulnerabilities. Each class names the adapter that executes it, whether
|
|
431
|
+
// that adapter was reused, its fixtures and the user-visible report text (prerequisites, platforms, limitations).
|
|
432
|
+
function scenarioClassEntries(adapters) {
|
|
433
|
+
return scenario_classes/* SCENARIO_CLASSES */.ML.map((c) => {
|
|
434
|
+
const a = adapters.find((x) => x.id === c.adapterId) || null;
|
|
435
|
+
return {
|
|
436
|
+
id: c.id, title: c.title, claim: c.claim, oracle: c.adapterId, adapterReuse: c.adapterReuse,
|
|
437
|
+
requires: c.requires, limitations: c.limitations, fixtures: c.fixtures,
|
|
438
|
+
prerequisites: a ? a.prerequisites.map((id) => ({ id, description: oracle/* PREREQUISITES */.sD[id].description })) : [],
|
|
439
|
+
platforms: a ? a.platforms : null,
|
|
440
|
+
report: (0,scenario_classes/* scenarioClassReportLines */.Uq)(c, a),
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function oracleManifest(adapters = listOracles()) {
|
|
446
|
+
const entries = adapters.map(manifestEntry);
|
|
447
|
+
return {
|
|
448
|
+
schema: MANIFEST_SCHEMA, schemaVersion: '1.0.0',
|
|
449
|
+
classes: [...oracle/* ORACLE_CLASSES */.aI],
|
|
450
|
+
oracles: entries,
|
|
451
|
+
// Every class the PRD names must have an adapter; a missing one is stated, never implied.
|
|
452
|
+
uncoveredClasses: oracle/* ORACLE_CLASSES */.aI.filter((c) => !adapters.some((a) => a.class === c)),
|
|
453
|
+
problems: [
|
|
454
|
+
...adapters.flatMap((a) => (0,oracle/* validateOracleSpec */.GX)(a).map((p) => `${a.id}: ${p}`)),
|
|
455
|
+
...scenario_classes/* SCENARIO_CLASSES */.ML.filter((c) => !adapters.some((a) => a.id === c.adapterId)).map((c) => `scenario class ${c.id}: its oracle '${c.adapterId}' is not registered`),
|
|
456
|
+
],
|
|
457
|
+
nonTaint: {
|
|
458
|
+
schema: scenario_classes/* NON_TAINT_SCHEMA */.RF, schemaVersion: '1.0.0',
|
|
459
|
+
classes: scenarioClassEntries(adapters),
|
|
460
|
+
unsupported: scenario_classes/* UNSUPPORTED_NON_TAINT */.bm.map((u) => ({ id: u.id, cwes: u.cwes, reason: u.reason })),
|
|
461
|
+
rules: ['taint absence never refutes a non-taint hypothesis', 'an unsupported or unrun case is never counted in the trusted-negative denominator'],
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
/***/ })
|
|
468
|
+
|
|
469
|
+
};
|
package/dist/7033.index.js
CHANGED
|
@@ -1428,13 +1428,16 @@ function resolvedHackageComponents(files) {
|
|
|
1428
1428
|
const SCOPE = { runtime: 'required', test: 'optional', benchmark: 'optional', setup: 'optional', 'build-tool': 'optional' };
|
|
1429
1429
|
const direct = new Set((g.declared || []).map((d) => d.name));
|
|
1430
1430
|
const components = [];
|
|
1431
|
+
// Boot libraries are not dependencies to match, but the plan states the exact version the compiler provides: the compiler-advisory
|
|
1432
|
+
// grouping decides a boot-library advisory from it (and from the compiler-id) instead of from a declared range.
|
|
1433
|
+
const bootLibraries = (g.units || []).filter((u) => u.origin === 'compiler' && u.name && u.version).map((u) => ({ name: u.name, version: u.version, versionSource: { file: r.file, line: null } }));
|
|
1431
1434
|
for (const u of g.units) {
|
|
1432
1435
|
if (u.origin === 'local' || u.origin === 'compiler' || !u.name || !u.version) continue; // the project itself and boot libraries are not dependencies to match
|
|
1433
1436
|
const scopes = Array.isArray(u.scopes) && u.scopes.length ? u.scopes : ['runtime'];
|
|
1434
1437
|
const scope = scopes.includes('runtime') ? 'runtime' : scopes[0];
|
|
1435
1438
|
components.push({ ecosystem: 'hackage', name: u.name, version: u.version, declaredRange: null, resolution: 'plan', scope, engineScope: SCOPE[scope] || 'required', target: null, componentKind: u.componentKind || null, manifest: r.file, line: null, ghcComponent: false, direct: direct.has(u.name), unitId: u.id, origin: u.origin, versionSource: { file: r.file, line: null } });
|
|
1436
1439
|
}
|
|
1437
|
-
return { components, gaps, summary };
|
|
1440
|
+
return { components, bootLibraries, compiler: g.compiler || null, gaps, summary };
|
|
1438
1441
|
}
|
|
1439
1442
|
|
|
1440
1443
|
// ── Nix closure ──────────────────────────────────────────────────────────────────
|
package/dist/7838.index.js
CHANGED
|
@@ -536,6 +536,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
536
536
|
/* harmony import */ var _fix_honesty_gate_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(27785);
|
|
537
537
|
/* harmony import */ var _test_runner_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(35136);
|
|
538
538
|
/* harmony import */ var _fix_metrics_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(32238);
|
|
539
|
+
/* harmony import */ var _verification_emit_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(85578);
|
|
539
540
|
var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([_engine_js__WEBPACK_IMPORTED_MODULE_3__]);
|
|
540
541
|
_engine_js__WEBPACK_IMPORTED_MODULE_3__ = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[0];
|
|
541
542
|
// Closed-loop /fix verification (Sentinel-parity FR-L4-4, FR-L4-5).
|
|
@@ -563,6 +564,7 @@ _engine_js__WEBPACK_IMPORTED_MODULE_3__ = (__webpack_async_dependencies__.then ?
|
|
|
563
564
|
|
|
564
565
|
|
|
565
566
|
|
|
567
|
+
|
|
566
568
|
const SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
567
569
|
|
|
568
570
|
// Run a focused re-scan over just the patched file(s) using the in-memory
|
|
@@ -582,7 +584,7 @@ async function verifyPatch({
|
|
|
582
584
|
// patched files are scanned deep, together with their import closure and manifests read from disk.
|
|
583
585
|
let deep;
|
|
584
586
|
if (Object.keys(fileContents).some((f) => /\.(?:l?hs|hs-boot|hsc|nix)$/i.test(f))) {
|
|
585
|
-
const { withLanguageContext } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__,
|
|
587
|
+
const { withLanguageContext } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 52603));
|
|
586
588
|
const lc = withLanguageContext(scanRoot, fileContents, depFileContents);
|
|
587
589
|
fileContents = lc.fileContents; depFileContents = lc.depFileContents; deep = true;
|
|
588
590
|
}
|
|
@@ -727,6 +729,8 @@ async function verifyFix({
|
|
|
727
729
|
testTimeoutMs,
|
|
728
730
|
recordMetrics = true,
|
|
729
731
|
poc,
|
|
732
|
+
patchNegative,
|
|
733
|
+
assuranceConfig,
|
|
730
734
|
} = {}) {
|
|
731
735
|
// R5 (reporting half) — time each stage as it runs. Measured here rather
|
|
732
736
|
// than inside each stage because only this function knows the boundaries of
|
|
@@ -809,7 +813,26 @@ async function verifyFix({
|
|
|
809
813
|
}
|
|
810
814
|
_lap('honesty');
|
|
811
815
|
|
|
812
|
-
|
|
816
|
+
// X-204: patch-negative verification, only when the caller asked for it AND the `patch-negative-verification` feature is
|
|
817
|
+
// on. `patchNegative` is the requester's exploit scenario and functional cases ({ original, functional, commit?, ... }); the
|
|
818
|
+
// candidate `files` are the proposed diff. With the flag off, or no request, nothing here runs and every field below is
|
|
819
|
+
// exactly what it was before.
|
|
820
|
+
let pn = null;
|
|
821
|
+
if (patchNegative && typeof patchNegative === 'object') {
|
|
822
|
+
try {
|
|
823
|
+
const { verifyPatchNegative } = await Promise.all(/* import() */[__webpack_require__.e(6239), __webpack_require__.e(5613)]).then(__webpack_require__.bind(__webpack_require__, 95613));
|
|
824
|
+
pn = await verifyPatchNegative({
|
|
825
|
+
hypothesisId: originalFindingStableId, ...patchNegative, patch: { files },
|
|
826
|
+
commit: patchNegative.commit ?? (0,_verification_emit_js__WEBPACK_IMPORTED_MODULE_7__.headCommit)(scanRoot),
|
|
827
|
+
}, { config: assuranceConfig });
|
|
828
|
+
} catch (e) {
|
|
829
|
+
pn = { status: 'error', verifiedFix: false, incompleteStep: 'patch-negative', failureCode: 'harness-error', reason: `patch-negative verification failed to run: ${String(e?.message || e).slice(0, 200)}`, summary: '' };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const pnActive = pn !== null && pn.status !== 'disabled';
|
|
833
|
+
_lap('patchNegative');
|
|
834
|
+
|
|
835
|
+
const ok = rescan.ok && (lint.ok || lint.skipped) && testsOk && pocOk && (honesty ? honesty.ok : true) && (pnActive ? pn.verifiedFix === true : true);
|
|
813
836
|
|
|
814
837
|
// FR-305 (assurance-hardening PRD): `ok` alone conflates "every leg
|
|
815
838
|
// genuinely ran and passed" with "passed, but a required leg was skipped
|
|
@@ -851,6 +874,7 @@ async function verifyFix({
|
|
|
851
874
|
: pocLeg.status === 'no-longer-proven' ? 'poc: PASS (ran against the patch and no longer demonstrates the vulnerability)'
|
|
852
875
|
: `poc: inconclusive — not counted either way (${pocLeg.reason || 'no detail reported'})`,
|
|
853
876
|
// FR-305: never let a degraded pass read the same as a full one.
|
|
877
|
+
pnActive ? `patch-negative: ${pn.verifiedFix ? 'PASS (verified-fix)' : `FAIL - not verified-fix, incomplete step '${pn.incompleteStep}' (${pn.failureCode}): ${pn.reason}`}` : null,
|
|
854
878
|
ok && !verifiedFull ? `NOTE: PASSED, but NOT fully verified — ${degradedLegs.join('; ')}` : null,
|
|
855
879
|
].filter(Boolean).join('\n');
|
|
856
880
|
// Persist the attempt so the distribution can be reported from real runs.
|
|
@@ -877,7 +901,19 @@ async function verifyFix({
|
|
|
877
901
|
});
|
|
878
902
|
}
|
|
879
903
|
|
|
880
|
-
|
|
904
|
+
// X-201: the one verification record, ADDITIVE. `ok` and every field above stay exactly as they were; the record
|
|
905
|
+
// says what was actually established (a static re-scan is not an exploit oracle, so without a PoC leg it is
|
|
906
|
+
// `not-run`, and nothing here can reach `confirmed`/`refuted`: that takes a trusted oracle adapter).
|
|
907
|
+
const emitted = (0,_verification_emit_js__WEBPACK_IMPORTED_MODULE_7__/* .emitVerification */ .T)('fix-verify', { rescan, lint, tests, poc: pocLeg }, {
|
|
908
|
+
originalFindingStableId, files, scanRoot, finding: poc?.finding,
|
|
909
|
+
});
|
|
910
|
+
const verificationRecord = emitted.ok ? emitted.record : null;
|
|
911
|
+
|
|
912
|
+
return {
|
|
913
|
+
ok, verifiedFull, degradedLegs, rescan, lint, tests, testedPrePatch: _testedPrePatch, honesty, poc: pocLeg, durations, summary, verificationRecord,
|
|
914
|
+
// additive, present only when patch-negative verification was requested (X-204)
|
|
915
|
+
...(pn ? { patchNegative: pn, ...(pnActive ? { fixStatus: pn.verifiedFix ? 'verified-fix' : 'not-verified-fix' } : {}) } : {}),
|
|
916
|
+
};
|
|
881
917
|
}
|
|
882
918
|
|
|
883
919
|
__webpack_async_result__();
|
package/dist/8714.index.js
CHANGED
|
@@ -12,7 +12,7 @@ export const modules = {
|
|
|
12
12
|
/* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(77598);
|
|
13
13
|
/* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(31174);
|
|
14
14
|
/* harmony import */ var _posture_integrity_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(71130);
|
|
15
|
-
/* harmony import */ var _context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
|
|
15
|
+
/* harmony import */ var _context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(52603);
|
|
16
16
|
// Haskell and Nix analysis metadata as registered state artifacts (X-016).
|
|
17
17
|
//
|
|
18
18
|
// An ordinary scan already leaves last-scan.json. Two things a reviewer, auditor or retention policy needs about the
|
package/dist/8798.index.js
CHANGED
|
@@ -24,7 +24,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
24
24
|
/* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(48161);
|
|
25
25
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(76760);
|
|
26
26
|
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(31421);
|
|
27
|
-
/* harmony import */ var _sandbox_index_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(
|
|
27
|
+
/* harmony import */ var _sandbox_index_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(75778);
|
|
28
28
|
/* harmony import */ var _nix_script_taint_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(11793);
|
|
29
29
|
// Controlled proof witnesses for Haskell and Nix findings (X-009).
|
|
30
30
|
//
|
|
@@ -120,7 +120,7 @@ function runNixScriptWitness(w, { timeoutMs = DEFAULT_TIMEOUT_MS, force } = {})
|
|
|
120
120
|
// 1. a witness that does not parse proves nothing in either direction
|
|
121
121
|
const syn = (0,_sandbox_index_js__WEBPACK_IMPORTED_MODULE_4__/* .runConfined */ .id)([shellBin, '-n', 'witness.sh'], { root, timeoutMs, force });
|
|
122
122
|
if (syn.status === 'disabled' || syn.status === 'error') return { ...base, backend: syn.backend, status: 'invalid', reason: `the sandbox could not run the syntax check (${syn.backend}): ${String(syn.stderr || '').trim() || 'no detail'}` };
|
|
123
|
-
if (syn.timedOut) return { ...base, backend: syn.backend, status: 'invalid', reason: 'the syntax check exceeded its time budget' };
|
|
123
|
+
if (syn.timedOut) return { ...base, backend: syn.backend, status: 'invalid', reason: 'the syntax check exceeded its time budget', timedOut: true };
|
|
124
124
|
if (syn.exitCode !== 0) return { ...base, backend: syn.backend, status: 'invalid', reason: `syntax error in the generated witness: ${String(syn.stderr || '').trim().split('\n')[0] || 'shell rejected it'}` };
|
|
125
125
|
// 2. the real run
|
|
126
126
|
const r = (0,_sandbox_index_js__WEBPACK_IMPORTED_MODULE_4__/* .runConfined */ .id)([shellBin, 'witness.sh'], { root, timeoutMs, force });
|
package/dist/9207.index.js
CHANGED
|
@@ -157,6 +157,11 @@ function computeRunAttestation({
|
|
|
157
157
|
try {
|
|
158
158
|
att.signature = (0,_integrity_js__WEBPACK_IMPORTED_MODULE_1__/* .signLastScan */ .lU)(digest);
|
|
159
159
|
att.signatureScope = 'per-install-hmac';
|
|
160
|
+
// CORE-003.AC03: say plainly who vouches. A symmetric install HMAC can be
|
|
161
|
+
// produced by anyone who can verify it, so it is self-issued by
|
|
162
|
+
// construction and can never be third-party certification.
|
|
163
|
+
att.trustBasis = 'self-issued-local-key';
|
|
164
|
+
att.independentlyCertified = false;
|
|
160
165
|
} catch { /* signing is best-effort; an unsigned attestation is still valid */ }
|
|
161
166
|
}
|
|
162
167
|
return att;
|