@clear-capabilities/agentic-security-scanner 0.145.0 → 0.147.5
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 +266 -0
- package/bin/agentic-security.js +3508 -69
- package/dist/1122.index.js +779 -0
- package/dist/{301.index.js → 1301.index.js} +2 -2
- package/dist/1379.index.js +591 -0
- package/dist/{444.index.js → 1444.index.js} +2 -2
- package/dist/{660.index.js → 1660.index.js} +2 -2
- package/dist/{700.index.js → 1700.index.js} +2 -2
- package/dist/{905.index.js → 1905.index.js} +3 -3
- package/dist/{920.index.js → 1920.index.js} +3 -3
- package/dist/{238.index.js → 2238.index.js} +3 -3
- package/dist/{271.index.js → 2271.index.js} +2 -2
- package/dist/{985.index.js → 2376.index.js} +1260 -340
- package/dist/2432.index.js +793 -0
- package/dist/2659.index.js +93 -0
- package/dist/{826.index.js → 2826.index.js} +2 -2
- package/dist/{830.index.js → 2830.index.js} +2 -2
- package/dist/2923.index.js +298 -0
- package/dist/{1.index.js → 3001.index.js} +5 -5
- package/dist/{117.index.js → 3117.index.js} +3 -3
- package/dist/3180.index.js +379 -0
- package/dist/3276.index.js +117 -0
- package/dist/{415.index.js → 3415.index.js} +2 -2
- package/dist/{499.index.js → 3499.index.js} +2 -2
- package/dist/3518.index.js +450 -0
- package/dist/{526.index.js → 3526.index.js} +5 -5
- package/dist/{736.index.js → 3736.index.js} +4 -4
- package/dist/{839.index.js → 3839.index.js} +4 -4
- package/dist/{113.index.js → 4113.index.js} +5 -5
- package/dist/{265.index.js → 4265.index.js} +2 -2
- package/dist/{384.index.js → 4384.index.js} +3 -3
- package/dist/4547.index.js +268 -0
- package/dist/4863.index.js +422 -0
- package/dist/{970.index.js → 4970.index.js} +2 -2
- package/dist/5051.index.js +841 -0
- package/dist/{144.index.js → 5144.index.js} +5 -5
- package/dist/{333.index.js → 5333.index.js} +3 -3
- package/dist/5343.index.js +185 -0
- package/dist/5350.index.js +866 -0
- package/dist/5561.index.js +436 -0
- package/dist/{637.index.js → 5637.index.js} +3 -3
- package/dist/{449.index.js → 5830.index.js} +2 -2
- package/dist/6626.index.js +532 -0
- package/dist/6662.index.js +297 -0
- package/dist/{675.index.js → 6675.index.js} +5 -5
- package/dist/{730.index.js → 6730.index.js} +6 -6
- package/dist/6829.index.js +225 -0
- package/dist/6944.index.js +130 -0
- package/dist/{178.index.js → 7178.index.js} +3 -3
- package/dist/{227.index.js → 7227.index.js} +2 -2
- package/dist/7310.index.js +520 -0
- package/dist/{552.index.js → 7552.index.js} +4 -4
- package/dist/7709.index.js +78 -0
- package/dist/8218.index.js +160 -0
- package/dist/{476.index.js → 8476.index.js} +4 -4
- package/dist/{513.index.js → 8513.index.js} +5 -5
- package/dist/{520.index.js → 8520.index.js} +2 -2
- package/dist/{718.index.js → 8718.index.js} +2 -2
- package/dist/{752.index.js → 8752.index.js} +2 -2
- package/dist/8846.index.js +100 -0
- package/dist/{435.index.js → 9091.index.js} +560 -189
- package/dist/{207.index.js → 9207.index.js} +2 -2
- package/dist/{220.index.js → 9220.index.js} +2 -2
- package/dist/9390.index.js +163 -0
- package/dist/{503.index.js → 9503.index.js} +2 -2
- package/dist/{801.index.js → 9801.index.js} +2 -2
- package/dist/{824.index.js → 9824.index.js} +2 -2
- package/dist/agentic-security.mjs +16 -16
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/hipaa-security-rule.json +3 -2
- package/dist/frontend/index.html +21 -0
- package/dist/frontend/src/app.js +176 -0
- package/dist/frontend/src/components/evidence-inspector.js +141 -0
- package/dist/frontend/src/components/filter-rail.js +119 -0
- package/dist/frontend/src/components/query-bar.js +126 -0
- package/dist/frontend/src/data/flagship-graph.js +1460 -0
- package/dist/frontend/src/export-entry.js +36 -0
- package/dist/frontend/src/lib/api-client.js +92 -0
- package/dist/frontend/src/lib/contrast.js +34 -0
- package/dist/frontend/src/lib/dom.js +24 -0
- package/dist/frontend/src/lib/escape-html.js +16 -0
- package/dist/frontend/src/lib/flow-path.js +40 -0
- package/dist/frontend/src/lib/focus-controls.js +149 -0
- package/dist/frontend/src/lib/protection-visual.js +46 -0
- package/dist/frontend/src/lib/query-language.js +240 -0
- package/dist/frontend/src/lib/row-filters.js +43 -0
- package/dist/frontend/src/lib/state.js +84 -0
- package/dist/frontend/src/main.js +83 -0
- package/dist/frontend/src/shell.js +184 -0
- package/dist/frontend/src/views/architecture-view.js +798 -0
- package/dist/frontend/src/views/inventory-view.js +292 -0
- package/dist/frontend/src/views/privacy-view.js +172 -0
- package/dist/frontend/src/views/trace-view.js +206 -0
- package/dist/frontend/styles/architecture-view.css +93 -0
- package/dist/frontend/styles/filter-rail.css +34 -0
- package/dist/frontend/styles/inspector.css +69 -0
- package/dist/frontend/styles/inventory-view.css +74 -0
- package/dist/frontend/styles/privacy-view.css +86 -0
- package/dist/frontend/styles/query-bar.css +107 -0
- package/dist/frontend/styles/shell.css +155 -0
- package/dist/frontend/styles/tokens.css +128 -0
- package/dist/frontend/styles/trace-view.css +95 -0
- package/package.json +13 -6
- package/src/dataflow/CLAUDE.md +1 -1
- package/src/dataflow/catalog.js +42 -0
- package/src/dataflow/orm-write-catalog.js +175 -0
- package/src/engine.js +92 -1
- package/src/ir/CLAUDE.md +1 -0
- package/src/ir/chrome-probe.mjs +150 -0
- package/src/ir/parser-js.js +94 -7
- package/src/lineage/CLAUDE.md +1203 -0
- package/src/lineage/DESIGN_DESTINATION_RESOLVER.md +156 -0
- package/src/lineage/DESIGN_GRAPH_BUILDER.md +938 -0
- package/src/lineage/DESIGN_HANDLING_ANALYZER.md +355 -0
- package/src/lineage/DESIGN_INTRAPROCEDURAL.md +628 -0
- package/src/lineage/DESIGN_PATH_PROVENANCE.md +3451 -0
- package/src/lineage/DESIGN_QUEUE_DETAIL.md +120 -0
- package/src/lineage/DESIGN_REGISTRIES.md +880 -0
- package/src/lineage/DESIGN_STORE_DETAIL.md +143 -0
- package/src/lineage/DESIGN_TRANSIT_PROTECTION.md +245 -0
- package/src/lineage/classification.js +56 -0
- package/src/lineage/coverage.js +658 -0
- package/src/lineage/cross-repo-link.js +107 -0
- package/src/lineage/dataflow-graph.schema.json +184 -0
- package/src/lineage/decision-story.js +206 -0
- package/src/lineage/drift-policy.js +279 -0
- package/src/lineage/driver.js +135 -0
- package/src/lineage/engine.js +992 -0
- package/src/lineage/export-briefing.js +628 -0
- package/src/lineage/export-csv.js +62 -0
- package/src/lineage/export-json.js +238 -0
- package/src/lineage/export-privacy.js +258 -0
- package/src/lineage/federation-loader.js +111 -0
- package/src/lineage/field-identity.js +78 -0
- package/src/lineage/fixtures/build-flagship-fixture.mjs +272 -0
- package/src/lineage/fixtures/flagship-graph.json +1453 -0
- package/src/lineage/flow-grade.js +221 -0
- package/src/lineage/governance-edit.js +169 -0
- package/src/lineage/graph-builder.js +1114 -0
- package/src/lineage/graph-diff.js +431 -0
- package/src/lineage/graph-snapshot.js +180 -0
- package/src/lineage/handling-analyzer.js +168 -0
- package/src/lineage/ids.js +349 -0
- package/src/lineage/impact-assessment.js +76 -0
- package/src/lineage/impact-engine.js +268 -0
- package/src/lineage/index.js +281 -0
- package/src/lineage/language-coverage-tiers.js +58 -0
- package/src/lineage/obligation-mapping.js +126 -0
- package/src/lineage/obligation-predicates.js +235 -0
- package/src/lineage/observation-adapters.js +282 -0
- package/src/lineage/observation-correlation.js +622 -0
- package/src/lineage/observation-store.js +497 -0
- package/src/lineage/path-query.js +410 -0
- package/src/lineage/path-store.js +400 -0
- package/src/lineage/protection.js +53 -0
- package/src/lineage/recipient-profile.js +192 -0
- package/src/lineage/recipient-registry.js +394 -0
- package/src/lineage/redact-graph.js +224 -0
- package/src/lineage/remediation.js +417 -0
- package/src/lineage/resolve-destination.js +91 -0
- package/src/lineage/runtime-observation.js +464 -0
- package/src/lineage/scenario-diff.js +84 -0
- package/src/lineage/scenario-engine.js +251 -0
- package/src/lineage/scenario.js +101 -0
- package/src/lineage/schema.js +167 -0
- package/src/lineage/sink-registry.js +427 -0
- package/src/lineage/source-registry.js +357 -0
- package/src/lineage/source-seeding.js +212 -0
- package/src/lineage/summaries.js +590 -0
- package/src/lineage/transform-catalog.js +397 -0
- package/src/lineage/transit-protection.js +150 -0
- package/src/lineage/validate.js +285 -0
- package/src/mcp/CLAUDE.md +7 -1
- package/src/mcp/dataflow-tools.js +160 -0
- package/src/mcp/server.js +1 -1
- package/src/mcp/tools.js +2 -1
- package/src/pipeline/scan-health.js +19 -1
- package/src/posture/CLAUDE.md +19 -0
- package/src/posture/artifact-registry.js +52 -0
- package/src/posture/auditor-walkthrough.js +76 -0
- package/src/posture/compliance-frameworks/hipaa-security-rule.json +3 -2
- package/src/posture/obligation-evidence-pack.js +202 -0
- package/src/posture/remediation-ledger.js +337 -0
- package/src/server/CLAUDE.md +47 -0
- package/src/server/graph-loader.js +141 -0
- package/src/server/http-server.js +325 -0
- package/src/server/routes.js +129 -0
- package/src/server/security.js +111 -0
- package/src/server/static-assets.js +144 -0
- package/src/shared/frontend-root.js +52 -0
- package/dist/11.index.js +0 -353
- package/dist/259.index.js +0 -975
- package/dist/317.index.js +0 -300
- package/dist/609.index.js +0 -741
- package/dist/838.index.js +0 -152
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const id =
|
|
2
|
-
export const ids = [
|
|
1
|
+
export const id = 7552;
|
|
2
|
+
export const ids = [7552];
|
|
3
3
|
export const modules = {
|
|
4
4
|
|
|
5
5
|
/***/ 7552:
|
|
@@ -64,7 +64,7 @@ function buildDeletionReport({ mode, dryRun, root, items, preserved }) {
|
|
|
64
64
|
|
|
65
65
|
function writeDeletionReport(scanRoot, report) {
|
|
66
66
|
try {
|
|
67
|
-
const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_0__
|
|
67
|
+
const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_0__.statePath)(scanRoot, DELETION_REPORT_FILE);
|
|
68
68
|
return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_0__/* .safeWriteState */ .Ep)(fp, JSON.stringify(report, null, 2) + '\n') ? fp : null;
|
|
69
69
|
} catch { return null; }
|
|
70
70
|
}
|
|
@@ -86,7 +86,7 @@ function buildExportReport({ root, outDir, items }) {
|
|
|
86
86
|
|
|
87
87
|
function writeExportReport(scanRoot, report) {
|
|
88
88
|
try {
|
|
89
|
-
const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_0__
|
|
89
|
+
const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_0__.statePath)(scanRoot, EXPORT_REPORT_FILE);
|
|
90
90
|
return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_0__/* .safeWriteState */ .Ep)(fp, JSON.stringify(report, null, 2) + '\n') ? fp : null;
|
|
91
91
|
} catch { return null; }
|
|
92
92
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export const id = 7709;
|
|
2
|
+
export const ids = [7709];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 7709:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ exportFlowsCSV: () => (/* binding */ exportFlowsCSV)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* harmony import */ var _protection_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(965);
|
|
12
|
+
// export-csv.js — Milestone 4, sub-project JSON/CSV export.
|
|
13
|
+
//
|
|
14
|
+
// One row per FLOW (not node, not edge — the closest analogue in this
|
|
15
|
+
// domain to report/index.js's own toCSV's "one row per finding", per this
|
|
16
|
+
// sub-project's own scoping doc decision 3). Node/edge CSV exports are
|
|
17
|
+
// deferred, named explicitly in that doc, not attempted here.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Same escaping convention as report/index.js's own toCSV — quote only
|
|
22
|
+
// when a comma/quote/newline is present, double embedded quotes.
|
|
23
|
+
function esc(v) {
|
|
24
|
+
if (v == null) return '';
|
|
25
|
+
const s = String(v);
|
|
26
|
+
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Join character for a flow's multiple `dataClasses` values in one CSV
|
|
30
|
+
// cell: `;`, not `,`. Chosen (this module's own decision, not prescribed
|
|
31
|
+
// by the plan) because `,` is CSV's own delimiter — joining multiple
|
|
32
|
+
// classes with it would put a raw delimiter inside a single logical cell
|
|
33
|
+
// value, forcing outer quoting for the ordinary multi-class case (e.g. a
|
|
34
|
+
// field classified both PCI and PII). `;` never collides with the CSV
|
|
35
|
+
// grammar, so the common case ("PCI;PII") never needs quoting at all.
|
|
36
|
+
// `esc()` still quotes correctly if a comma ends up in cell content for
|
|
37
|
+
// any other reason (see export-csv.test.js's own escaping test) — this
|
|
38
|
+
// choice only avoids manufacturing that case in the common path.
|
|
39
|
+
const DATA_CLASSES_JOIN = ';';
|
|
40
|
+
|
|
41
|
+
function _dataClassesForFlow(flow, dataElementsById) {
|
|
42
|
+
const classes = new Set();
|
|
43
|
+
for (const deId of flow.dataElementIds ?? []) {
|
|
44
|
+
const de = dataElementsById.get(deId);
|
|
45
|
+
for (const c of de?.dataClasses ?? []) classes.add(c);
|
|
46
|
+
}
|
|
47
|
+
return [...classes].sort();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function _dimensionVerdict(flow, edgesById, dimension) {
|
|
51
|
+
const verdicts = (flow.edgeIds ?? [])
|
|
52
|
+
.map((id) => edgesById.get(id)?.protection?.[dimension]?.verdict)
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
return (0,_protection_js__WEBPACK_IMPORTED_MODULE_0__/* .aggregateVerdicts */ .SX)(verdicts);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function exportFlowsCSV(graph) {
|
|
58
|
+
const dataElementsById = new Map((graph.dataElements ?? []).map((d) => [d.id, d]));
|
|
59
|
+
const edgesById = new Map((graph.edges ?? []).map((e) => [e.id, e]));
|
|
60
|
+
const header = ['id', 'source', 'sink', 'dataClasses', 'transitVerdict', 'atRestVerdict', 'handlingVerdict', 'policyVerdict', 'coverageStatus'];
|
|
61
|
+
const rows = [header.join(',')];
|
|
62
|
+
for (const flow of graph.flows ?? []) {
|
|
63
|
+
rows.push([
|
|
64
|
+
esc(flow.id), esc(flow.source), esc(flow.sink),
|
|
65
|
+
esc(_dataClassesForFlow(flow, dataElementsById).join(DATA_CLASSES_JOIN)),
|
|
66
|
+
esc(_dimensionVerdict(flow, edgesById, 'transit')),
|
|
67
|
+
esc(_dimensionVerdict(flow, edgesById, 'atRest')),
|
|
68
|
+
esc(_dimensionVerdict(flow, edgesById, 'handling')),
|
|
69
|
+
esc(flow.policyVerdict), esc(flow.coverageStatus),
|
|
70
|
+
].join(','));
|
|
71
|
+
}
|
|
72
|
+
return rows.join('\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
/***/ })
|
|
77
|
+
|
|
78
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const id = 8218;
|
|
2
|
+
export const ids = [8218];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 8218:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ loadFreshLineageGraph: () => (/* binding */ loadFreshLineageGraph),
|
|
10
|
+
/* harmony export */ loadSignedGraph: () => (/* binding */ loadSignedGraph)
|
|
11
|
+
/* harmony export */ });
|
|
12
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
13
|
+
/* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1174);
|
|
14
|
+
/* harmony import */ var _posture_integrity_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1130);
|
|
15
|
+
// graph-loader.js — Milestone 3, sub-project Server, increment 1.
|
|
16
|
+
//
|
|
17
|
+
// Reads and VERIFIES the `.agentic-security/lineage-graph.json` artifact
|
|
18
|
+
// before `explore` is allowed to serve a single byte of it. Reuses
|
|
19
|
+
// `posture/integrity.js`'s `verifyLastScan` DIRECTLY (per the plan and the
|
|
20
|
+
// root CLAUDE.md's own instruction) — this module does not implement any
|
|
21
|
+
// signature comparison of its own. `verifyLastScan` already uses
|
|
22
|
+
// `crypto.timingSafeEqual` internally.
|
|
23
|
+
//
|
|
24
|
+
// Loaded ONCE at server startup (see bin/agentic-security.js's cmdExplore)
|
|
25
|
+
// and held in memory for the life of the process — this is a read-only,
|
|
26
|
+
// single-scan-snapshot server; a change to the graph on disk mid-session is
|
|
27
|
+
// out of scope for this increment (threat-model doc's own "P0 is
|
|
28
|
+
// read-only" framing).
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} scanRoot
|
|
36
|
+
* @returns {{ok:true, graph:object} | {ok:false, reason:'missing'|'unsigned'|'tampered'|'malformed', message:string}}
|
|
37
|
+
*
|
|
38
|
+
* Four, and only four, distinct failure reasons — each with its own clear
|
|
39
|
+
* message so an operator knows exactly what to do next:
|
|
40
|
+
* - 'missing' — no lineage-graph.json at all. Run a scan with
|
|
41
|
+
* AGENTIC_SECURITY_LINEAGE_DEEP=1 first.
|
|
42
|
+
* - 'unsigned' — the graph exists but its .sig sibling does not
|
|
43
|
+
* (verifyLastScan returns null). Refuse to serve an
|
|
44
|
+
* unverifiable graph.
|
|
45
|
+
* - 'tampered' — the graph exists and has a .sig, but the signature does
|
|
46
|
+
* not match the body (verifyLastScan returns false). The
|
|
47
|
+
* file was modified after signing, or signed under a
|
|
48
|
+
* different install key.
|
|
49
|
+
* - 'malformed' — the body passed signature verification but is not
|
|
50
|
+
* valid JSON. Should not happen from a normal scan; the
|
|
51
|
+
* file may be corrupted on disk after signing.
|
|
52
|
+
*/
|
|
53
|
+
function loadSignedGraph(scanRoot) {
|
|
54
|
+
const graphPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, 'lineage-graph.json');
|
|
55
|
+
const sigPath = graphPath + '.sig';
|
|
56
|
+
|
|
57
|
+
if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(graphPath)) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: 'missing',
|
|
61
|
+
message: `No lineage graph found at ${graphPath}. Run a scan with AGENTIC_SECURITY_LINEAGE_DEEP=1 first (e.g. \`AGENTIC_SECURITY_LINEAGE_DEEP=1 agentic-security scan\`), then re-run \`agentic-security explore\`.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let body;
|
|
66
|
+
try {
|
|
67
|
+
body = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(graphPath, 'utf8');
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
reason: 'missing',
|
|
72
|
+
message: `Lineage graph found at ${graphPath} but could not be read: ${e && e.message ? e.message : e}.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const verified = (0,_posture_integrity_js__WEBPACK_IMPORTED_MODULE_2__/* .verifyLastScan */ .Ef)(body, sigPath);
|
|
77
|
+
if (verified === null) {
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
reason: 'unsigned',
|
|
81
|
+
message: `Lineage graph at ${graphPath} has no signature file (${sigPath} is missing). Refusing to serve an unverifiable graph. Re-run the scan (AGENTIC_SECURITY_LINEAGE_DEEP=1) to regenerate both files together.`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (verified === false) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: 'tampered',
|
|
88
|
+
message: `Lineage graph at ${graphPath} FAILED signature verification — its contents do not match ${sigPath}. The file may have been modified after the scan, or signed under a different install key. Refusing to serve a tampered graph. Re-run the scan to regenerate it.`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let graph;
|
|
93
|
+
try {
|
|
94
|
+
graph = JSON.parse(body);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
reason: 'malformed',
|
|
99
|
+
message: `Lineage graph at ${graphPath} passed signature verification but is not valid JSON (${e && e.message ? e.message : e}). This should not happen from a normal scan — the file may be corrupted. Re-run the scan to regenerate it.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { ok: true, graph };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Load .agentic-security/lineage-graph.json ONLY when it is genuinely
|
|
108
|
+
* fresh for THIS scan — never merely because a file happens to exist on
|
|
109
|
+
* disk. Shared by every caller that signs or narrates a graph:-derived
|
|
110
|
+
* compliance claim (M4 sub-project 6c's final whole-branch review found
|
|
111
|
+
* the identical staleness gap independently reachable from
|
|
112
|
+
* `attest --obligations` AND `compliance --walkthrough`, and required
|
|
113
|
+
* this predicate to live in exactly one place rather than being
|
|
114
|
+
* copy-pasted per caller — a safety check that drifts between two
|
|
115
|
+
* near-identical inline copies is worse than one shared bug).
|
|
116
|
+
*
|
|
117
|
+
* `.agentic-security/lineage-graph.json` is only rewritten when a scan
|
|
118
|
+
* actually finishes building a graph (`if (scan.lineageGraph)` in
|
|
119
|
+
* bin/agentic-security.js's persistence code) — an ordinary non-deep
|
|
120
|
+
* rescan, or a deep scan whose lineage build fails, leaves whatever file
|
|
121
|
+
* was there from an earlier successful deep scan untouched. Loading that
|
|
122
|
+
* stale graph and joining it to the CURRENT scan's other data would let a
|
|
123
|
+
* caller assert a graph-derived fact (e.g. "transit protected") about
|
|
124
|
+
* code that has since changed.
|
|
125
|
+
*
|
|
126
|
+
* `enabled: true` in `scan.scanHealth.lineageAnalysis` does NOT by itself
|
|
127
|
+
* mean the build succeeded — engine.js sets it the moment
|
|
128
|
+
* AGENTIC_SECURITY_LINEAGE_DEEP=1 is read, before the build even starts,
|
|
129
|
+
* and leaves it `true` even when the build later throws (only `failure`
|
|
130
|
+
* gets set in that case). `requested && enabled` alone therefore still
|
|
131
|
+
* accepts a stale graph after a failed rebuild — reproduced live via the
|
|
132
|
+
* scan's own already-shipped fault-injection fixture
|
|
133
|
+
* (test/lineage-fault-injection.test.js) before this `failure === null`
|
|
134
|
+
* check was added.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} scanRoot
|
|
137
|
+
* @param {object} scan - the parsed last-scan.json for the CURRENT scan
|
|
138
|
+
* @returns {{graph:object|null, fresh:boolean, loaded:ReturnType<typeof loadSignedGraph>}}
|
|
139
|
+
* `fresh` is true only when a signed graph loaded successfully AND this
|
|
140
|
+
* scan's own scanHealth confirms lineage analysis was requested,
|
|
141
|
+
* enabled, and did not fail. `graph` is `loaded.graph` when fresh, else
|
|
142
|
+
* `null` — never the stale file, even when one exists on disk.
|
|
143
|
+
* `loaded` is the raw `loadSignedGraph` result, so a caller can still
|
|
144
|
+
* distinguish "no file at all" from "a file exists but isn't fresh" for
|
|
145
|
+
* its own disclosure message.
|
|
146
|
+
*/
|
|
147
|
+
function loadFreshLineageGraph(scanRoot, scan) {
|
|
148
|
+
const la = scan?.scanHealth?.lineageAnalysis;
|
|
149
|
+
const requested = la?.requested === true;
|
|
150
|
+
const enabled = la?.enabled === true;
|
|
151
|
+
const failure = la?.failure ?? null;
|
|
152
|
+
const loaded = loadSignedGraph(scanRoot);
|
|
153
|
+
const fresh = loaded.ok && requested && enabled && failure === null;
|
|
154
|
+
return { graph: fresh ? loaded.graph : null, fresh, loaded };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
/***/ })
|
|
159
|
+
|
|
160
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const id =
|
|
2
|
-
export const ids = [
|
|
1
|
+
export const id = 8476;
|
|
2
|
+
export const ids = [8476];
|
|
3
3
|
export const modules = {
|
|
4
4
|
|
|
5
5
|
/***/ 8476:
|
|
@@ -32,7 +32,7 @@ export const modules = {
|
|
|
32
32
|
const DEFAULT_FP_THRESHOLD = 5;
|
|
33
33
|
|
|
34
34
|
function _readTriage(scanRoot) {
|
|
35
|
-
const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__
|
|
35
|
+
const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, 'triage-feedback.json');
|
|
36
36
|
if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
|
|
37
37
|
try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); } catch { return null; }
|
|
38
38
|
}
|
|
@@ -99,7 +99,7 @@ function synthesizeRules(scanRoot, opts = {}) {
|
|
|
99
99
|
groups.get(k).push(e);
|
|
100
100
|
}
|
|
101
101
|
const proposals = [];
|
|
102
|
-
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__
|
|
102
|
+
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, 'rules-proposed');
|
|
103
103
|
if (!opts.dryRun && !(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(node_path__WEBPACK_IMPORTED_MODULE_1__.dirname(dir))) return [];
|
|
104
104
|
for (const [, group] of groups) {
|
|
105
105
|
if (group.length < threshold) continue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const id =
|
|
2
|
-
export const ids = [
|
|
1
|
+
export const id = 8513;
|
|
2
|
+
export const ids = [8513,3117];
|
|
3
3
|
export const modules = {
|
|
4
4
|
|
|
5
5
|
/***/ 3117:
|
|
@@ -58,7 +58,7 @@ const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
|
|
|
58
58
|
|
|
59
59
|
function _readLastScan(scanRoot) {
|
|
60
60
|
if (!scanRoot) return null;
|
|
61
|
-
const fp = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__
|
|
61
|
+
const fp = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, 'last-scan.json');
|
|
62
62
|
if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
|
|
63
63
|
try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); }
|
|
64
64
|
catch { return null; }
|
|
@@ -310,7 +310,7 @@ function _deltaTrend(history) {
|
|
|
310
310
|
*/
|
|
311
311
|
function leaderboardRowFor({ scanRoot, repo, badgeBase = 'https://agentic-security.dev/badge' } = {}) {
|
|
312
312
|
if (!repo) throw new Error('leaderboardRowFor: repo slug is required');
|
|
313
|
-
const lastScanPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__
|
|
313
|
+
const lastScanPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__.statePath)(scanRoot || '.', 'last-scan.json');
|
|
314
314
|
let scan = null;
|
|
315
315
|
try { scan = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(lastScanPath, 'utf8')); } catch {}
|
|
316
316
|
const summary = (0,_badge_js__WEBPACK_IMPORTED_MODULE_2__/* .summarizeForBadge */ .iI)(scan);
|
|
@@ -318,7 +318,7 @@ function leaderboardRowFor({ scanRoot, repo, badgeBase = 'https://agentic-securi
|
|
|
318
318
|
const topCwe = _topCwe(scan);
|
|
319
319
|
|
|
320
320
|
// Optional scan history for the trend signal.
|
|
321
|
-
const historyPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__
|
|
321
|
+
const historyPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_3__.statePath)(scanRoot || '.', 'scan-history.jsonl');
|
|
322
322
|
let history = [];
|
|
323
323
|
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(historyPath)) {
|
|
324
324
|
try {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export const id = 8846;
|
|
2
|
+
export const ids = [8846];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 8846:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ diffScenarioGraph: () => (/* binding */ diffScenarioGraph)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* unused harmony export WATCHED_SCENARIO_FIELDS */
|
|
12
|
+
// scenario-diff.js — M5 deliverable #3a (FR-502): a dedicated
|
|
13
|
+
// comparison for a Scenario's own simulated graph delta. Deliberately
|
|
14
|
+
// NOT graph-diff.js's computeGraphDiff — see this sub-project's own
|
|
15
|
+
// scoping doc for the full reasoning: computeGraphDiff's flow-
|
|
16
|
+
// reidentification pairing and causeClassification vocabulary
|
|
17
|
+
// (`'application_change'`, `'possible_coverage_regression'`,
|
|
18
|
+
// `'reidentified'`) are shaped for a REAL rescan across two commits,
|
|
19
|
+
// where the cause of a change is genuinely ambiguous. A Scenario's own
|
|
20
|
+
// delta has no such ambiguity — every difference between the base graph
|
|
21
|
+
// and a scenario clone IS the declared hypothetical operation that
|
|
22
|
+
// produced it — so this module reports only "what differs", with no
|
|
23
|
+
// cause classification and no reidentification.
|
|
24
|
+
|
|
25
|
+
const WATCHED_SCENARIO_FIELDS = Object.freeze({
|
|
26
|
+
// 'destination' + 'storeDetail' are exhaustive by construction, not by
|
|
27
|
+
// convention: change_storage_fact/change_governance_fact write into an
|
|
28
|
+
// already-watched CONTAINER object, so any field they set is caught
|
|
29
|
+
// regardless of key; replace_recipient_fact is the one operation that
|
|
30
|
+
// writes directly onto the node's own top level, and scenario-engine.js
|
|
31
|
+
// restricts it, at the applier level, to exactly node.destination (any
|
|
32
|
+
// other `field` value is skipped, never applied) — so this list never
|
|
33
|
+
// needs to name a field replace_recipient_fact could reach beyond
|
|
34
|
+
// 'destination'. See scenario-engine.js's _applyReplaceRecipientFact.
|
|
35
|
+
node: Object.freeze(['destination', 'storeDetail']),
|
|
36
|
+
edge: Object.freeze(['protection.transit', 'protection.atRest', 'protection.handling']),
|
|
37
|
+
flow: Object.freeze(['policyVerdict', 'protectionSummary', 'governanceRefs']),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function _get(obj, dottedPath) {
|
|
41
|
+
return dottedPath.split('.').reduce((v, k) => (v == null ? v : v[k]), obj);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _deepEqual(a, b) { return JSON.stringify(a) === JSON.stringify(b); }
|
|
45
|
+
|
|
46
|
+
function _byId(arr) { return new Map((arr ?? []).map((x) => [x.id, x])); }
|
|
47
|
+
|
|
48
|
+
function _diffKind(beforeArr, afterArr, kind) {
|
|
49
|
+
const beforeMap = _byId(beforeArr);
|
|
50
|
+
const afterMap = _byId(afterArr);
|
|
51
|
+
const changed = [];
|
|
52
|
+
const removed = [];
|
|
53
|
+
for (const [id, beforeEntity] of beforeMap) {
|
|
54
|
+
const afterEntity = afterMap.get(id);
|
|
55
|
+
if (!afterEntity) { removed.push(id); continue; }
|
|
56
|
+
const changedFields = [];
|
|
57
|
+
for (const field of WATCHED_SCENARIO_FIELDS[kind]) {
|
|
58
|
+
const before = _get(beforeEntity, field);
|
|
59
|
+
const after = _get(afterEntity, field);
|
|
60
|
+
if (!_deepEqual(before, after)) changedFields.push({ field, before, after });
|
|
61
|
+
}
|
|
62
|
+
if (changedFields.length) changed.push({ id, kind, changedFields });
|
|
63
|
+
}
|
|
64
|
+
return { changed, removed };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// AC-26 ("What-if changes cannot masquerade as implementation"): a
|
|
68
|
+
// simulated edge must be LABELED `HYPOTHETICAL`, not merely carry an
|
|
69
|
+
// `evidenceGrade: 'assumed'` value a reader has to notice buried inside
|
|
70
|
+
// a before/after diff. Mirrors AC-29's own precedent
|
|
71
|
+
// (`observation-correlation.js`'s literal `'runtime_observed'` layer
|
|
72
|
+
// value) — a named acceptance criterion that specifies an exact string
|
|
73
|
+
// gets an exact string in the shipped output, not an inference left to
|
|
74
|
+
// the reader.
|
|
75
|
+
function _hasAssumedEvidence(changedEntity) {
|
|
76
|
+
return changedEntity.changedFields.some((f) => f.after && typeof f.after === 'object' && f.after.evidenceGrade === 'assumed');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compare `baseGraph` against `scenarioGraph` (either a real
|
|
81
|
+
* applyScenario({graph}) result, or another Scenario's own graph, for a
|
|
82
|
+
* scenario-vs-scenario comparison). Never throws — an empty/missing
|
|
83
|
+
* entity array on either side is treated as zero entities.
|
|
84
|
+
*/
|
|
85
|
+
function diffScenarioGraph(baseGraph, scenarioGraph) {
|
|
86
|
+
const nodeDiff = _diffKind(baseGraph.nodes ?? [], scenarioGraph.nodes ?? [], 'node');
|
|
87
|
+
const edgeDiff = _diffKind(baseGraph.edges ?? [], scenarioGraph.edges ?? [], 'edge');
|
|
88
|
+
const flowDiff = _diffKind(baseGraph.flows ?? [], scenarioGraph.flows ?? [], 'flow');
|
|
89
|
+
const changedEntities = [...nodeDiff.changed, ...edgeDiff.changed, ...flowDiff.changed]
|
|
90
|
+
.map((e) => (e.kind === 'edge' && _hasAssumedEvidence(e) ? { ...e, label: 'HYPOTHETICAL' } : e));
|
|
91
|
+
return {
|
|
92
|
+
changedEntities,
|
|
93
|
+
removedEntityIds: [...nodeDiff.removed, ...edgeDiff.removed, ...flowDiff.removed],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/***/ })
|
|
99
|
+
|
|
100
|
+
};
|