@clear-capabilities/agentic-security-scanner 0.143.0 → 0.145.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 +555 -0
- package/bin/agentic-security.js +770 -49
- package/dist/1.index.js +223 -0
- package/dist/113.index.js +117 -18
- package/dist/144.index.js +163 -0
- package/dist/178.index.js +24 -6
- package/dist/238.index.js +3 -2
- package/dist/265.index.js +191 -0
- package/dist/271.index.js +165 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +187 -52
- package/dist/444.index.js +11 -2
- package/dist/449.index.js +76 -12
- package/dist/526.index.js +117 -18
- package/dist/552.index.js +97 -0
- package/dist/637.index.js +27 -5
- package/dist/730.index.js +311 -0
- package/dist/736.index.js +301 -0
- package/dist/824.index.js +7 -0
- package/dist/905.index.js +88 -22
- package/dist/920.index.js +491 -0
- package/dist/970.index.js +173 -0
- package/dist/agentic-security.mjs +13 -13
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/calibration-seed.json +2 -0
- package/package.json +23 -9
- package/src/compare.js +6 -1
- package/src/dataflow/CLAUDE.md +1 -1
- package/src/dataflow/index.js +18 -0
- package/src/dataflow/privacy-catalog.js +290 -0
- package/src/dataflow/privacy-deep-walker.js +515 -0
- package/src/dataflow/privacy-governance.js +126 -0
- package/src/dataflow/privacy-inventory.js +154 -0
- package/src/dataflow/privacy-sink-policy.js +125 -0
- package/src/dataflow/privacy-taint.js +115 -54
- package/src/dataflow/privacy-taxonomy.js +233 -0
- package/src/discovery/disprove.js +7 -3
- package/src/discovery/hunter.js +9 -5
- package/src/discovery/index.js +2 -2
- package/src/discovery/llm-invoke.js +69 -13
- package/src/egress/audit.js +147 -0
- package/src/egress/policy.js +313 -0
- package/src/egress/redact.js +180 -0
- package/src/engine.js +1048 -302
- package/src/fix/apply-fix-service.js +404 -0
- package/src/fix/approver-registry.js +157 -0
- package/src/history-scan.js +22 -5
- package/src/ir/CLAUDE.md +1 -1
- package/src/llm-validator/index.js +86 -9
- package/src/llm-validator/model-status.js +66 -0
- package/src/lsp/server.js +49 -2
- package/src/mcp/tools.js +177 -50
- package/src/pipeline/analyzer-supervisor.js +93 -0
- package/src/pipeline/analyzer-worker.js +26 -0
- package/src/pipeline/annotator-runner.js +33 -0
- package/src/pipeline/assurance-mode.js +154 -0
- package/src/pipeline/cascade-worker-pool.js +172 -0
- package/src/pipeline/cascade-worker.js +43 -0
- package/src/pipeline/coverage-ledger.js +0 -0
- package/src/pipeline/detector-runner.js +51 -0
- package/src/pipeline/enrichment-completion.js +58 -0
- package/src/pipeline/evidence-provenance.js +91 -0
- package/src/pipeline/finding-schema.js +108 -0
- package/src/pipeline/legacy-compat.js +101 -0
- package/src/pipeline/producer-collector.js +48 -0
- package/src/pipeline/producer-registry.js +112 -0
- package/src/pipeline/scan-health.js +144 -0
- package/src/posture/CLAUDE.md +123 -0
- package/src/posture/accuracy-scorecard.js +156 -1
- package/src/posture/adversary-agent.js +15 -3
- package/src/posture/artifact-registry.js +241 -0
- package/src/posture/auditor-walkthrough.js +186 -21
- package/src/posture/calibration-feedback.js +201 -0
- package/src/posture/calibration-seed.json +2 -0
- package/src/posture/calibration.js +25 -0
- package/src/posture/compliance-evidence-signing.js +131 -0
- package/src/posture/compliance-policy.js +324 -17
- package/src/posture/cross-repo-memory.js +7 -2
- package/src/posture/custom-rules.js +36 -0
- package/src/posture/deterministic.js +8 -1
- package/src/posture/encryption-provider.js +205 -0
- package/src/posture/evidence-grade-wording.js +71 -0
- package/src/posture/fix-history.js +137 -20
- package/src/posture/fix-honesty-gate.js +47 -6
- package/src/posture/fix-verify.js +65 -8
- package/src/posture/fleet.js +0 -0
- package/src/posture/flow-narration.js +7 -2
- package/src/posture/git-history.js +13 -5
- package/src/posture/legal-hold.js +140 -0
- package/src/posture/llm-redteam.js +10 -1
- package/src/posture/material-change.js +111 -2
- package/src/posture/mttr.js +75 -12
- package/src/posture/policy-bundle.js +274 -0
- package/src/posture/pre-incident-archaeology.js +39 -7
- package/src/posture/privacy-framework.js +47 -6
- package/src/posture/production-feedback.js +179 -0
- package/src/posture/provenance/ai-authorship.js +68 -0
- package/src/posture/provenance/branch-entry.js +80 -0
- package/src/posture/provenance/cache.js +143 -0
- package/src/posture/provenance/confidence.js +36 -0
- package/src/posture/provenance/coordinator.js +786 -0
- package/src/posture/provenance/dag-walk.js +249 -0
- package/src/posture/provenance/evidence-attribution.js +59 -0
- package/src/posture/provenance/git-evidence.js +310 -0
- package/src/posture/provenance/lifecycle.js +208 -0
- package/src/posture/provenance/missing-control-resolver.js +137 -0
- package/src/posture/provenance/origin-resolver.js +342 -0
- package/src/posture/provenance/predicate-replay.js +133 -0
- package/src/posture/provenance/providers/config.js +39 -0
- package/src/posture/provenance/providers/github.js +62 -0
- package/src/posture/provenance/providers/gitlab.js +58 -0
- package/src/posture/provenance/repo-lineage.js +74 -0
- package/src/posture/provenance/sca-origin.js +139 -0
- package/src/posture/provenance/schema.js +255 -0
- package/src/posture/provenance/transitive-sca.js +147 -0
- package/src/posture/provenance/validate.js +30 -0
- package/src/posture/provenance-evidence-bundle.js +144 -0
- package/src/posture/retention-policy.js +132 -0
- package/src/posture/risk-dollars.js +216 -26
- package/src/posture/sbom-diff.js +15 -2
- package/src/posture/scan-checkpoint.js +176 -31
- package/src/posture/secret-history.js +10 -2
- package/src/posture/state-dir.js +64 -5
- package/src/posture/state-lifecycle-report.js +77 -0
- package/src/posture/suppressions.js +59 -3
- package/src/posture/vuln-archaeology.js +8 -2
- package/src/pr-delta.js +25 -4
- package/src/privacy/ir-adapter.js +380 -0
- package/src/report/index.js +248 -5
- package/src/report/oscal.js +7 -2
- package/src/runScan.js +34 -5
- package/src/sast/cpp.js +3 -14
- package/src/sast/rate-limit.js +33 -3
- package/src/sca/llm-function-extract.js +6 -0
- package/src/util/git-hardening.js +128 -0
package/dist/178.index.js
CHANGED
|
@@ -13,7 +13,8 @@ export const modules = {
|
|
|
13
13
|
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
|
|
14
14
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
|
|
15
15
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
16
|
-
/* harmony import */ var
|
|
16
|
+
/* harmony import */ var _util_git_hardening_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8844);
|
|
17
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7691);
|
|
17
18
|
// Time-travel + counterfactual scanning (v0.68).
|
|
18
19
|
//
|
|
19
20
|
// Two new modes that exploit the pure-input shape of runFullScan:
|
|
@@ -36,10 +37,14 @@ export const modules = {
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
|
|
39
41
|
const MAX_FILES_PER_SCAN = 5000;
|
|
40
42
|
|
|
43
|
+
// `root` is the scan target's repository, not this project's own trusted
|
|
44
|
+
// checkout — hardened per FR-PROV-024 / the second Finding Provenance PRD
|
|
45
|
+
// audit (same exposure class as provenance/git-evidence.js's `_run`).
|
|
41
46
|
function _git(root, args) {
|
|
42
|
-
const r = (0,node_child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync)('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
47
|
+
const r = (0,node_child_process__WEBPACK_IMPORTED_MODULE_0__.spawnSync)('git', (0,_util_git_hardening_js__WEBPACK_IMPORTED_MODULE_3__/* .hardenGitArgs */ .Ax)(args), { cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, env: (0,_util_git_hardening_js__WEBPACK_IMPORTED_MODULE_3__/* .hardenGitEnv */ .Si)() });
|
|
43
48
|
return { ok: r.status === 0, stdout: r.stdout || '', stderr: r.stderr || '' };
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -99,7 +104,10 @@ function _listFilesAtRef(root, ref) {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
function _readFileAtRef(root, ref, file) {
|
|
102
|
-
|
|
107
|
+
// `--no-textconv`: this blob-cat form of `show` was verified NOT
|
|
108
|
+
// reachable via a hostile textconv driver in current git — kept for
|
|
109
|
+
// defense-in-depth/uniformity, same as pr-delta.js's equivalent.
|
|
110
|
+
const r = _git(root, ['show', '--no-textconv', `${ref}:${file}`]);
|
|
103
111
|
if (!r.ok) return null;
|
|
104
112
|
return r.stdout;
|
|
105
113
|
}
|
|
@@ -111,7 +119,13 @@ async function _scanAtRef(root, ref) {
|
|
|
111
119
|
const c = _readFileAtRef(root, ref, f);
|
|
112
120
|
if (c != null) fileContents[f] = c;
|
|
113
121
|
}
|
|
114
|
-
|
|
122
|
+
// `provenance:false` — this is a HISTORICAL ref, not "the state of this repo
|
|
123
|
+
// right now". Two things must not happen here: resolving git provenance for
|
|
124
|
+
// findings that already are a point in history (pure waste), and letting
|
|
125
|
+
// updateLifecycle see this partial, historical finding set as the current
|
|
126
|
+
// one — it marks every open stableId NOT in the set as `remediated`, so one
|
|
127
|
+
// per-ref scan would mass-remediate the whole project's real open findings.
|
|
128
|
+
const scan = await (0,_engine_js__WEBPACK_IMPORTED_MODULE_4__/* .runFullScan */ .wW)({ fileContents, scanRoot: root, provenance: false }, () => {});
|
|
115
129
|
return {
|
|
116
130
|
ref,
|
|
117
131
|
fileCount: Object.keys(fileContents).length,
|
|
@@ -193,8 +207,12 @@ async function runWhatIf(root, { overlays = [], remove = [] } = {}) {
|
|
|
193
207
|
}
|
|
194
208
|
}
|
|
195
209
|
// Baseline (without overlays) for delta computation.
|
|
196
|
-
|
|
197
|
-
|
|
210
|
+
// Both legs are hypothetical snapshots being differenced against each other,
|
|
211
|
+
// never the repo's current state — `provenance:false` for the same reason as
|
|
212
|
+
// _scanAtRef above (no provenance to resolve, and updateLifecycle must not
|
|
213
|
+
// treat either snapshot as "what is open right now").
|
|
214
|
+
const baseScan = await (0,_engine_js__WEBPACK_IMPORTED_MODULE_4__/* .runFullScan */ .wW)({ fileContents: _baselineFor(fileContents, overlays, remove, root), scanRoot: root, provenance: false }, () => {});
|
|
215
|
+
const whatIfScan = await (0,_engine_js__WEBPACK_IMPORTED_MODULE_4__/* .runFullScan */ .wW)({ fileContents, scanRoot: root, provenance: false }, () => {});
|
|
198
216
|
const baseIds = new Set((baseScan.findings || []).map(f => f.stableId || f.id));
|
|
199
217
|
const wIds = new Set((whatIfScan.findings || []).map(f => f.stableId || f.id));
|
|
200
218
|
const introduced = (whatIfScan.findings || []).filter(f => !baseIds.has(f.stableId || f.id)).map(_compact);
|
package/dist/238.index.js
CHANGED
|
@@ -8,9 +8,10 @@ export const modules = {
|
|
|
8
8
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
9
|
/* harmony export */ I3: () => (/* binding */ recordFixAttempt),
|
|
10
10
|
/* harmony export */ fixDurationReport: () => (/* binding */ fixDurationReport),
|
|
11
|
-
/* harmony export */ renderFixDurationSummary: () => (/* binding */ renderFixDurationSummary)
|
|
11
|
+
/* harmony export */ renderFixDurationSummary: () => (/* binding */ renderFixDurationSummary),
|
|
12
|
+
/* harmony export */ sU: () => (/* binding */ loadFixAttempts)
|
|
12
13
|
/* harmony export */ });
|
|
13
|
-
/* unused harmony exports FIX_STAGES,
|
|
14
|
+
/* unused harmony exports FIX_STAGES, bucketOf, summarizeFixDurations, _internals, summarizeFixAxes, renderFixAxes */
|
|
14
15
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
15
16
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
|
|
16
17
|
/* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export const id = 265;
|
|
2
|
+
export const ids = [265];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 4265:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ createCascadePool: () => (/* binding */ createCascadePool)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* unused harmony export DEFAULT_GRACE_MS */
|
|
12
|
+
/* harmony import */ var node_worker_threads__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5919);
|
|
13
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
|
|
14
|
+
/* harmony import */ var node_url__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3136);
|
|
15
|
+
// FR-202 phase 3b (D-0050): a pool of long-lived workers, each running engine.js's
|
|
16
|
+
// _runFileCascade for many files over its lifetime, instead of the one-shot-per-call
|
|
17
|
+
// model runWithDeadline/analyzer-supervisor.js uses (which is correct for FR-202's
|
|
18
|
+
// primitive-level proof but would force whole-project setup — _buildProjectIndex,
|
|
19
|
+
// _buildGlobalJavaTaintedMethodIndex, _loadCustomRules — to redo per FILE, an O(n^2)
|
|
20
|
+
// cost across a scan). Each worker performs that setup ONCE at creation via
|
|
21
|
+
// _initCascadeWorkerState, then answers task messages over its lifetime.
|
|
22
|
+
//
|
|
23
|
+
// A task that exceeds its deadline+grace gets its OWN worker terminated (genuinely
|
|
24
|
+
// preemptive, mirroring analyzer-supervisor.js's worker.terminate() precedent) and a
|
|
25
|
+
// fresh replacement worker is spawned to keep the pool at full size for later files —
|
|
26
|
+
// one hung file costs one worker restart, not the whole scan.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// `new Worker()` needs a REAL file on disk -- once ncc bundles this module's code
|
|
32
|
+
// into a dist/ chunk, `import.meta.url` resolves to that chunk's location (dist/),
|
|
33
|
+
// not to src/pipeline/, so a naive path.join(HERE, 'cascade-worker.js') would look
|
|
34
|
+
// for a file that was never copied there. src/ always ships as a sibling of dist/
|
|
35
|
+
// (see package.json's "files"), in both a dev checkout and the published npm
|
|
36
|
+
// package, so fall back to the real, unbundled worker script in that case.
|
|
37
|
+
function _resolveDefaultWorkerScript() {
|
|
38
|
+
const hereDir = node_path__WEBPACK_IMPORTED_MODULE_1__.dirname((0,node_url__WEBPACK_IMPORTED_MODULE_2__.fileURLToPath)(import.meta.url));
|
|
39
|
+
if (node_path__WEBPACK_IMPORTED_MODULE_1__.basename(hereDir) === 'pipeline') return node_path__WEBPACK_IMPORTED_MODULE_1__.join(hereDir, 'cascade-worker.js');
|
|
40
|
+
return node_path__WEBPACK_IMPORTED_MODULE_1__.join(hereDir, '..', 'src', 'pipeline', 'cascade-worker.js');
|
|
41
|
+
}
|
|
42
|
+
const DEFAULT_WORKER_SCRIPT = _resolveDefaultWorkerScript();
|
|
43
|
+
const DEFAULT_GRACE_MS = 500;
|
|
44
|
+
|
|
45
|
+
function spawnReadyWorker({ workerScript, modulePath, fileContents, scanRoot }) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
let settled = false;
|
|
48
|
+
const worker = new node_worker_threads__WEBPACK_IMPORTED_MODULE_0__.Worker(workerScript, { workerData: { modulePath, fileContents, scanRoot } });
|
|
49
|
+
const finish = (fn, value) => {
|
|
50
|
+
if (settled) return;
|
|
51
|
+
settled = true;
|
|
52
|
+
worker.off('message', onMessage);
|
|
53
|
+
worker.off('error', onError);
|
|
54
|
+
fn(value);
|
|
55
|
+
};
|
|
56
|
+
const onMessage = (msg) => {
|
|
57
|
+
if (!msg) return;
|
|
58
|
+
if (msg.type === 'ready') finish(resolve, worker);
|
|
59
|
+
else if (msg.type === 'init-error') { worker.terminate().catch(() => {}); finish(reject, new Error(msg.error)); }
|
|
60
|
+
};
|
|
61
|
+
const onError = (err) => { worker.terminate().catch(() => {}); finish(reject, err); };
|
|
62
|
+
worker.on('message', onMessage);
|
|
63
|
+
worker.on('error', onError);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {object} opts
|
|
69
|
+
* @param {Record<string,string>} opts.fileContents - broadcast once to every worker at creation.
|
|
70
|
+
* @param {string|null} opts.scanRoot
|
|
71
|
+
* @param {number} [opts.poolSize=4]
|
|
72
|
+
* @param {string} opts.modulePath - absolute path to the engine.js each worker imports.
|
|
73
|
+
* @param {string} [opts.workerScript] - defaults to cascade-worker.js next to this file.
|
|
74
|
+
* @param {number} [opts.graceMs]
|
|
75
|
+
*/
|
|
76
|
+
function createCascadePool(opts = {}) {
|
|
77
|
+
const {
|
|
78
|
+
fileContents = {},
|
|
79
|
+
scanRoot = null,
|
|
80
|
+
poolSize = 4,
|
|
81
|
+
workerScript = DEFAULT_WORKER_SCRIPT,
|
|
82
|
+
modulePath,
|
|
83
|
+
graceMs = DEFAULT_GRACE_MS,
|
|
84
|
+
} = opts;
|
|
85
|
+
if (!modulePath) throw new Error('createCascadePool requires opts.modulePath');
|
|
86
|
+
if (!(Number.isInteger(poolSize) && poolSize > 0)) throw new Error('createCascadePool requires opts.poolSize to be a positive integer');
|
|
87
|
+
|
|
88
|
+
const idle = [];
|
|
89
|
+
const pendingIdle = [];
|
|
90
|
+
const allWorkers = new Set();
|
|
91
|
+
let nextTaskId = 1;
|
|
92
|
+
let closed = false;
|
|
93
|
+
let spawnFailureCount = 0;
|
|
94
|
+
let lastSpawnError = null;
|
|
95
|
+
|
|
96
|
+
function spawnAndRegister() {
|
|
97
|
+
return spawnReadyWorker({ workerScript, modulePath, fileContents, scanRoot }).then((w) => {
|
|
98
|
+
allWorkers.add(w);
|
|
99
|
+
return w;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Background respawn after a killed worker (see runFile's timeout branch)
|
|
104
|
+
// is deliberately fire-and-forget -- the timed-out task has already
|
|
105
|
+
// resolved and nothing is waiting on this promise -- but a swallowed
|
|
106
|
+
// failure here would otherwise shrink the pool invisibly (e.g. thread
|
|
107
|
+
// exhaustion making every future respawn fail silently forever). Track
|
|
108
|
+
// it instead of a bare `.catch(() => {})`.
|
|
109
|
+
function respawnInBackground() {
|
|
110
|
+
spawnAndRegister().then(releaseWorker).catch((err) => {
|
|
111
|
+
spawnFailureCount += 1;
|
|
112
|
+
lastSpawnError = err;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function releaseWorker(w) {
|
|
117
|
+
if (closed) { allWorkers.delete(w); w.terminate().catch(() => {}); return; }
|
|
118
|
+
if (pendingIdle.length) pendingIdle.shift()(w);
|
|
119
|
+
else idle.push(w);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function acquireWorker() {
|
|
123
|
+
if (idle.length) return Promise.resolve(idle.pop());
|
|
124
|
+
return new Promise((resolve) => pendingIdle.push(resolve));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const ready = Promise.all(
|
|
128
|
+
Array.from({ length: poolSize }, () => spawnAndRegister().then(releaseWorker))
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Run one file's cascade on a pooled worker. Resolves with
|
|
133
|
+
* {ok:true, result} | {ok:false, error} | {ok:false, timedOut:true, timeoutMs, graceMs}.
|
|
134
|
+
* Never rejects.
|
|
135
|
+
*/
|
|
136
|
+
async function runFile(p, c, taskScanRoot, detectorErrors, taskOpts = {}) {
|
|
137
|
+
await ready;
|
|
138
|
+
if (closed) return { ok: false, error: 'pool is closed' };
|
|
139
|
+
const timeoutMs = taskOpts.timeoutMs;
|
|
140
|
+
const worker = await acquireWorker();
|
|
141
|
+
const id = nextTaskId++;
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
let settled = false;
|
|
144
|
+
let killTimer = null;
|
|
145
|
+
const onMessage = (msg) => {
|
|
146
|
+
if (settled || !msg || msg.id !== id) return;
|
|
147
|
+
settled = true;
|
|
148
|
+
if (killTimer) clearTimeout(killTimer);
|
|
149
|
+
worker.off('message', onMessage);
|
|
150
|
+
releaseWorker(worker);
|
|
151
|
+
resolve(msg.ok ? { ok: true, result: msg.result } : { ok: false, error: msg.error });
|
|
152
|
+
};
|
|
153
|
+
worker.on('message', onMessage);
|
|
154
|
+
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
155
|
+
killTimer = setTimeout(() => {
|
|
156
|
+
if (settled) return;
|
|
157
|
+
settled = true;
|
|
158
|
+
worker.off('message', onMessage);
|
|
159
|
+
allWorkers.delete(worker);
|
|
160
|
+
worker.terminate().catch(() => {});
|
|
161
|
+
if (!closed) respawnInBackground();
|
|
162
|
+
resolve({ ok: false, timedOut: true, timeoutMs, graceMs });
|
|
163
|
+
}, timeoutMs + graceMs);
|
|
164
|
+
killTimer.unref?.();
|
|
165
|
+
}
|
|
166
|
+
worker.postMessage({ type: 'task', id, p, c, scanRoot: taskScanRoot, detectorErrors });
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function shutdown() {
|
|
171
|
+
closed = true;
|
|
172
|
+
await ready.catch(() => {});
|
|
173
|
+
for (const w of allWorkers) await w.terminate().catch(() => {});
|
|
174
|
+
allWorkers.clear();
|
|
175
|
+
idle.length = 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
runFile,
|
|
180
|
+
shutdown,
|
|
181
|
+
ready,
|
|
182
|
+
DEFAULT_GRACE_MS: graceMs,
|
|
183
|
+
getSpawnFailureCount: () => spawnFailureCount,
|
|
184
|
+
getLastSpawnError: () => lastSpawnError,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
/***/ })
|
|
190
|
+
|
|
191
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export const id = 271;
|
|
2
|
+
export const ids = [271];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 2271:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ PROVENANCE_BUNDLE_SCHEMA: () => (/* binding */ PROVENANCE_BUNDLE_SCHEMA),
|
|
10
|
+
/* harmony export */ buildProvenanceEvidenceBundle: () => (/* binding */ buildProvenanceEvidenceBundle),
|
|
11
|
+
/* harmony export */ ensureKeyPair: () => (/* reexport safe */ _evidence_bundle_js__WEBPACK_IMPORTED_MODULE_1__.ensureKeyPair),
|
|
12
|
+
/* harmony export */ signProvenanceEvidenceBundle: () => (/* binding */ signProvenanceEvidenceBundle),
|
|
13
|
+
/* harmony export */ verifyProvenanceEvidenceBundle: () => (/* binding */ verifyProvenanceEvidenceBundle)
|
|
14
|
+
/* harmony export */ });
|
|
15
|
+
/* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7598);
|
|
16
|
+
/* harmony import */ var _evidence_bundle_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8317);
|
|
17
|
+
// Signed provenance evidence bundles (Finding Provenance PRD, M4 §4.1).
|
|
18
|
+
//
|
|
19
|
+
// Sibling to posture/evidence-bundle.js, not a generalization of it — that
|
|
20
|
+
// module's bundle shape (proofTier, taintPath, exploitability...) answers
|
|
21
|
+
// "is this finding real"; this one answers "who/when introduced it, how
|
|
22
|
+
// sure are we." Forcing one shape to cover both would leave half of every
|
|
23
|
+
// bundle null. Same four-function pattern (build/sign/verify + a schema
|
|
24
|
+
// string + a top-level-key allowlist), same Ed25519 key material — reused,
|
|
25
|
+
// not reimplemented.
|
|
26
|
+
//
|
|
27
|
+
// The allowlisted fields mirror provenance/coordinator.js's own
|
|
28
|
+
// computeDigest() material EXACTLY (stableId, findingOrigin.commit,
|
|
29
|
+
// branchIntroduction.commit, evidenceAttribution role:path:line:commit
|
|
30
|
+
// strings, method, confidence.reasons, limitations) plus repo/HEAD identity
|
|
31
|
+
// (not in computeDigest's material, since that digest never leaves the repo
|
|
32
|
+
// it was computed in, but a bundle does). Everything here is copied from
|
|
33
|
+
// what findingProvenance already computed. Nothing is inferred.
|
|
34
|
+
//
|
|
35
|
+
// canonicalJson vs canonicalBytes — resolved before writing this file
|
|
36
|
+
// -------------------------------------------------------------------
|
|
37
|
+
// evidence-bundle.js exports TWO canonicalisation helpers, not one, and they
|
|
38
|
+
// are not interchangeable. `canonicalJson(value)` is a pure, shape-agnostic
|
|
39
|
+
// deterministic serialiser (sorted keys at every level, order-preserving
|
|
40
|
+
// arrays) — safe to reuse for any bundle shape. `canonicalBytes(bundle)`
|
|
41
|
+
// is NOT generic: it hardcodes evidence-bundle.js's own six top-level keys
|
|
42
|
+
// (`schema, finding, evidence, engine, proves, doesNotProve`) when building
|
|
43
|
+
// the object it signs. Reusing `canonicalBytes` here would silently sign a
|
|
44
|
+
// filtered object missing this module's `repo` and `provenance` fields
|
|
45
|
+
// (and would read `bundle.evidence`, which a provenance bundle never has) —
|
|
46
|
+
// exactly the EA-03 failure mode the allowlist check below exists to catch,
|
|
47
|
+
// just introduced from the signing side instead of the verifying side. So
|
|
48
|
+
// this module imports the generic `canonicalJson` (as the brief's Step 1
|
|
49
|
+
// sketch already did) and signs `canonicalJson(bundle)` directly, since
|
|
50
|
+
// `buildProvenanceEvidenceBundle` never puts anything on the unsigned
|
|
51
|
+
// object beyond this module's own allowlisted fields.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
const PROVENANCE_BUNDLE_SCHEMA = 'agentic-security/provenance-evidence@1';
|
|
57
|
+
|
|
58
|
+
const PROVES = 'This bundle\'s contents are exactly what was signed at attestation time.';
|
|
59
|
+
const DOES_NOT_PROVE = 'This bundle does NOT prove the origin commit is correctly identified — read confidence.level and limitations for that. It proves the RECORD is unmodified, not that the record is right.';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build an unsigned provenance bundle from one finding's findingProvenance.
|
|
63
|
+
* Returns null for a finding with no findingProvenance at all (nothing to
|
|
64
|
+
* attest) — this is a caller error (attest a scan before its provenance
|
|
65
|
+
* pass ran), not a case to paper over with an empty bundle.
|
|
66
|
+
*/
|
|
67
|
+
function buildProvenanceEvidenceBundle(finding, { engineVersion, repoIdentity, head } = {}) {
|
|
68
|
+
if (!finding || typeof finding !== 'object') return null;
|
|
69
|
+
const fp = finding.findingProvenance;
|
|
70
|
+
if (!fp || typeof fp !== 'object') return null;
|
|
71
|
+
return {
|
|
72
|
+
schema: PROVENANCE_BUNDLE_SCHEMA,
|
|
73
|
+
finding: {
|
|
74
|
+
id: finding.id ?? null,
|
|
75
|
+
stableId: finding.stableId ?? null,
|
|
76
|
+
},
|
|
77
|
+
repo: {
|
|
78
|
+
identity: repoIdentity ?? null,
|
|
79
|
+
head: head ?? fp.analysisBasis?.head ?? null,
|
|
80
|
+
},
|
|
81
|
+
provenance: {
|
|
82
|
+
status: fp.status ?? null,
|
|
83
|
+
findingOrigin: fp.findingOrigin
|
|
84
|
+
? {
|
|
85
|
+
commit: fp.findingOrigin.commit ?? null,
|
|
86
|
+
authorName: fp.findingOrigin.authorName ?? null,
|
|
87
|
+
authorDate: fp.findingOrigin.authorDate ?? null,
|
|
88
|
+
summary: fp.findingOrigin.summary ?? null,
|
|
89
|
+
}
|
|
90
|
+
: null,
|
|
91
|
+
branchIntroduction: fp.branchIntroduction
|
|
92
|
+
? { commit: fp.branchIntroduction.commit ?? null, branch: fp.branchIntroduction.branch ?? null }
|
|
93
|
+
: null,
|
|
94
|
+
evidenceAttribution: (fp.evidenceAttribution || []).map((n) => ({
|
|
95
|
+
role: n.role ?? null, path: n.path ?? null, line: n.line ?? null, commit: n.commit ?? null,
|
|
96
|
+
})),
|
|
97
|
+
method: fp.method ?? null,
|
|
98
|
+
confidence: fp.confidence
|
|
99
|
+
? { level: fp.confidence.level ?? null, score: fp.confidence.score ?? null, reasons: fp.confidence.reasons || [] }
|
|
100
|
+
: null,
|
|
101
|
+
limitations: fp.limitations || [],
|
|
102
|
+
// M4 §4.2 final-review fix: this module was written (Task 1) before
|
|
103
|
+
// the cross-repo lineage feature existed (Task 5), so a
|
|
104
|
+
// cross-repo-resolved origin's boundary-crossing marker was dropped
|
|
105
|
+
// from the signed bundle — a foreign repository's commit SHA and a
|
|
106
|
+
// real author name, with no MACHINE-READABLE signal that the origin
|
|
107
|
+
// crossed a repository boundary (only the prose in `limitations`
|
|
108
|
+
// said so, and a programmatic verifier does not read prose). Nested
|
|
109
|
+
// under `provenance` alongside the fields above, so it is part of
|
|
110
|
+
// what gets signed — never a new top-level key.
|
|
111
|
+
historyCoverage: { crossRepoLineage: fp.historyCoverage?.crossRepoLineage ?? false },
|
|
112
|
+
},
|
|
113
|
+
engine: { engineVersion: engineVersion ?? null },
|
|
114
|
+
proves: PROVES,
|
|
115
|
+
doesNotProve: DOES_NOT_PROVE,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function signProvenanceEvidenceBundle(bundle, privateKeyPem) {
|
|
120
|
+
const sig = node_crypto__WEBPACK_IMPORTED_MODULE_0__.sign(null, Buffer.from((0,_evidence_bundle_js__WEBPACK_IMPORTED_MODULE_1__/* .canonicalJson */ .dj)(bundle), 'utf8'), privateKeyPem);
|
|
121
|
+
return {
|
|
122
|
+
...bundle,
|
|
123
|
+
signature: { algorithm: 'ed25519', canonicalisation: PROVENANCE_BUNDLE_SCHEMA, value: sig.toString('base64') },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const PROVENANCE_BUNDLE_TOP_LEVEL_KEYS = new Set([
|
|
128
|
+
'schema', 'finding', 'repo', 'provenance', 'engine', 'proves', 'doesNotProve', 'signature',
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Verify with a PUBLIC key only. Rejects any top-level key outside the
|
|
133
|
+
* allowlist BEFORE checking the signature — same EA-03 fix evidence-bundle.js
|
|
134
|
+
* carries: a signature only covers the bytes it was computed over, so an
|
|
135
|
+
* unknown key stapled on after signing would otherwise verify as authentic.
|
|
136
|
+
*/
|
|
137
|
+
function verifyProvenanceEvidenceBundle(bundle, publicKeyPem) {
|
|
138
|
+
if (!bundle || typeof bundle !== 'object') return { ok: false, reason: 'bundle is not an object' };
|
|
139
|
+
if (bundle.schema !== PROVENANCE_BUNDLE_SCHEMA) return { ok: false, reason: `unrecognised schema: ${bundle.schema}` };
|
|
140
|
+
const unknownKeys = Object.keys(bundle).filter((k) => !PROVENANCE_BUNDLE_TOP_LEVEL_KEYS.has(k));
|
|
141
|
+
if (unknownKeys.length) {
|
|
142
|
+
return { ok: false, reason: `unrecognised top-level key(s) not covered by the signature: ${unknownKeys.join(', ')}` };
|
|
143
|
+
}
|
|
144
|
+
const sig = bundle.signature;
|
|
145
|
+
if (!sig?.value) return { ok: false, reason: 'bundle is unsigned' };
|
|
146
|
+
if (sig.algorithm !== 'ed25519') return { ok: false, reason: `unsupported algorithm: ${sig.algorithm}` };
|
|
147
|
+
if (!publicKeyPem) return { ok: false, reason: 'no public key supplied' };
|
|
148
|
+
const { signature, ...unsigned } = bundle;
|
|
149
|
+
let ok = false;
|
|
150
|
+
try {
|
|
151
|
+
ok = node_crypto__WEBPACK_IMPORTED_MODULE_0__.verify(null, Buffer.from((0,_evidence_bundle_js__WEBPACK_IMPORTED_MODULE_1__/* .canonicalJson */ .dj)(unsigned), 'utf8'), publicKeyPem, Buffer.from(sig.value, 'base64'));
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return { ok: false, reason: `verification error: ${e.message}` };
|
|
154
|
+
}
|
|
155
|
+
return ok
|
|
156
|
+
? { ok: true, reason: null }
|
|
157
|
+
: { ok: false, reason: 'signature does not match the bundle contents — it was modified after signing' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
/***/ })
|
|
164
|
+
|
|
165
|
+
};
|
package/dist/384.index.js
CHANGED
|
@@ -8,7 +8,7 @@ export const modules = {
|
|
|
8
8
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
9
|
/* harmony export */ scanCredentials: () => (/* reexport safe */ _engine_js__WEBPACK_IMPORTED_MODULE_0__.Sv)
|
|
10
10
|
/* harmony export */ });
|
|
11
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
11
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7691);
|
|
12
12
|
// Secrets submodule view of the engine — credential + entropy + TODO scanning.
|
|
13
13
|
|
|
14
14
|
|