@clear-capabilities/agentic-security-scanner 0.145.0 → 0.147.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.
Files changed (162) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/bin/agentic-security.js +3508 -69
  3. package/dist/1122.index.js +702 -0
  4. package/dist/{301.index.js → 1301.index.js} +2 -2
  5. package/dist/1379.index.js +591 -0
  6. package/dist/{444.index.js → 1444.index.js} +2 -2
  7. package/dist/{660.index.js → 1660.index.js} +2 -2
  8. package/dist/{700.index.js → 1700.index.js} +2 -2
  9. package/dist/{905.index.js → 1905.index.js} +3 -3
  10. package/dist/{920.index.js → 1920.index.js} +3 -3
  11. package/dist/{238.index.js → 2238.index.js} +3 -3
  12. package/dist/{271.index.js → 2271.index.js} +2 -2
  13. package/dist/{985.index.js → 2376.index.js} +1260 -340
  14. package/dist/2432.index.js +793 -0
  15. package/dist/2659.index.js +93 -0
  16. package/dist/{826.index.js → 2826.index.js} +2 -2
  17. package/dist/{830.index.js → 2830.index.js} +2 -2
  18. package/dist/2923.index.js +298 -0
  19. package/dist/{1.index.js → 3001.index.js} +5 -5
  20. package/dist/{117.index.js → 3117.index.js} +3 -3
  21. package/dist/3180.index.js +307 -0
  22. package/dist/3276.index.js +117 -0
  23. package/dist/{415.index.js → 3415.index.js} +2 -2
  24. package/dist/{499.index.js → 3499.index.js} +2 -2
  25. package/dist/3518.index.js +450 -0
  26. package/dist/{526.index.js → 3526.index.js} +5 -5
  27. package/dist/{736.index.js → 3736.index.js} +4 -4
  28. package/dist/{839.index.js → 3839.index.js} +4 -4
  29. package/dist/{113.index.js → 4113.index.js} +5 -5
  30. package/dist/{265.index.js → 4265.index.js} +2 -2
  31. package/dist/{384.index.js → 4384.index.js} +3 -3
  32. package/dist/4547.index.js +268 -0
  33. package/dist/4863.index.js +422 -0
  34. package/dist/{970.index.js → 4970.index.js} +2 -2
  35. package/dist/5051.index.js +770 -0
  36. package/dist/{144.index.js → 5144.index.js} +5 -5
  37. package/dist/{333.index.js → 5333.index.js} +3 -3
  38. package/dist/5343.index.js +185 -0
  39. package/dist/5350.index.js +866 -0
  40. package/dist/5561.index.js +436 -0
  41. package/dist/{637.index.js → 5637.index.js} +3 -3
  42. package/dist/{449.index.js → 5830.index.js} +2 -2
  43. package/dist/6626.index.js +532 -0
  44. package/dist/6662.index.js +297 -0
  45. package/dist/{675.index.js → 6675.index.js} +5 -5
  46. package/dist/{730.index.js → 6730.index.js} +6 -6
  47. package/dist/6829.index.js +225 -0
  48. package/dist/6944.index.js +130 -0
  49. package/dist/{178.index.js → 7178.index.js} +3 -3
  50. package/dist/{227.index.js → 7227.index.js} +2 -2
  51. package/dist/7310.index.js +520 -0
  52. package/dist/{552.index.js → 7552.index.js} +4 -4
  53. package/dist/7709.index.js +78 -0
  54. package/dist/8218.index.js +160 -0
  55. package/dist/{476.index.js → 8476.index.js} +4 -4
  56. package/dist/{513.index.js → 8513.index.js} +5 -5
  57. package/dist/{520.index.js → 8520.index.js} +2 -2
  58. package/dist/{718.index.js → 8718.index.js} +2 -2
  59. package/dist/{752.index.js → 8752.index.js} +2 -2
  60. package/dist/8846.index.js +100 -0
  61. package/dist/{435.index.js → 9091.index.js} +560 -189
  62. package/dist/{207.index.js → 9207.index.js} +2 -2
  63. package/dist/{220.index.js → 9220.index.js} +2 -2
  64. package/dist/9390.index.js +163 -0
  65. package/dist/{503.index.js → 9503.index.js} +2 -2
  66. package/dist/{801.index.js → 9801.index.js} +2 -2
  67. package/dist/{824.index.js → 9824.index.js} +2 -2
  68. package/dist/agentic-security.mjs +16 -16
  69. package/dist/agentic-security.mjs.sha256 +1 -1
  70. package/dist/compliance-frameworks/hipaa-security-rule.json +3 -2
  71. package/package.json +12 -5
  72. package/src/dataflow/CLAUDE.md +1 -1
  73. package/src/dataflow/catalog.js +42 -0
  74. package/src/dataflow/orm-write-catalog.js +175 -0
  75. package/src/engine.js +92 -1
  76. package/src/ir/CLAUDE.md +1 -0
  77. package/src/ir/chrome-probe.mjs +150 -0
  78. package/src/ir/parser-js.js +94 -7
  79. package/src/lineage/CLAUDE.md +1203 -0
  80. package/src/lineage/DESIGN_DESTINATION_RESOLVER.md +156 -0
  81. package/src/lineage/DESIGN_GRAPH_BUILDER.md +938 -0
  82. package/src/lineage/DESIGN_HANDLING_ANALYZER.md +355 -0
  83. package/src/lineage/DESIGN_INTRAPROCEDURAL.md +628 -0
  84. package/src/lineage/DESIGN_PATH_PROVENANCE.md +3451 -0
  85. package/src/lineage/DESIGN_QUEUE_DETAIL.md +120 -0
  86. package/src/lineage/DESIGN_REGISTRIES.md +880 -0
  87. package/src/lineage/DESIGN_STORE_DETAIL.md +143 -0
  88. package/src/lineage/DESIGN_TRANSIT_PROTECTION.md +245 -0
  89. package/src/lineage/classification.js +56 -0
  90. package/src/lineage/coverage.js +658 -0
  91. package/src/lineage/cross-repo-link.js +107 -0
  92. package/src/lineage/dataflow-graph.schema.json +184 -0
  93. package/src/lineage/decision-story.js +206 -0
  94. package/src/lineage/drift-policy.js +279 -0
  95. package/src/lineage/driver.js +135 -0
  96. package/src/lineage/engine.js +992 -0
  97. package/src/lineage/export-briefing.js +628 -0
  98. package/src/lineage/export-csv.js +62 -0
  99. package/src/lineage/export-json.js +238 -0
  100. package/src/lineage/export-privacy.js +258 -0
  101. package/src/lineage/federation-loader.js +111 -0
  102. package/src/lineage/field-identity.js +78 -0
  103. package/src/lineage/fixtures/build-flagship-fixture.mjs +272 -0
  104. package/src/lineage/fixtures/flagship-graph.json +1453 -0
  105. package/src/lineage/flow-grade.js +221 -0
  106. package/src/lineage/governance-edit.js +169 -0
  107. package/src/lineage/graph-builder.js +1114 -0
  108. package/src/lineage/graph-diff.js +431 -0
  109. package/src/lineage/graph-snapshot.js +180 -0
  110. package/src/lineage/handling-analyzer.js +168 -0
  111. package/src/lineage/ids.js +349 -0
  112. package/src/lineage/impact-assessment.js +76 -0
  113. package/src/lineage/impact-engine.js +268 -0
  114. package/src/lineage/index.js +281 -0
  115. package/src/lineage/language-coverage-tiers.js +58 -0
  116. package/src/lineage/obligation-mapping.js +126 -0
  117. package/src/lineage/obligation-predicates.js +235 -0
  118. package/src/lineage/observation-adapters.js +282 -0
  119. package/src/lineage/observation-correlation.js +622 -0
  120. package/src/lineage/observation-store.js +497 -0
  121. package/src/lineage/path-query.js +410 -0
  122. package/src/lineage/path-store.js +400 -0
  123. package/src/lineage/protection.js +53 -0
  124. package/src/lineage/recipient-profile.js +192 -0
  125. package/src/lineage/recipient-registry.js +394 -0
  126. package/src/lineage/redact-graph.js +224 -0
  127. package/src/lineage/remediation.js +417 -0
  128. package/src/lineage/resolve-destination.js +91 -0
  129. package/src/lineage/runtime-observation.js +464 -0
  130. package/src/lineage/scenario-diff.js +84 -0
  131. package/src/lineage/scenario-engine.js +251 -0
  132. package/src/lineage/scenario.js +101 -0
  133. package/src/lineage/schema.js +167 -0
  134. package/src/lineage/sink-registry.js +427 -0
  135. package/src/lineage/source-registry.js +357 -0
  136. package/src/lineage/source-seeding.js +212 -0
  137. package/src/lineage/summaries.js +590 -0
  138. package/src/lineage/transform-catalog.js +397 -0
  139. package/src/lineage/transit-protection.js +150 -0
  140. package/src/lineage/validate.js +285 -0
  141. package/src/mcp/CLAUDE.md +7 -1
  142. package/src/mcp/dataflow-tools.js +160 -0
  143. package/src/mcp/server.js +1 -1
  144. package/src/mcp/tools.js +2 -1
  145. package/src/pipeline/scan-health.js +19 -1
  146. package/src/posture/CLAUDE.md +19 -0
  147. package/src/posture/artifact-registry.js +52 -0
  148. package/src/posture/auditor-walkthrough.js +76 -0
  149. package/src/posture/compliance-frameworks/hipaa-security-rule.json +3 -2
  150. package/src/posture/obligation-evidence-pack.js +202 -0
  151. package/src/posture/remediation-ledger.js +337 -0
  152. package/src/server/CLAUDE.md +47 -0
  153. package/src/server/graph-loader.js +141 -0
  154. package/src/server/http-server.js +325 -0
  155. package/src/server/routes.js +129 -0
  156. package/src/server/security.js +111 -0
  157. package/src/server/static-assets.js +139 -0
  158. package/dist/11.index.js +0 -353
  159. package/dist/259.index.js +0 -975
  160. package/dist/317.index.js +0 -300
  161. package/dist/609.index.js +0 -741
  162. package/dist/838.index.js +0 -152
@@ -0,0 +1,297 @@
1
+ export const id = 6662;
2
+ export const ids = [6662];
3
+ export const modules = {
4
+
5
+ /***/ 6662:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ __webpack_require__.r(__webpack_exports__);
9
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
10
+ /* harmony export */ evaluateDriftPolicies: () => (/* binding */ evaluateDriftPolicies),
11
+ /* harmony export */ loadDriftPolicies: () => (/* binding */ loadDriftPolicies)
12
+ /* harmony export */ });
13
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
14
+ // drift-policy.js — M4 deliverable #8, sub-project 8b, Task 2: a
15
+ // before/after drift-policy DSL evaluated against a GraphDiff (Task 1's
16
+ // own output, see graph-diff.js).
17
+ //
18
+ // Mirrors dataflow/privacy-sink-policy.js's fail-closed axis-matching
19
+ // STYLE exactly — the `_matchesEnvironment`/`_matchesDestination`
20
+ // precedent (an unset rule field is unconstrained; a set field the
21
+ // current context has no comparable value for does NOT match) — never
22
+ // its literal single-graph-state shape. That module evaluates a static
23
+ // state ("is this (class, sink) pair permitted right now"); this module
24
+ // evaluates a TRANSITION ("did this (class, sink) pair newly start
25
+ // flowing, or did this flow's own verdict regress"), which is why its
26
+ // rule shape is `{trigger, ...}`-keyed rather than `{allow: [...]}`.
27
+ //
28
+ // ── Design judgment calls made in this file (disclosed, not hidden) ──
29
+ //
30
+ // 1. `loadDriftPolicies(policyFilePath)` takes a literal file path, not
31
+ // a scanRoot — a deliberate, task-brief-directed divergence from
32
+ // `loadPrivacySinkPolicy(scanRoot)`'s own scanRoot-relative
33
+ // convention (`statePath(scanRoot, 'privacy-policy.json')`). Drift
34
+ // policies are a new artifact with no established scanRoot-relative
35
+ // home yet (Task 3's CLI wiring is the only real caller, and it can
36
+ // resolve whatever path convention it wants — e.g. a `--policy`
37
+ // flag, or a default under `.agentic-security/` — without this
38
+ // module forcing that choice). Keeping the signature a plain path
39
+ // keeps this module honest about being a pure evaluator with no
40
+ // opinion on where its config file lives.
41
+ //
42
+ // 2. The rule vocabulary generalizes `fromPolicyVerdict`/
43
+ // `toPolicyVerdict` to a second, structurally identical pair,
44
+ // `fromProtectionSummary`/`toProtectionSummary`, rather than adding a
45
+ // generic `field`/`from`/`to` triple. The task brief's own worked
46
+ // examples name `protectionSummary`'s "protected -> unknown/
47
+ // unprotected" transition as "an equivalent from/to pair for
48
+ // protectionSummary, generalizing" the policyVerdict pair — read
49
+ // literally, that is an instruction to add the SAME shape for a
50
+ // second watched field, not to invent a fully generic DSL over every
51
+ // WATCHED_FLOW_FIELDS entry. A generic `field`/`from`/`to` triple was
52
+ // considered and rejected: nothing in the brief's worked examples
53
+ // needs a THIRD field's transition, and a generic form would need its
54
+ // own validation against `WATCHED_FLOW_FIELDS` this task's tests
55
+ // don't exercise. Extending to a fully generic form remains easy
56
+ // later (both matchers already share one small helper,
57
+ // `_matchesVerdictTransition`) without a rule-shape migration for
58
+ // what's shipped today.
59
+ //
60
+ // 3. `dataClass`/`sinkCategory` are read as ADDITIONAL, optional
61
+ // constraints on a `changed_flow` rule too, not just `new_flow`
62
+ // rules — the brief's own rule shape lists them as top-level fields
63
+ // (not scoped textually to `new_flow` alone), and a changed flow's
64
+ // id is guaranteed present in `graphAfter` by construction (a
65
+ // `changed.flows` entry only exists when the SAME flow id survived
66
+ // into the after-graph — see graph-diff.js's own `computeGraphDiff`
67
+ // header), so the same `_resolveFlowContext` resolution that
68
+ // `new_flow` rules use applies unchanged. This lets an operator write
69
+ // "a PHI flow that regresses from protected" as one rule instead of
70
+ // two, and costs nothing when a rule doesn't set either field (both
71
+ // stay unconstrained, per the fail-closed style above).
72
+ //
73
+ // 4. A `changed_flow` rule that sets none of
74
+ // dataClass/sinkCategory/fromPolicyVerdict/toPolicyVerdict/
75
+ // fromProtectionSummary/toProtectionSummary matches EVERY
76
+ // `changed.flows` entry — every axis is independently unconstrained,
77
+ // so a rule with no axes set is the fully unconstrained case, exactly
78
+ // as an `allow` rule with neither `class` nor `sink` would be in
79
+ // `privacy-sink-policy.js` (it doesn't have that case in practice
80
+ // because `sink` is required there; this module has no required axis
81
+ // besides `trigger`, so the maximally-open rule is reachable and
82
+ // intentional — "alert on ANY change to a flow matching these
83
+ // constraints" is a real, useful policy shape).
84
+ //
85
+ // 5. The task brief's PRD-quoted worked examples also name "PII -> AI"
86
+ // ,"PCI -> log" (both plain `new_flow` shapes, already covered by
87
+ // dataClass/sinkCategory matching — no separate mechanism needed) and
88
+ // "new unresolved recipient" (a `governanceRefs.recipient`-shaped
89
+ // transition). The brief's own "Tests to write" section does not
90
+ // require a test for the recipient example, and it does not reduce
91
+ // cleanly to either matcher this task implements (a "new unresolved
92
+ // recipient" is a value becoming a specific sentinel, not a flow
93
+ // newly existing or an already-modeled verdict field transitioning)
94
+ // — left out rather than guessed at. Judgment call #2's rejected
95
+ // generic `field`/`from`/`to` form is the natural place to add it
96
+ // later, against a real governanceRefs.recipient fixture.
97
+ //
98
+ // 6. Fix round 1, Important 1 (see graph-diff.js's own judgment call #4):
99
+ // a `new_flow` rule must skip an added flow entry whose
100
+ // `causeClassification === 'reidentified'` — that flow isn't actually
101
+ // new, it's the SAME real-world (source, sink, dataElementIds) flow as
102
+ // a flow that existed before, just minted under a new flowId because
103
+ // the engine's own confidence/shape (evidenceGrade/transformationIds)
104
+ // changed. Firing `new_flow` on it would false-fire `--fail-on-drift`
105
+ // on a non-event, exactly what this fix round exists to close.
106
+
107
+
108
+
109
+ const POLICY_TRIGGERS = Object.freeze(['new_flow', 'changed_flow']);
110
+
111
+ function _isValidRuleShape(p) {
112
+ return !!p && typeof p === 'object' && !Array.isArray(p) && POLICY_TRIGGERS.includes(p.trigger);
113
+ }
114
+
115
+ /**
116
+ * Load an operator's drift-policy rules from a literal JSON file path
117
+ * (see judgment call #1 above — never a scanRoot). Never throws — a
118
+ * missing file (ENOENT) is "no policies configured," matching
119
+ * `loadPrivacySinkPolicy`'s own precedent; a malformed file logs a
120
+ * warning and degrades to no policies. Each entry is validated loosely:
121
+ * an entry that isn't a plain object with a recognized `trigger` is
122
+ * skipped (with a warning naming how many were dropped), never crashing
123
+ * the whole load over one bad rule.
124
+ */
125
+ function loadDriftPolicies(policyFilePath) {
126
+ const EMPTY = { policies: [] };
127
+ if (!policyFilePath) return EMPTY;
128
+
129
+ let raw = null;
130
+ try {
131
+ raw = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(policyFilePath, 'utf8'));
132
+ } catch (e) {
133
+ if (e.code !== 'ENOENT') {
134
+ console.error(`agentic-security: bad JSON in drift policy file (${policyFilePath}) — falling back to no policies (${e.message})`);
135
+ }
136
+ return EMPTY;
137
+ }
138
+
139
+ if (!Array.isArray(raw?.policies)) {
140
+ console.error(`agentic-security: drift policy file ${policyFilePath} has no "policies" array — falling back to no policies (expected {"policies":[...]})`);
141
+ }
142
+ const rawPolicies = Array.isArray(raw?.policies) ? raw.policies : [];
143
+ const policies = rawPolicies.filter(_isValidRuleShape);
144
+ const skipped = rawPolicies.length - policies.length;
145
+ if (skipped > 0) {
146
+ console.error(`agentic-security: skipped ${skipped} malformed drift-policy rule(s) in ${policyFilePath} (each must be an object with trigger: 'new_flow' | 'changed_flow')`);
147
+ }
148
+ return { policies };
149
+ }
150
+
151
+ // Fail-closed, matching privacy-sink-policy.js's own
152
+ // _matchesEnvironment/_matchesDestination precedent exactly: an unset
153
+ // rule value is unconstrained (matches anything); a set rule value with
154
+ // no comparable current value does NOT match.
155
+ function _matchesDataClass(ruleDataClass, dataClasses) {
156
+ if (ruleDataClass == null) return true;
157
+ if (!Array.isArray(dataClasses) || !dataClasses.length) return false;
158
+ return dataClasses.includes(ruleDataClass);
159
+ }
160
+
161
+ function _matchesSinkCategory(ruleSinkCategory, sinkCategory) {
162
+ if (ruleSinkCategory == null) return true;
163
+ if (!sinkCategory) return false;
164
+ return ruleSinkCategory === sinkCategory;
165
+ }
166
+
167
+ // Matches a rule's {fromX, toX} pair against the {before, after} of the
168
+ // changes[] entry for field X (or undefined, when that field didn't
169
+ // change on this flow). Unset fromX/toX is unconstrained. A set fromX/
170
+ // toX with no changes[] entry for that field at all (the flow changed,
171
+ // but not on this axis) does NOT match — fails closed, same as an
172
+ // environment-scoped rule against a caller who supplied no environment.
173
+ function _matchesVerdictTransition(ruleFrom, ruleTo, changeEntry) {
174
+ if (ruleFrom == null && ruleTo == null) return true;
175
+ if (!changeEntry) return false;
176
+ if (ruleFrom != null && changeEntry.before !== ruleFrom) return false;
177
+ if (ruleTo != null && changeEntry.after !== ruleTo) return false;
178
+ return true;
179
+ }
180
+
181
+ // Resolves the real dataClasses/sinkCategory/sinkNode/flow for a flow id
182
+ // against graphAfter — shared by both new_flow (added.flows entries) and
183
+ // changed_flow (changed.flows entries) matching, since a changed.flows
184
+ // entry's id is, by construction, present in graphAfter too (see
185
+ // graph-diff.js's own computeGraphDiff: a changed entry only exists for
186
+ // a flow id surviving into the after-graph). Returns null when the id
187
+ // can't be resolved (should not happen for a well-formed GraphDiff
188
+ // produced by computeGraphDiff against its own graphAfter, but a rule
189
+ // must never crash the whole evaluation over a caller-supplied
190
+ // diff/graph pairing that doesn't actually match).
191
+ function _resolveFlowContext(flowId, graphAfter) {
192
+ const flow = (graphAfter?.flows ?? []).find((f) => f.id === flowId);
193
+ if (!flow) return null;
194
+
195
+ const dataClasses = [];
196
+ const seen = new Set();
197
+ for (const deId of flow.dataElementIds ?? []) {
198
+ const de = (graphAfter.dataElements ?? []).find((d) => d.id === deId);
199
+ if (!de) continue;
200
+ for (const cls of de.dataClasses ?? []) {
201
+ if (!seen.has(cls)) { seen.add(cls); dataClasses.push(cls); }
202
+ }
203
+ }
204
+ const dataElementNames = (flow.dataElementIds ?? [])
205
+ .map((id) => (graphAfter.dataElements ?? []).find((d) => d.id === id)?.name)
206
+ .filter((n) => typeof n === 'string' && n.length > 0);
207
+
208
+ const sinkNode = (graphAfter.nodes ?? []).find((n) => n.id === flow.sink) ?? null;
209
+ const sinkCategory = sinkNode?.subtype ?? sinkNode?.kind ?? null;
210
+
211
+ return { flow, dataClasses, dataElementNames, sinkNode, sinkCategory };
212
+ }
213
+
214
+ function _describeTransition(changeEntry) {
215
+ if (!changeEntry) return null;
216
+ return `${changeEntry.field} ${changeEntry.before} -> ${changeEntry.after}`;
217
+ }
218
+
219
+ function _buildViolation(rule, trigger, ctx, transitionDescriptions) {
220
+ const base = trigger === 'new_flow'
221
+ ? `New flow ${ctx.flow.id} (${ctx.dataClasses.join('/') || 'unclassified data'}) newly reaches sink ${ctx.sinkCategory ?? ctx.sinkNode?.id ?? 'unknown'}`
222
+ : `Flow ${ctx.flow.id} changed: ${transitionDescriptions.length ? transitionDescriptions.join(', ') : 'matched changed_flow rule (no policyVerdict/protectionSummary transition named by this rule)'}`;
223
+ const reason = rule.reason ? `${base} (${rule.reason})` : base;
224
+
225
+ return {
226
+ flowId: ctx.flow.id,
227
+ dataElementNames: ctx.dataElementNames,
228
+ sinkCategory: ctx.sinkCategory,
229
+ sinkNodeId: ctx.sinkNode?.id ?? ctx.flow.sink,
230
+ trigger,
231
+ rule,
232
+ reason,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Evaluates operator-configured drift policies against a GraphDiff.
238
+ * `policies` is `{policies: [...]}` (loadDriftPolicies' own return
239
+ * shape). `graphAfter` is the AFTER graph the diff was computed against
240
+ * — needed because an `added.flows`/`changed.flows` entry carries only
241
+ * `{id, causeClassification, ...}` (see graph-diff.js's own header),
242
+ * never dataElement/sink info directly.
243
+ *
244
+ * Returns `{violations: [...]}` — never a bare boolean per flow; every
245
+ * violation names the real triggering flow (id, data element names,
246
+ * sink), the rule that fired, and a human-readable `reason` string.
247
+ */
248
+ function evaluateDriftPolicies(diff, policies, graphAfter) {
249
+ const violations = [];
250
+ const rules = Array.isArray(policies?.policies) ? policies.policies : [];
251
+ if (!rules.length) return { violations };
252
+
253
+ for (const rule of rules) {
254
+ if (rule.trigger === 'new_flow') {
255
+ for (const addedEntry of diff?.added?.flows ?? []) {
256
+ // See judgment call #6 above / graph-diff.js's own judgment call
257
+ // #4: a reidentified added flow is not actually new.
258
+ if (addedEntry.causeClassification === 'reidentified') continue;
259
+ const ctx = _resolveFlowContext(addedEntry.id, graphAfter);
260
+ if (!ctx) continue;
261
+ if (!_matchesDataClass(rule.dataClass, ctx.dataClasses)) continue;
262
+ if (!_matchesSinkCategory(rule.sinkCategory, ctx.sinkCategory)) continue;
263
+ violations.push(_buildViolation(rule, 'new_flow', ctx, []));
264
+ }
265
+ } else if (rule.trigger === 'changed_flow') {
266
+ for (const changedEntry of diff?.changed?.flows ?? []) {
267
+ const ctx = _resolveFlowContext(changedEntry.id, graphAfter);
268
+ if (!ctx) continue;
269
+ if (!_matchesDataClass(rule.dataClass, ctx.dataClasses)) continue;
270
+ if (!_matchesSinkCategory(rule.sinkCategory, ctx.sinkCategory)) continue;
271
+
272
+ const changes = Array.isArray(changedEntry.changes) ? changedEntry.changes : [];
273
+ const policyChange = changes.find((c) => c.field === 'policyVerdict');
274
+ if (!_matchesVerdictTransition(rule.fromPolicyVerdict, rule.toPolicyVerdict, policyChange)) continue;
275
+
276
+ const protectionChange = changes.find((c) => c.field === 'protectionSummary');
277
+ if (!_matchesVerdictTransition(rule.fromProtectionSummary, rule.toProtectionSummary, protectionChange)) continue;
278
+
279
+ const transitionDescriptions = [policyChange, protectionChange].map(_describeTransition).filter(Boolean);
280
+ violations.push(_buildViolation(rule, 'changed_flow', ctx, transitionDescriptions));
281
+ }
282
+ }
283
+ // An unrecognized trigger was already filtered out by
284
+ // loadDriftPolicies' own _isValidRuleShape when loaded from disk; a
285
+ // directly hand-built `policies` object (as tests do) with an
286
+ // unrecognized trigger simply matches nothing, silently — the same
287
+ // "match nothing rather than throw" discipline as every other
288
+ // unresolvable axis in this file.
289
+ }
290
+
291
+ return { violations };
292
+ }
293
+
294
+
295
+ /***/ })
296
+
297
+ };
@@ -1,5 +1,5 @@
1
- export const id = 675;
2
- export const ids = [675];
1
+ export const id = 6675;
2
+ export const ids = [6675];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 6675:
@@ -281,7 +281,7 @@ async function _queryOsvForDep({ name, ecosystem }, { fetchImpl = globalThis.fet
281
281
  // ─── State persistence ───────────────────────────────────────────────────
282
282
 
283
283
  function loadState(scanRoot) {
284
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'cve-alerts-state.json');
284
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, 'cve-alerts-state.json');
285
285
  if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return { known: new Set(), lastRun: null };
286
286
  try {
287
287
  const j = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'));
@@ -290,7 +290,7 @@ function loadState(scanRoot) {
290
290
  }
291
291
 
292
292
  function persistState(scanRoot, state) {
293
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'cve-alerts-state.json');
293
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, 'cve-alerts-state.json');
294
294
  if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return;
295
295
  node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(node_path__WEBPACK_IMPORTED_MODULE_1__.dirname(fp), { recursive: true });
296
296
  node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(fp, JSON.stringify({
@@ -300,7 +300,7 @@ function persistState(scanRoot, state) {
300
300
  }
301
301
 
302
302
  function loadConfig(scanRoot) {
303
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'cve-alerts.json');
303
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, 'cve-alerts.json');
304
304
  if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
305
305
  try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); } catch { return null; }
306
306
  }
@@ -1,5 +1,5 @@
1
- export const id = 730;
2
- export const ids = [730,144];
1
+ export const id = 6730;
2
+ export const ids = [6730,5144];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 5144:
@@ -65,7 +65,7 @@ const LEGAL_HOLD_FILE = 'legal-holds.json';
65
65
 
66
66
  function _loadRaw(scanRoot) {
67
67
  let fp;
68
- try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .statePath */ .BQ)(scanRoot, LEGAL_HOLD_FILE); } catch { return []; }
68
+ try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE); } catch { return []; }
69
69
  let raw;
70
70
  try { raw = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'); } catch { return []; }
71
71
  try {
@@ -135,7 +135,7 @@ function addLegalHold(scanRoot, { artifact, owner, reason, expires_at } = {}) {
135
135
  const hold = { artifact, owner, reason, expires_at: expires_at || null, created_at: new Date().toISOString() };
136
136
  const holds = _loadRaw(scanRoot);
137
137
  holds.push(hold);
138
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .statePath */ .BQ)(scanRoot, LEGAL_HOLD_FILE);
138
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE);
139
139
  if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .safeWriteState */ .Ep)(fp, JSON.stringify(holds, null, 2) + '\n')) {
140
140
  return { ok: false, reason: 'state writes are disabled (--no-state) or this is not a safe state directory' };
141
141
  }
@@ -151,7 +151,7 @@ function removeLegalHold(scanRoot, artifact) {
151
151
  const remaining = holds.filter(h => !(h && h.artifact === artifact));
152
152
  const removedCount = holds.length - remaining.length;
153
153
  if (removedCount > 0) {
154
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .statePath */ .BQ)(scanRoot, LEGAL_HOLD_FILE);
154
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE);
155
155
  (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .safeWriteState */ .Ep)(fp, JSON.stringify(remaining, null, 2) + '\n');
156
156
  }
157
157
  return removedCount;
@@ -242,7 +242,7 @@ const RETENTION_DEFAULTS = {
242
242
  function loadRetentionPolicy(scanRoot) {
243
243
  if (!scanRoot) return null;
244
244
  let fp;
245
- try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, POLICY_FILE); } catch { return null; }
245
+ try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, POLICY_FILE); } catch { return null; }
246
246
  let raw;
247
247
  try { raw = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'); } catch { return null; }
248
248
  try {
@@ -0,0 +1,225 @@
1
+ export const id = 6829;
2
+ export const ids = [6829];
3
+ export const modules = {
4
+
5
+ /***/ 6829:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ OBLIGATION_EVIDENCE_PACK_SCHEMA: () => (/* binding */ OBLIGATION_EVIDENCE_PACK_SCHEMA),
10
+ /* harmony export */ buildObligationEvidencePack: () => (/* binding */ buildObligationEvidencePack),
11
+ /* harmony export */ ensureKeyPair: () => (/* reexport safe */ _evidence_bundle_js__WEBPACK_IMPORTED_MODULE_0__.ensureKeyPair),
12
+ /* harmony export */ signObligationEvidencePack: () => (/* binding */ signObligationEvidencePack),
13
+ /* harmony export */ verifyObligationEvidencePack: () => (/* binding */ verifyObligationEvidencePack)
14
+ /* harmony export */ });
15
+ /* harmony import */ var _evidence_bundle_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8317);
16
+ /* harmony import */ var _evidence_grade_wording_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3540);
17
+ /* harmony import */ var _lineage_export_json_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(859);
18
+ /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7598);
19
+ // obligation-evidence-pack.js — Milestone 4 sub-project 6c: signed,
20
+ // versioned evidence packs for a Regulatory Obligation Overlay framework
21
+ // evaluation (FR-504, PRD §10.10). Fourth sibling in the
22
+ // evidence-bundle.js family — see that module's own header for the
23
+ // shared-key rationale, and provenance-evidence-bundle.js for the most
24
+ // directly mirrored precedent (same reused ensureKeyPair/keyPaths/
25
+ // canonicalJson, own schema string, own build/sign/verify trio, own
26
+ // top-level-key allowlist).
27
+ //
28
+ // WHAT THIS IS NOT
29
+ // -----------------
30
+ // Not a reuse of evidence-bundle.js's own bundle shape (that's a single
31
+ // FINDING's evidence — proofTier/taintPath/etc; this artifact has no
32
+ // finding at all). Not compliance-evidence-signing.js's ComplianceEvidence
33
+ // manifest either — that signs the pre-existing family:/module:/rule:-
34
+ // driven present/partial/absent/manual walkthrough status; this signs the
35
+ // newer, additive graph: mapping type's own real ObligationMapping
36
+ // records (evaluateGraphFlowPredicate/buildObligationMappingFromGraphPredicate,
37
+ // sub-project 6b) — a distinct FR-504 artifact with its own field list
38
+ // (scope, framework versions, facts, evidence index, unknown/manual
39
+ // items, accepted exceptions, scan health, limitations, graph digest,
40
+ // reproducibility metadata).
41
+ //
42
+ // THE EVIDENCE-INDEX DESIGN DECISION (see the scoping doc for the full
43
+ // writeup)
44
+ // -----------------------------------------------------------------
45
+ // A real ObligationMapping record's own `evidence[]` is structurally
46
+ // always empty today (graph-builder.js hardcodes `edge.evidenceRefs: []`
47
+ // on every minted edge, disclosed in obligation-predicates.js's own
48
+ // header). Rather than ship an evidence pack whose "evidence index" is
49
+ // honestly, permanently empty, this module builds a REAL evidence index
50
+ // from each fact's own `contributingGraphIds` (real flow ids
51
+ // evaluateGraphFlowPredicate already returns) — resolving each flow id
52
+ // back into a small, real summary (source/sink kind, dataElement
53
+ // dataClasses, the edge's own transit/atRest/handling verdicts) by
54
+ // joining against the graph's own entity arrays. `record.evidence` is
55
+ // still carried through verbatim on each fact (honest, even though
56
+ // empty) — the evidence index is an ADDITIONAL section built from data
57
+ // that genuinely IS populated, not a replacement that hides the gap.
58
+
59
+
60
+
61
+
62
+
63
+
64
+ const OBLIGATION_EVIDENCE_PACK_SCHEMA = 'agentic-security/obligation-evidence-pack@1';
65
+
66
+ function _asArray(v) {
67
+ return Array.isArray(v) ? v : [];
68
+ }
69
+
70
+ /**
71
+ * Resolve one ObligationMapping fact's contributingGraphIds (real flow
72
+ * ids) into a small, real, human-and-machine-readable summary per flow —
73
+ * the evidence index's own per-fact contribution. Never throws: a flow id
74
+ * that no longer resolves against this graph (a stale pack re-evaluated
75
+ * against a newer graph, or a malformed fact) is simply skipped, not
76
+ * fabricated.
77
+ */
78
+ function _resolveEvidenceForFact(fact, joins) {
79
+ const { flowsById, nodesById, edgesById, dataElementsById } = joins;
80
+ const flowIds = _asArray(fact?.contributingGraphIds);
81
+ const resolved = [];
82
+ for (const flowId of flowIds) {
83
+ const flow = flowsById.get(flowId);
84
+ if (!flow) continue;
85
+ const sourceNode = nodesById.get(flow.source);
86
+ const sinkNode = nodesById.get(flow.sink);
87
+ // Reads only edgeIds[0] — mirrors obligation-predicates.js's own
88
+ // disclosed, currently-unreachable limitation (graph-builder.js always
89
+ // mints single-edge flows; see that module's own header) rather than
90
+ // inventing a different convention for the same case here.
91
+ const edge = edgesById.get(_asArray(flow.edgeIds)[0]);
92
+ const dataClasses = _asArray(flow.dataElementIds)
93
+ .map((id) => dataElementsById.get(id))
94
+ .filter(Boolean)
95
+ .flatMap((d) => _asArray(d.dataClasses));
96
+ resolved.push({
97
+ flowId,
98
+ source: sourceNode ? { kind: sourceNode.kind ?? null, subtype: sourceNode.subtype ?? null } : null,
99
+ sink: sinkNode ? { kind: sinkNode.kind ?? null, subtype: sinkNode.subtype ?? null } : null,
100
+ dataClasses: [...new Set(dataClasses)],
101
+ transitVerdict: edge?.protection?.transit?.verdict ?? null,
102
+ atRestVerdict: edge?.protection?.atRest?.verdict ?? null,
103
+ handlingVerdict: edge?.protection?.handling?.verdict ?? null,
104
+ });
105
+ }
106
+ return resolved;
107
+ }
108
+
109
+ function _buildJoins(graph) {
110
+ return {
111
+ flowsById: new Map(_asArray(graph?.flows).filter(Boolean).map((f) => [f.id, f])),
112
+ nodesById: new Map(_asArray(graph?.nodes).filter(Boolean).map((n) => [n.id, n])),
113
+ edgesById: new Map(_asArray(graph?.edges).filter(Boolean).map((e) => [e.id, e])),
114
+ dataElementsById: new Map(_asArray(graph?.dataElements).filter(Boolean).map((d) => [d.id, d])),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Build an unsigned evidence pack from a framework evaluation. Never
120
+ * throws: every field degrades honestly on missing input rather than
121
+ * fabricating a value — a null/absent graph yields empty facts/
122
+ * evidenceIndex and a null graphDigest/scope, never a guess.
123
+ *
124
+ * @param {object} args
125
+ * @param {object|null} args.graph - the scan's DataFlowGraph v1 document (scan.lineageGraph), or null
126
+ * @param {object} args.framework - the loaded framework object (auditor-walkthrough.js#loadFramework's return)
127
+ * @param {Array} args.evaluation - auditor-walkthrough.js#evaluateFramework's return (per-control entries, each carrying .obligationMappings)
128
+ * @param {object|null} [args.scanHealth] - scan.scanHealth, passed through verbatim; null if not supplied, never fabricated
129
+ * @param {string|null} [args.engineVersion]
130
+ * @param {string|null} [args.rulesetVersion]
131
+ * @param {string|null} [args.bundleSha]
132
+ * @param {string} [args.generatedAt] - defaults to new Date().toISOString()
133
+ */
134
+ function buildObligationEvidencePack({
135
+ graph, framework, evaluation, scanHealth, engineVersion, rulesetVersion, bundleSha, generatedAt,
136
+ } = {}) {
137
+ const facts = _asArray(evaluation).flatMap((e) => _asArray(e?.obligationMappings));
138
+ const joins = _buildJoins(graph);
139
+ const evidenceIndex = facts.map((fact) => ({
140
+ obligationId: fact?.id ?? null,
141
+ requirementId: fact?.requirementId ?? null,
142
+ evidence: _resolveEvidenceForFact(fact, joins),
143
+ }));
144
+
145
+ return {
146
+ schema: OBLIGATION_EVIDENCE_PACK_SCHEMA,
147
+ framework: {
148
+ id: framework?.id ?? null,
149
+ name: framework?.name ?? null,
150
+ version: framework?.controlsDigest ?? null,
151
+ publisher: framework?.publisher ?? null,
152
+ url: framework?.url ?? null,
153
+ },
154
+ scope: graph?.scope ?? null,
155
+ facts,
156
+ evidenceIndex,
157
+ unknownItems: facts.filter((f) => f?.state === 'unknown'),
158
+ manualItems: facts.filter((f) => f?.state === 'manual_required'),
159
+ acceptedExceptions: facts.filter((f) => f?.state === 'accepted_exception'),
160
+ scanHealth: scanHealth ?? null,
161
+ limitations: _asArray(graph?.limitations),
162
+ graphDigest: graph ? (0,_lineage_export_json_js__WEBPACK_IMPORTED_MODULE_2__.computeGraphDigest)(graph) : null,
163
+ reproducibility: {
164
+ graphId: graph?.graphId ?? null,
165
+ graphDigest: graph ? (0,_lineage_export_json_js__WEBPACK_IMPORTED_MODULE_2__.computeGraphDigest)(graph) : null,
166
+ engineVersion: engineVersion ?? null,
167
+ rulesetVersion: rulesetVersion ?? null,
168
+ bundleSha: bundleSha ?? null,
169
+ generatedAt: generatedAt ?? new Date().toISOString(),
170
+ },
171
+ disclaimer: _evidence_grade_wording_js__WEBPACK_IMPORTED_MODULE_1__/* .EVIDENCE_GRADE_DISCLAIMER */ .ux,
172
+ };
173
+ }
174
+
175
+ /** Sign a pack. Returns a new object; the input is not mutated. */
176
+ function signObligationEvidencePack(pack, privateKeyPem) {
177
+ const sig = node_crypto__WEBPACK_IMPORTED_MODULE_3__.sign(null, Buffer.from((0,_evidence_bundle_js__WEBPACK_IMPORTED_MODULE_0__/* .canonicalJson */ .dj)(pack), 'utf8'), privateKeyPem);
178
+ return {
179
+ ...pack,
180
+ signature: { algorithm: 'ed25519', canonicalisation: OBLIGATION_EVIDENCE_PACK_SCHEMA, value: sig.toString('base64') },
181
+ };
182
+ }
183
+
184
+ const OBLIGATION_EVIDENCE_PACK_TOP_LEVEL_KEYS = new Set([
185
+ 'schema', 'framework', 'scope', 'facts', 'evidenceIndex', 'unknownItems',
186
+ 'manualItems', 'acceptedExceptions', 'scanHealth', 'limitations',
187
+ 'graphDigest', 'reproducibility', 'disclaimer', 'signature',
188
+ ]);
189
+
190
+ /**
191
+ * Verify with a PUBLIC key only. Rejects any top-level key outside the
192
+ * allowlist BEFORE checking the signature — same EA-03 discipline every
193
+ * sibling in this family carries: a signature only covers the bytes it
194
+ * was computed over, so a key stapled on after signing would otherwise
195
+ * verify as authentic.
196
+ */
197
+ function verifyObligationEvidencePack(pack, publicKeyPem) {
198
+ if (!pack || typeof pack !== 'object') return { ok: false, reason: 'pack is not an object' };
199
+ if (pack.schema !== OBLIGATION_EVIDENCE_PACK_SCHEMA) return { ok: false, reason: `unrecognised schema: ${pack.schema}` };
200
+ const unknownKeys = Object.keys(pack).filter((k) => !OBLIGATION_EVIDENCE_PACK_TOP_LEVEL_KEYS.has(k));
201
+ if (unknownKeys.length) {
202
+ return { ok: false, reason: `unrecognised top-level key(s) not covered by the signature: ${unknownKeys.join(', ')}` };
203
+ }
204
+ const sig = pack.signature;
205
+ if (!sig?.value) return { ok: false, reason: 'pack is unsigned' };
206
+ if (sig.algorithm !== 'ed25519') return { ok: false, reason: `unsupported algorithm: ${sig.algorithm}` };
207
+ if (!publicKeyPem) return { ok: false, reason: 'no public key supplied' };
208
+ const { signature, ...unsigned } = pack;
209
+ let ok = false;
210
+ try {
211
+ ok = node_crypto__WEBPACK_IMPORTED_MODULE_3__.verify(null, Buffer.from((0,_evidence_bundle_js__WEBPACK_IMPORTED_MODULE_0__/* .canonicalJson */ .dj)(unsigned), 'utf8'), publicKeyPem, Buffer.from(sig.value, 'base64'));
212
+ } catch (e) {
213
+ return { ok: false, reason: `verification error: ${e.message}` };
214
+ }
215
+ return ok
216
+ ? { ok: true, reason: null }
217
+ : { ok: false, reason: 'signature does not match the pack contents — it was modified after signing' };
218
+ }
219
+
220
+
221
+
222
+
223
+ /***/ })
224
+
225
+ };