@clear-capabilities/agentic-security-scanner 0.176.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 +9 -0
- package/bin/agentic-security.js +9 -2
- package/dist/1919.index.js +1 -1
- package/dist/2066.index.js +2 -1
- package/dist/2584.index.js +19 -1
- package/dist/{4978bec8fdf5c733dc85.json → 37d64df006576248d0d8.json} +5 -2
- package/dist/5456.index.js +291 -0
- package/dist/5620.index.js +147 -23
- package/dist/5937.index.js +672 -0
- package/dist/7838.index.js +70 -25
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/{df1e8683e7eda7b2b79c.js → b4289e728659450b5e19.js} +4 -1
- package/package.json +5 -2
- package/src/posture/custom-rules.js +54 -8
- package/src/posture/dnr/exploit-correlation.js +25 -0
- package/src/posture/evaluation/lab/dataset.js +1 -1
- package/src/posture/evaluation/lab/report.js +19 -1
- package/src/posture/fleet/cli.js +22 -10
- package/src/posture/fleet/enterprise-graph.js +44 -3
- package/src/posture/fleet/job-runner.js +7 -7
- package/src/posture/fleet/job-store.js +3 -1
- package/src/posture/fleet/lineage-summary.js +55 -0
- package/src/posture/fleet/scan-child.js +4 -1
- package/src/posture/proof-fabric/generate.js +2 -1
- package/src/posture/proof-fabric/runner.js +24 -2
- package/src/posture/rule-compiler/cli.js +40 -6
- package/src/posture/rule-compiler/corpus.js +3 -7
- package/src/posture/rule-compiler/ir.js +52 -9
- package/src/posture/rule-compiler/proposal.js +14 -9
- package/src/posture/rule-compiler/regression.js +95 -0
- package/src/posture/rule-compiler/store.js +29 -1
- package/src/posture/rule-pack-signing.js +4 -2
package/dist/5620.index.js
CHANGED
|
@@ -5,7 +5,7 @@ export const modules = {
|
|
|
5
5
|
/***/ 53948:
|
|
6
6
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
7
7
|
|
|
8
|
-
module.exports = __webpack_require__.p + "
|
|
8
|
+
module.exports = __webpack_require__.p + "b4289e728659450b5e19.js";
|
|
9
9
|
|
|
10
10
|
/***/ }),
|
|
11
11
|
|
|
@@ -609,7 +609,7 @@ const DB_FILE = node_path__WEBPACK_IMPORTED_MODULE_1__.join('fleet', 'fleet.db')
|
|
|
609
609
|
const ROLLUP_FILE = 'fleet-rollup.json';
|
|
610
610
|
const sha = (s) => (0,node_crypto__WEBPACK_IMPORTED_MODULE_2__.createHash)('sha256').update(s).digest('hex');
|
|
611
611
|
|
|
612
|
-
const usage = () => { console.error('usage: fleet init | edges [--undeclared] | scan <manifest.yaml> [--workers N] [--retries N] [--timeout-seconds N | --timeout-ms N] [--no-cache] | status [run-id] | resume <run-id|manifest.yaml> [--retry-failed] | query (--depends-on <id> | --blast-radius <component> [--version v] | --internet-to <data-class> [--involving-vulnerable auth|<repo>] | "<question>") | explore [--out file.html]'); return 4; };
|
|
612
|
+
const usage = () => { console.error('usage: fleet init | edges [--undeclared] | scan <manifest.yaml> [--workers N] [--retries N] [--timeout-seconds N | --timeout-ms N] [--no-cache] [--no-discover] [--lineage] | status [run-id] | resume <run-id|manifest.yaml> [--retry-failed] | query (--depends-on <id> | --blast-radius <component> [--version v] | --internet-to <data-class> [--involving-vulnerable auth|<repo>] | "<question>") | explore [--out file.html]'); return 4; };
|
|
613
613
|
const isManifestArg = (s) => /\.ya?ml$/i.test(String(s ?? ''));
|
|
614
614
|
const manifestArg = (args) => { const m = args._[2]; return m && !String(m).startsWith('--') ? m : 'fleet.yaml'; };
|
|
615
615
|
const dbPath = (root) => (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.statePath)(root, DB_FILE);
|
|
@@ -635,7 +635,7 @@ async function cmdEdges(args, root) {
|
|
|
635
635
|
if (args.flags.json) { console.log(JSON.stringify({ discovery: g.discovery ?? null, edges }, null, 2)); return 0; }
|
|
636
636
|
if (!edges.length) { console.log(args.flags.undeclared ? 'No edge was found in code that the manifest does not declare.' : 'The graph has no edges.'); }
|
|
637
637
|
for (const e of edges) {
|
|
638
|
-
const where = e.evidence.filter((x) => x.source === 'discovered-code-literal').map((x) => `${x.file}:${x.line}`);
|
|
638
|
+
const where = e.evidence.filter((x) => x.source === 'discovered-code-literal' || x.source === 'code-boundaries').map((x) => (x.file ? `${x.file}:${x.line}` : `code boundaries: ${x.host ?? x.topic ?? x.name}`));
|
|
639
639
|
console.log(`${e.from} -[${e.type}]-> ${e.to} ${e.discovered ? 'FOUND ONLY IN CODE' : where.length ? 'declared, and seen in code' : 'declared'}${where.length ? ` (${where.join(', ')})` : ''}`);
|
|
640
640
|
}
|
|
641
641
|
if (g.discovery?.limitations) console.log(`\nLimits: ${g.discovery.limitations.join(' ')}`);
|
|
@@ -665,6 +665,12 @@ function entriesOf(artifacts, manifest) {
|
|
|
665
665
|
return manifest.repositories.filter((r) => artifacts.has(r.id)).map((r) => ({ ...artifacts.get(r.id).entry, repo: r.id }));
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
+
/** The PRD names its commands `fleet status JOB-ID` and `fleet resume JOB-ID`; a run is a set of jobs, so a job id names the run it belongs to. */
|
|
669
|
+
function runOfJob(store, id) {
|
|
670
|
+
const job = store.getJob(String(id));
|
|
671
|
+
return job ? store.getRun(job.runId) : null;
|
|
672
|
+
}
|
|
673
|
+
|
|
668
674
|
async function finishRun({ args, root, store, runId, manifest, manifestFile, interrupted }) {
|
|
669
675
|
const { artifacts, problems } = (0,_job_runner_js__WEBPACK_IMPORTED_MODULE_8__/* .loadArtifacts */ .kZ)({ store, runId, root });
|
|
670
676
|
const jobs = store.listJobs(runId);
|
|
@@ -684,14 +690,16 @@ async function finishRun({ args, root, store, runId, manifest, manifestFile, int
|
|
|
684
690
|
let discovered = null; let discoveryNote = args.flags['no-discover'] ? 'skipped (--no-discover)' : null;
|
|
685
691
|
if (!args.flags['no-discover']) { try { discovered = (0,_discover_edges_js__WEBPACK_IMPORTED_MODULE_10__/* .discoverEdges */ .U)({ manifest }); } catch (e) { discoveryNote = `failed (${String(e.message).slice(0, 80)}); the graph holds declared edges only`; } }
|
|
686
692
|
const split = discovered ? (0,_discover_edges_js__WEBPACK_IMPORTED_MODULE_10__/* .splitDeclared */ .Z)(discovered, manifest) : null;
|
|
687
|
-
const
|
|
693
|
+
const lineageByRepo = new Map([...artifacts].filter(([, a]) => a.lineage).map(([id, a]) => [id, a.lineage]));
|
|
694
|
+
const graph = (0,_enterprise_graph_js__WEBPACK_IMPORTED_MODULE_11__/* .finalizeGraph */ .Th)((0,_enterprise_graph_js__WEBPACK_IMPORTED_MODULE_11__/* .buildEnterpriseGraph */ .Ws)({ manifest, dependencyGraph: dep, discovered, lineage: lineageByRepo.size ? lineageByRepo : null }));
|
|
688
695
|
const findingsByRepo = Object.fromEntries([...artifacts].map(([id, a]) => [id, a.findings ?? []]));
|
|
689
|
-
(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.safeWriteState)((0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.statePath)(root, ENTERPRISE_FILE), JSON.stringify({ ...graph, runId, manifestFile, findingsByRepo, discovery: discovered ? { scanned: discovered.scanned, limitations: discovered.limitations, undeclared: split.undeclared.length, confirmed: split.confirmed.length } : { note: discoveryNote }, builtAt: new Date().toISOString() }));
|
|
696
|
+
(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.safeWriteState)((0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.statePath)(root, ENTERPRISE_FILE), JSON.stringify({ ...graph, runId, manifestFile, findingsByRepo, codeBoundaries: lineageByRepo.size ? { repositories: lineageByRepo.size, undeclaredDataClasses: Object.fromEntries(graph.nodes.filter((n) => n.discoveredDataClasses?.length).map((n) => [n.id, n.discoveredDataClasses])) } : { note: args.flags.lineage ? 'no repository returned a Code Boundaries graph' : 'not requested (--lineage)' }, discovery: discovered ? { scanned: discovered.scanned, limitations: discovered.limitations, undeclared: split.undeclared.length, confirmed: split.confirmed.length } : { note: discoveryNote }, builtAt: new Date().toISOString() }));
|
|
690
697
|
|
|
691
698
|
if (args.flags.json) console.log(JSON.stringify({ runId, health, rollup }, null, 2));
|
|
692
699
|
else {
|
|
693
700
|
console.log((0,_fleet_js__WEBPACK_IMPORTED_MODULE_5__/* .renderFleetSummary */ .LL)(rollup));
|
|
694
701
|
console.log(`\nRun: ${runId}\n${(0,_org_health_js__WEBPACK_IMPORTED_MODULE_9__/* .renderOrgHealth */ .Vp)(health)}`);
|
|
702
|
+
if (lineageByRepo.size) { const und = graph.nodes.filter((n) => n.discoveredDataClasses?.length); console.log(`\nCode Boundaries: ${lineageByRepo.size} repositor${lineageByRepo.size === 1 ? 'y' : 'ies'} contributed a data flow graph.${und.length ? ` Data classes found in code but not declared: ${und.map((n) => `${n.id} (${n.discoveredDataClasses.join(', ')})`).join('; ')}.` : ' No undeclared data class was found.'}`); }
|
|
695
703
|
if (split) console.log(`\nEdges found in code: ${split.undeclared.length} not in the manifest, ${split.confirmed.length} matching a declared edge. ${split.undeclared.length ? 'List them with: agentic-security fleet edges --undeclared' : ''}`.trimEnd());
|
|
696
704
|
else if (discoveryNote) console.log(`\nEdge discovery ${discoveryNote}.`);
|
|
697
705
|
if (interrupted) console.log(`\nInterrupted. Continue with: agentic-security fleet resume ${runId}`);
|
|
@@ -703,7 +711,9 @@ async function finishRun({ args, root, store, runId, manifest, manifestFile, int
|
|
|
703
711
|
async function runAll({ args, root, store, runId, manifest, manifestFile }) {
|
|
704
712
|
const ac = new AbortController(); const onSig = () => ac.abort(); process.once('SIGINT', onSig); process.once('SIGTERM', onSig);
|
|
705
713
|
try {
|
|
706
|
-
|
|
714
|
+
// Code Boundaries are requested at scan time and remembered on the run, so a resume does the same thing the scan would have
|
|
715
|
+
const lineage = !!args.flags.lineage || !!store.getRun(runId)?.meta?.lineage;
|
|
716
|
+
await (0,_job_runner_js__WEBPACK_IMPORTED_MODULE_8__/* .runJobs */ .Ag)({ manifest, store, runId, root, scan: _job_runner_js__WEBPACK_IMPORTED_MODULE_8__/* .childScan */ .IT, scanOptions: { lineage }, signal: ac.signal, useCache: !args.flags['no-cache'], onEvent: (e) => {
|
|
707
717
|
if (args.flags.json) return;
|
|
708
718
|
if (e.type === 'done') console.error(`[fleet] done: ${e.repo}${e.cached ? ' (cached)' : ''}${e.status === 'partial' ? ' (partial)' : ''}`);
|
|
709
719
|
else if (e.type === 'failed') console.error(`[fleet] ${e.timedOut ? 'TIMED OUT' : 'FAILED'}: ${e.repo} (${e.error})${e.next === 'queued' ? ', will retry' : ''}`);
|
|
@@ -725,7 +735,7 @@ async function cmdScan(args, root) {
|
|
|
725
735
|
try {
|
|
726
736
|
const manifestHash = sha(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(manifestFile));
|
|
727
737
|
console.error(`[fleet] planning ${manifest.repositories.length} repository(ies) for ${manifest.organization} (${manifest.workers} worker(s), ${manifest.retries} retr${manifest.retries === 1 ? 'y' : 'ies'}, ${Math.round(manifest.perRepoTimeoutMs / 1000)}s per repository)...`);
|
|
728
|
-
const plan = (0,_job_runner_js__WEBPACK_IMPORTED_MODULE_8__/* .planRun */ .nY)({ manifest, manifestHash, store, meta: { manifestFile } });
|
|
738
|
+
const plan = (0,_job_runner_js__WEBPACK_IMPORTED_MODULE_8__/* .planRun */ .nY)({ manifest, manifestHash, store, config: args.flags.lineage ? { lineage: true } : {}, meta: { manifestFile, lineage: !!args.flags.lineage } });
|
|
729
739
|
return await runAll({ args, root, store, runId: plan.runId, manifest, manifestFile });
|
|
730
740
|
} finally { store.close(); }
|
|
731
741
|
}
|
|
@@ -742,7 +752,7 @@ async function cmdResume(args, root) {
|
|
|
742
752
|
const hash = sha(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(root, target)));
|
|
743
753
|
run = store.listRuns().find((r) => r.manifestHash === hash && store.listJobs(r.runId).some((j) => ['queued', 'leased'].includes(j.status)));
|
|
744
754
|
if (!run) { console.error('Nothing to resume for that manifest: no run of it has unfinished work. Use `fleet resume <run-id> --retry-failed` to retry failed repositories.'); return 4; }
|
|
745
|
-
} else run = store.getRun(target);
|
|
755
|
+
} else run = store.getRun(target) ?? runOfJob(store, target);
|
|
746
756
|
if (!run) { console.error(`No run ${target}. Runs: ${store.listRuns().slice(0, 5).map((r) => r.runId).join(', ') || 'none'}`); return 4; }
|
|
747
757
|
const full = store.getRun(run.runId);
|
|
748
758
|
const manifestFile = node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(root, args.flags.manifest ?? full.meta?.manifestFile ?? (isManifestArg(target) ? target : 'fleet.yaml'));
|
|
@@ -774,14 +784,15 @@ async function cmdStatus(args, root) {
|
|
|
774
784
|
const store = await (0,_job_store_js__WEBPACK_IMPORTED_MODULE_7__/* .createSqliteJobStore */ .J6)({ file: dbFile });
|
|
775
785
|
try {
|
|
776
786
|
const arg = args._[2];
|
|
777
|
-
const run = arg && !isManifestArg(arg) ? store.getRun(String(arg)) : store.listRuns()[0] && store.getRun(store.listRuns()[0].runId);
|
|
778
|
-
if (!run) { console.error(arg && !isManifestArg(arg) ? `No run ${arg}.` : 'No fleet run exists yet.'); return 4; }
|
|
787
|
+
const run = arg && !isManifestArg(arg) ? (store.getRun(String(arg)) ?? runOfJob(store, String(arg))) : store.listRuns()[0] && store.getRun(store.listRuns()[0].runId);
|
|
788
|
+
if (!run) { console.error(arg && !isManifestArg(arg) ? `No run or job ${arg}.` : 'No fleet run exists yet.'); return 4; }
|
|
789
|
+
if (arg && !isManifestArg(arg) && String(arg) !== run.runId) console.error(`Job ${arg} belongs to run ${run.runId}.`);
|
|
779
790
|
const meta = run.meta ?? {};
|
|
780
791
|
const manifest = (0,_fleet_manifest_js__WEBPACK_IMPORTED_MODULE_3__/* .loadFleetManifest */ .Cb)(node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(root, meta.manifestFile ?? 'fleet.yaml'));
|
|
781
792
|
const jobs = store.listJobs(run.runId);
|
|
782
793
|
const m = manifest.ok ? manifest.manifest : { repositories: jobs.map((j) => ({ id: j.repoId, required: j.required })) };
|
|
783
794
|
const health = (0,_org_health_js__WEBPACK_IMPORTED_MODULE_9__/* .computeOrgHealth */ .WB)({ manifest: m, jobs });
|
|
784
|
-
if (args.flags.json) { console.log(JSON.stringify({ runId: run.runId, health, jobs: jobs.map((j) => ({ repo: j.repoId, status: j.status, attempts: j.attempts, worker: j.worker, leaseExpiresAt: j.leaseExpiresAt, error: j.error })) }, null, 2)); return 0; }
|
|
795
|
+
if (args.flags.json) { console.log(JSON.stringify({ runId: run.runId, health, jobs: jobs.map((j) => ({ jobId: j.jobId, repo: j.repoId, status: j.status, attempts: j.attempts, worker: j.worker, leaseExpiresAt: j.leaseExpiresAt, error: j.error })) }, null, 2)); return 0; }
|
|
785
796
|
console.log(`Run: ${run.runId} (${run.organization})\n${(0,_org_health_js__WEBPACK_IMPORTED_MODULE_9__/* .renderOrgHealth */ .Vp)(health)}`);
|
|
786
797
|
const live = jobs.filter((j) => j.status === 'leased');
|
|
787
798
|
if (live.length) console.log(`\nIn progress: ${live.map((j) => `${j.repoId} (attempt ${j.attempts}, lease until ${new Date(j.leaseExpiresAt).toISOString()})`).join(', ')}`);
|
|
@@ -850,6 +861,7 @@ async function cmdQuery(args, root) {
|
|
|
850
861
|
for (const p of res.paths) {
|
|
851
862
|
console.log(p.nodes.map((n) => (n === _enterprise_graph_js__WEBPACK_IMPORTED_MODULE_11__/* .INTERNET */ .aw ? 'Internet' : n)).join(' -> '));
|
|
852
863
|
if (p.discoveredEdges?.length) console.log(` rests on edge(s) found only in code, not declared: ${p.discoveredEdges.join(', ')}`);
|
|
864
|
+
if (p.discoveredDataClassNodes?.length) console.log(` reaches ${toClass} data only because the code shows it, not the manifest: ${p.discoveredDataClassNodes.join(', ')}`);
|
|
853
865
|
if (p.involving.length) console.log(` involves vulnerable code: ${p.involving.map((n) => `${n} (${involving.why.get(n)})`).join(', ')}`);
|
|
854
866
|
}
|
|
855
867
|
if (res.paths.length) console.log(`\n${res.paths.length} path(s). Reach edges are as declared in the fleet manifest, plus any marked above as found only in code.${res.truncated ? ' The search bound was reached; more may exist.' : ''}`);
|
|
@@ -1064,7 +1076,7 @@ const AUTH_CWES = new Set(['CWE-287', 'CWE-306', 'CWE-285', 'CWE-862', 'CWE-863'
|
|
|
1064
1076
|
const edgeId = (type, from, to) => `edge:${type}:${from}:${to}`;
|
|
1065
1077
|
const hash = (v) => (0,node_crypto__WEBPACK_IMPORTED_MODULE_0__.createHash)('sha256').update(JSON.stringify(v)).digest('hex');
|
|
1066
1078
|
|
|
1067
|
-
function buildEnterpriseGraph({ manifest, dependencyGraph, discovered = null }) {
|
|
1079
|
+
function buildEnterpriseGraph({ manifest, dependencyGraph, discovered = null, lineage = null }) {
|
|
1068
1080
|
const nodes = new Map();
|
|
1069
1081
|
const edges = new Map();
|
|
1070
1082
|
const addNode = (n) => nodes.set(n.id, n);
|
|
@@ -1092,7 +1104,48 @@ function buildEnterpriseGraph({ manifest, dependencyGraph, discovered = null })
|
|
|
1092
1104
|
// Edges found in the code. They share an id with a declared edge of the same type and endpoints, so the evidence list shows both; an
|
|
1093
1105
|
// edge with ONLY discovered evidence is marked `discovered: true` so a query can say it rests on a code literal, not on a declaration.
|
|
1094
1106
|
for (const d of discovered?.edges ?? []) for (const evidence of d.evidence) addEdge(d.type, d.from, d.to, evidence, true);
|
|
1095
|
-
|
|
1107
|
+
// Code Boundaries (each repository's own data flow graph, summarized): data classes the code handles, and destinations it sends to.
|
|
1108
|
+
// These add to what the manifest declared and never replace it; a class the manifest did not declare is listed on the node so a path
|
|
1109
|
+
// that exists only because of it can say so.
|
|
1110
|
+
const aliasesOf = new Map([...manifest.repositories, ...(manifest.resources ?? [])].map((n) => [n.id, [n.id, ...(n.aliases ?? [])].map((a) => String(a).toLowerCase())]));
|
|
1111
|
+
for (const [repoId, sum] of lineage ?? []) {
|
|
1112
|
+
const node = nodes.get(repoId);
|
|
1113
|
+
if (!node || !sum) continue;
|
|
1114
|
+
const declared = new Set(node.dataClasses);
|
|
1115
|
+
const found = (sum.dataClasses ?? []).filter((c) => !declared.has(c));
|
|
1116
|
+
node.dataClasses = [...declared, ...found];
|
|
1117
|
+
node.discoveredDataClasses = found;
|
|
1118
|
+
const cb = (extra) => ({ source: 'code-boundaries', repo: repoId, declared: false, file: null, ...extra });
|
|
1119
|
+
for (const d of sum.externalDestinations ?? []) {
|
|
1120
|
+
if (!d.host) continue;
|
|
1121
|
+
const first = d.host.split('.')[0];
|
|
1122
|
+
for (const [target, names] of aliasesOf) {
|
|
1123
|
+
if (target === repoId || !(names.includes(d.host) || names.includes(first))) continue;
|
|
1124
|
+
const tn = nodes.get(target);
|
|
1125
|
+
addEdge(tn?.type === 'external-provider' ? 'uses-provider' : 'calls', repoId, target, cb({ host: d.host, nodeKind: 'external' }), true);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
for (const q of sum.queues ?? []) {
|
|
1129
|
+
for (const [target, names] of aliasesOf) {
|
|
1130
|
+
const tn = nodes.get(target);
|
|
1131
|
+
if (tn?.type !== 'queue' || !names.includes(String(q.topic).toLowerCase())) continue;
|
|
1132
|
+
if (q.operation === 'consume') addEdge('consumes', target, repoId, cb({ topic: q.topic }), true); else addEdge('produces', repoId, target, cb({ topic: q.topic }), true);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
for (const st of sum.stores ?? []) {
|
|
1136
|
+
for (const key of [st.host, st.database]) {
|
|
1137
|
+
if (!key) continue;
|
|
1138
|
+
const k = String(key).toLowerCase();
|
|
1139
|
+
for (const [target, names] of aliasesOf) {
|
|
1140
|
+
const tn = nodes.get(target);
|
|
1141
|
+
if (tn?.type !== 'database' || !(names.includes(k) || names.includes(k.split('.')[0]))) continue;
|
|
1142
|
+
addEdge('connects-to', repoId, target, cb({ store: st.subtype, name: key }), true);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const FOUND_IN_CODE = new Set(['discovered-code-literal', 'code-boundaries']);
|
|
1148
|
+
for (const e of edges.values()) e.discovered = e.evidence.every((x) => FOUND_IN_CODE.has(x.source));
|
|
1096
1149
|
return { schema: ENTERPRISE_GRAPH_SCHEMA, version: 1, organization: manifest.organization, nodes: [...nodes.values()], edges: [...edges.values()], digest: null };
|
|
1097
1150
|
}
|
|
1098
1151
|
|
|
@@ -1139,7 +1192,7 @@ function attackPaths({ graph, toDataClass, from = INTERNET, involving = null, ma
|
|
|
1139
1192
|
if (results.length >= maxPaths) { truncated = true; return; }
|
|
1140
1193
|
if (targets.has(id) && nodePath.length > 1) {
|
|
1141
1194
|
const inv = involving ? nodePath.filter((n) => involving.all.has(n)) : [];
|
|
1142
|
-
if (!involving || inv.length) results.push({ nodes: [...nodePath], edges: edgePath.map((e) => e.id), involving: inv, discoveredEdges: edgePath.filter((e) => e.discovered).map((e) => e.id), evidence: edgePath.flatMap((e) => e.evidence.map((x) => ({ edge: e.id, ...x }))) });
|
|
1195
|
+
if (!involving || inv.length) results.push({ nodes: [...nodePath], edges: edgePath.map((e) => e.id), involving: inv, discoveredEdges: edgePath.filter((e) => e.discovered).map((e) => e.id), discoveredDataClassNodes: nodePath.filter((n) => (nodeOf(graph, n)?.discoveredDataClasses ?? []).includes(toDataClass)), evidence: edgePath.flatMap((e) => e.evidence.map((x) => ({ edge: e.id, ...x }))) });
|
|
1143
1196
|
}
|
|
1144
1197
|
if (nodePath.length > maxDepth) { truncated = true; return; }
|
|
1145
1198
|
for (const e of out.get(id) ?? []) if (!nodePath.includes(e.to)) walk(e.to, [...nodePath, e.to], [...edgePath, e]);
|
|
@@ -1540,8 +1593,8 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
1540
1593
|
const DEFAULTS = Object.freeze({ leaseMs: 30_000, heartbeatMs: 10_000, retryDelayMs: 2_000, pollMs: 100 });
|
|
1541
1594
|
|
|
1542
1595
|
/** The default scan: one supervised child process per repository. Throws `{code: 'TIMEOUT'|'ERROR', message}`. */
|
|
1543
|
-
async function childScan(repoPath, { timeoutMs, signal }) {
|
|
1544
|
-
const r = await (0,_sandbox_supervise_js__WEBPACK_IMPORTED_MODULE_5__/* .superviseSpawn */ .u)(process.execPath, [SCAN_CHILD, repoPath], { cwd: repoPath, env: { ...process.env, AGENTIC_SECURITY_NO_STATE: '1' }, timeoutMs, signal, maxOutputBytes: 96 * 1024 * 1024 });
|
|
1596
|
+
async function childScan(repoPath, { timeoutMs, signal, lineage = false }) {
|
|
1597
|
+
const r = await (0,_sandbox_supervise_js__WEBPACK_IMPORTED_MODULE_5__/* .superviseSpawn */ .u)(process.execPath, [SCAN_CHILD, repoPath], { cwd: repoPath, env: { ...process.env, AGENTIC_SECURITY_NO_STATE: '1', ...(lineage ? { AGENTIC_SECURITY_LINEAGE_DEEP: '1' } : {}) }, timeoutMs, signal, maxOutputBytes: 96 * 1024 * 1024 });
|
|
1545
1598
|
const fail = (code, message) => Object.assign(new Error(message), { code });
|
|
1546
1599
|
if (r.timedOut) throw fail('TIMEOUT', `no result within ${Math.round(timeoutMs / 1000)}s`);
|
|
1547
1600
|
if (r.cancelled) throw fail('CANCELLED', 'cancelled');
|
|
@@ -1584,7 +1637,7 @@ function planRun({ manifest, manifestHash, store, runId = null, config = {}, now
|
|
|
1584
1637
|
* Run (or continue) a planned run until no job is queued or leased by a live worker. Returns when the work is done, the signal
|
|
1585
1638
|
* aborts, or `maxWaitMs` passes with only other workers' leases outstanding.
|
|
1586
1639
|
*/
|
|
1587
|
-
async function runJobs({ manifest, store, runId, root, scan = childScan, workers = manifest.workers, leaseMs = DEFAULTS.leaseMs, heartbeatMs = DEFAULTS.heartbeatMs, retryDelayMs = DEFAULTS.retryDelayMs, pollMs = DEFAULTS.pollMs, maxWaitMs = null, signal, now = () => Date.now(), onEvent = () => {}, cacheDir = null, artifactDir = null, useCache = true }) {
|
|
1640
|
+
async function runJobs({ manifest, store, runId, root, scan = childScan, scanOptions = {}, workers = manifest.workers, leaseMs = DEFAULTS.leaseMs, heartbeatMs = DEFAULTS.heartbeatMs, retryDelayMs = DEFAULTS.retryDelayMs, pollMs = DEFAULTS.pollMs, maxWaitMs = null, signal, now = () => Date.now(), onEvent = () => {}, cacheDir = null, artifactDir = null, useCache = true }) {
|
|
1588
1641
|
const repoById = new Map(manifest.repositories.map((r) => [r.id, r]));
|
|
1589
1642
|
const cacheD = cacheDir ?? (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.statePath)(root, 'fleet', 'cache');
|
|
1590
1643
|
const artD = artifactDir ?? (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.statePath)(root, 'fleet', 'artifacts', runId);
|
|
@@ -1617,7 +1670,7 @@ async function runJobs({ manifest, store, runId, root, scan = childScan, workers
|
|
|
1617
1670
|
onEvent({ type: 'start', repo: job.repoId, attempt: job.attempts });
|
|
1618
1671
|
const cached = useCache ? (0,_scan_cache_js__WEBPACK_IMPORTED_MODULE_7__/* .readCache */ .xe)(cacheD, job.cacheKey) : null;
|
|
1619
1672
|
if (cached) {
|
|
1620
|
-
const a = writeArtifact(job, { entry: cached.entry, findings: cached.findings, findingsTruncated: !!cached.findingsTruncated, cached: true });
|
|
1673
|
+
const a = writeArtifact(job, { entry: cached.entry, findings: cached.findings, findingsTruncated: !!cached.findingsTruncated, lineage: cached.lineage ?? null, cached: true });
|
|
1621
1674
|
const status = cached.entry?.scanHealth && cached.entry.scanHealth.status !== 'complete' ? 'partial' : 'succeeded';
|
|
1622
1675
|
if (store.complete(job.jobId, job.epoch, { status, artifactSha256: a.sha256, summary: { total: cached.entry?.total ?? null, cached: true, artifact: node_path__WEBPACK_IMPORTED_MODULE_2__.basename(a.file) }, now: now() })) onEvent({ type: 'done', repo: job.repoId, cached: true, status });
|
|
1623
1676
|
continue;
|
|
@@ -1628,15 +1681,15 @@ async function runJobs({ manifest, store, runId, root, scan = childScan, workers
|
|
|
1628
1681
|
let lost = false;
|
|
1629
1682
|
const hb = setInterval(() => { if (!store.heartbeat(job.jobId, job.epoch, { now: now(), leaseMs })) { lost = true; ac.abort(); } }, heartbeatMs);
|
|
1630
1683
|
try {
|
|
1631
|
-
const out = await scan(repo.path, { timeoutMs: job.timeoutMs, signal: ac.signal });
|
|
1684
|
+
const out = await scan(repo.path, { timeoutMs: job.timeoutMs, signal: ac.signal, ...scanOptions });
|
|
1632
1685
|
clearInterval(hb);
|
|
1633
1686
|
if (lost) { onEvent({ type: 'fenced', repo: job.repoId }); continue; }
|
|
1634
1687
|
if (!out?.entry) throw Object.assign(new Error('the scan returned no entry'), { code: 'ERROR' });
|
|
1635
1688
|
if (out.entry.ok === false) throw Object.assign(new Error(out.entry.error || 'the scan failed'), { code: 'ERROR' });
|
|
1636
|
-
const a = writeArtifact(job, { entry: out.entry, findings: out.findings ?? [], findingsTruncated: !!out.findingsTruncated });
|
|
1689
|
+
const a = writeArtifact(job, { entry: out.entry, findings: out.findings ?? [], findingsTruncated: !!out.findingsTruncated, lineage: out.lineage ?? null });
|
|
1637
1690
|
const status = out.entry.scanHealth && out.entry.scanHealth.status !== 'complete' ? 'partial' : 'succeeded';
|
|
1638
1691
|
if (store.complete(job.jobId, job.epoch, { status, artifactSha256: a.sha256, summary: { total: out.entry.total ?? null, cached: false, artifact: node_path__WEBPACK_IMPORTED_MODULE_2__.basename(a.file) }, now: now() })) {
|
|
1639
|
-
if (useCache && status === 'succeeded') (0,_scan_cache_js__WEBPACK_IMPORTED_MODULE_7__/* .writeCache */ .aR)(cacheD, job.cacheKey, { entry: out.entry, findings: out.findings ?? [], findingsTruncated: !!out.findingsTruncated });
|
|
1692
|
+
if (useCache && status === 'succeeded') (0,_scan_cache_js__WEBPACK_IMPORTED_MODULE_7__/* .writeCache */ .aR)(cacheD, job.cacheKey, { entry: out.entry, findings: out.findings ?? [], findingsTruncated: !!out.findingsTruncated, lineage: out.lineage ?? null });
|
|
1640
1693
|
onEvent({ type: 'done', repo: job.repoId, cached: false, status });
|
|
1641
1694
|
} else onEvent({ type: 'fenced', repo: job.repoId });
|
|
1642
1695
|
} catch (e) {
|
|
@@ -1773,7 +1826,9 @@ async function createSqliteJobStore({ file }) {
|
|
|
1773
1826
|
const { DatabaseSync } = await Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 80099, 19));
|
|
1774
1827
|
if (file !== ':memory:') node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(node_path__WEBPACK_IMPORTED_MODULE_1__.dirname(file), { recursive: true, mode: 0o700 });
|
|
1775
1828
|
const db = new DatabaseSync(file);
|
|
1776
|
-
|
|
1829
|
+
// the busy timeout comes FIRST: the very next statements (switching the journal mode, creating tables) take locks, and another process
|
|
1830
|
+
// opening the same file at the same instant must wait for them instead of failing with "database is locked"
|
|
1831
|
+
db.exec('PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;');
|
|
1777
1832
|
db.exec(SCHEMA);
|
|
1778
1833
|
const tx = (fn) => { db.exec('BEGIN IMMEDIATE'); try { const r = fn(); db.exec('COMMIT'); return r; } catch (e) { try { db.exec('ROLLBACK'); } catch { /* already rolled back */ } throw e; } };
|
|
1779
1834
|
const q = (sql, ...p) => db.prepare(sql).all(...p);
|
|
@@ -1820,6 +1875,71 @@ async function createSqliteJobStore({ file }) {
|
|
|
1820
1875
|
}
|
|
1821
1876
|
|
|
1822
1877
|
|
|
1878
|
+
/***/ }),
|
|
1879
|
+
|
|
1880
|
+
/***/ 81010:
|
|
1881
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
1882
|
+
|
|
1883
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
1884
|
+
/* harmony export */ h: () => (/* binding */ summarizeLineage)
|
|
1885
|
+
/* harmony export */ });
|
|
1886
|
+
// A bounded summary of one repository's Code Boundaries graph (the Data Flow Explorer's `DataFlowGraph v1`), for the enterprise graph
|
|
1887
|
+
// (PRD 4: "federate Code Boundaries across repositories"). The whole graph is not copied: a fleet of hundreds of repositories does not
|
|
1888
|
+
// need each node, only what crosses a repository boundary or changes what the organization knows about it:
|
|
1889
|
+
// * the data classes that appear in the code (so a repository holding payment data is known to, even if the manifest forgot to say);
|
|
1890
|
+
// * the external destinations the code sends data to, by host;
|
|
1891
|
+
// * the queue topics it publishes to or consumes from;
|
|
1892
|
+
// * the stores it touches, by host/database/table name;
|
|
1893
|
+
// * counts, including flows that carry sensitive classes across a trust boundary.
|
|
1894
|
+
// Pure; hostile input (the graph came out of a scan of someone's code), so every string is bounded and shape-checked.
|
|
1895
|
+
const MAX_ITEMS = 64;
|
|
1896
|
+
const SENSITIVE = new Set(['pii', 'phi', 'pci', 'fin', 'credentials']);
|
|
1897
|
+
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
1898
|
+
const clean = (v, max = 120) => (typeof v === 'string' && v.length > 0 && v.length <= max && !/[\s\u0000-\u001f]/.test(v) ? v : null);
|
|
1899
|
+
|
|
1900
|
+
function hostOf(literal) {
|
|
1901
|
+
if (typeof literal !== 'string') return null;
|
|
1902
|
+
try { const h = new URL(literal).hostname.toLowerCase(); return clean(h, 253); } catch { /* not a URL */ }
|
|
1903
|
+
const bare = literal.toLowerCase().replace(/:\d+$/, '');
|
|
1904
|
+
return /^[a-z0-9][a-z0-9.-]{0,252}$/.test(bare) ? bare : null;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
function summarizeLineage(graph) {
|
|
1908
|
+
if (!graph || typeof graph !== 'object') return null;
|
|
1909
|
+
const classes = new Set();
|
|
1910
|
+
for (const d of arr(graph.dataElements)) for (const c of arr(d?.dataClasses)) { if (typeof c !== 'string') continue; const l = clean(c.toLowerCase(), 31); if (l && /^[a-z][a-z0-9-]*$/.test(l)) classes.add(l); }
|
|
1911
|
+
const ext = new Map(); const queues = new Map(); const stores = new Map();
|
|
1912
|
+
for (const n of arr(graph.nodes)) {
|
|
1913
|
+
if (!n || typeof n !== 'object') continue;
|
|
1914
|
+
if (n.kind === 'external') {
|
|
1915
|
+
const host = hostOf(n.destination?.literalValue);
|
|
1916
|
+
const key = `${n.subtype ?? 'external'}|${host ?? ''}`;
|
|
1917
|
+
if (!ext.has(key) && ext.size < MAX_ITEMS) ext.set(key, { subtype: clean(n.subtype, 60) ?? 'external', host });
|
|
1918
|
+
} else if (n.kind === 'queue') {
|
|
1919
|
+
const topic = clean(n.queueDetail?.topic, 120); const op = n.queueDetail?.operation === 'consume' || n.queueDetail?.operation === 'receive' ? 'consume' : 'publish';
|
|
1920
|
+
if (topic && queues.size < MAX_ITEMS) queues.set(`${topic}|${op}`, { topic, operation: op });
|
|
1921
|
+
} else if (n.kind === 'store') {
|
|
1922
|
+
const d = n.storeDetail ?? {};
|
|
1923
|
+
const item = { subtype: clean(n.subtype, 60) ?? 'store', host: hostOf(d.host), database: clean(d.database, 120), table: clean(d.table, 120) };
|
|
1924
|
+
const key = JSON.stringify(item);
|
|
1925
|
+
if (stores.size < MAX_ITEMS) stores.set(key, item);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
const edges = arr(graph.edges);
|
|
1929
|
+
const crossing = new Set(edges.filter((e) => arr(e?.boundaryCrossings).length).map((e) => e.id));
|
|
1930
|
+
const dataById = new Map(arr(graph.dataElements).map((d) => [d?.id, d]));
|
|
1931
|
+
const sensitiveCrossing = arr(graph.flows).filter((f) => arr(f?.edgeIds).some((id) => crossing.has(id)) && arr(f?.dataElementIds).some((id) => arr(dataById.get(id)?.dataClasses).some((c) => SENSITIVE.has(String(c).toLowerCase())))).length;
|
|
1932
|
+
return {
|
|
1933
|
+
schema: 'agentic-security/fleet-lineage-summary', version: 1,
|
|
1934
|
+
dataClasses: [...classes].sort().slice(0, MAX_ITEMS),
|
|
1935
|
+
externalDestinations: [...ext.values()],
|
|
1936
|
+
queues: [...queues.values()],
|
|
1937
|
+
stores: [...stores.values()],
|
|
1938
|
+
counts: { nodes: arr(graph.nodes).length, edges: edges.length, flows: arr(graph.flows).length, boundaryCrossingEdges: crossing.size, sensitiveFlowsCrossingBoundary: sensitiveCrossing },
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
|
|
1823
1943
|
/***/ }),
|
|
1824
1944
|
|
|
1825
1945
|
/***/ 75435:
|
|
@@ -2033,6 +2153,7 @@ __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependen
|
|
|
2033
2153
|
/* harmony export */ });
|
|
2034
2154
|
/* harmony import */ var _fleet_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(80313);
|
|
2035
2155
|
/* harmony import */ var _runScan_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45950);
|
|
2156
|
+
/* harmony import */ var _lineage_summary_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(81010);
|
|
2036
2157
|
var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([_runScan_js__WEBPACK_IMPORTED_MODULE_1__]);
|
|
2037
2158
|
_runScan_js__WEBPACK_IMPORTED_MODULE_1__ = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[0];
|
|
2038
2159
|
// Child-process entry for ONE repository's scan inside a fleet run (PRD 4, requirement 4: one hung repository must not stop the
|
|
@@ -2046,6 +2167,7 @@ _runScan_js__WEBPACK_IMPORTED_MODULE_1__ = (__webpack_async_dependencies__.then
|
|
|
2046
2167
|
|
|
2047
2168
|
|
|
2048
2169
|
|
|
2170
|
+
|
|
2049
2171
|
const BEGIN = '@@FLEET-SCAN-BEGIN@@';
|
|
2050
2172
|
const END = '@@FLEET-SCAN-END@@';
|
|
2051
2173
|
const MAX_FINDINGS = 5000;
|
|
@@ -2058,7 +2180,9 @@ async function main(repoPath) {
|
|
|
2058
2180
|
const entry = res.results?.[0];
|
|
2059
2181
|
if (!entry) throw new Error('the scan produced no entry');
|
|
2060
2182
|
const findings = (captured?.findings ?? []).slice(0, MAX_FINDINGS).map(slim);
|
|
2061
|
-
|
|
2183
|
+
// the Code Boundaries graph exists only when the controller asked for it (AGENTIC_SECURITY_LINEAGE_DEEP=1); it is summarized, never copied whole
|
|
2184
|
+
const lineage = process.env.AGENTIC_SECURITY_LINEAGE_DEEP === '1' ? (0,_lineage_summary_js__WEBPACK_IMPORTED_MODULE_2__/* .summarizeLineage */ .h)(captured?.lineageGraph) : null;
|
|
2185
|
+
process.stdout.write(`${BEGIN}${JSON.stringify({ entry, findings, findingsTruncated: (captured?.findings?.length ?? 0) > MAX_FINDINGS, lineage })}${END}`);
|
|
2062
2186
|
}
|
|
2063
2187
|
|
|
2064
2188
|
if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
|