@clear-capabilities/agentic-security-scanner 0.144.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 (209) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/bin/agentic-security.js +3813 -83
  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} +13 -4
  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/2271.index.js +165 -0
  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} +14 -6
  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} +14 -6
  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} +67 -3
  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} +29 -7
  42. package/dist/{449.index.js → 5830.index.js} +78 -14
  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} +26 -8
  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} +580 -187
  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 +23 -10
  72. package/src/compare.js +6 -1
  73. package/src/dataflow/CLAUDE.md +2 -2
  74. package/src/dataflow/catalog.js +42 -0
  75. package/src/dataflow/orm-write-catalog.js +175 -0
  76. package/src/engine.js +580 -30
  77. package/src/fix/apply-fix-service.js +1 -0
  78. package/src/history-scan.js +22 -5
  79. package/src/ir/CLAUDE.md +2 -1
  80. package/src/ir/chrome-probe.mjs +150 -0
  81. package/src/ir/parser-js.js +94 -7
  82. package/src/lineage/CLAUDE.md +1203 -0
  83. package/src/lineage/DESIGN_DESTINATION_RESOLVER.md +156 -0
  84. package/src/lineage/DESIGN_GRAPH_BUILDER.md +938 -0
  85. package/src/lineage/DESIGN_HANDLING_ANALYZER.md +355 -0
  86. package/src/lineage/DESIGN_INTRAPROCEDURAL.md +628 -0
  87. package/src/lineage/DESIGN_PATH_PROVENANCE.md +3451 -0
  88. package/src/lineage/DESIGN_QUEUE_DETAIL.md +120 -0
  89. package/src/lineage/DESIGN_REGISTRIES.md +880 -0
  90. package/src/lineage/DESIGN_STORE_DETAIL.md +143 -0
  91. package/src/lineage/DESIGN_TRANSIT_PROTECTION.md +245 -0
  92. package/src/lineage/classification.js +56 -0
  93. package/src/lineage/coverage.js +658 -0
  94. package/src/lineage/cross-repo-link.js +107 -0
  95. package/src/lineage/dataflow-graph.schema.json +184 -0
  96. package/src/lineage/decision-story.js +206 -0
  97. package/src/lineage/drift-policy.js +279 -0
  98. package/src/lineage/driver.js +135 -0
  99. package/src/lineage/engine.js +992 -0
  100. package/src/lineage/export-briefing.js +628 -0
  101. package/src/lineage/export-csv.js +62 -0
  102. package/src/lineage/export-json.js +238 -0
  103. package/src/lineage/export-privacy.js +258 -0
  104. package/src/lineage/federation-loader.js +111 -0
  105. package/src/lineage/field-identity.js +78 -0
  106. package/src/lineage/fixtures/build-flagship-fixture.mjs +272 -0
  107. package/src/lineage/fixtures/flagship-graph.json +1453 -0
  108. package/src/lineage/flow-grade.js +221 -0
  109. package/src/lineage/governance-edit.js +169 -0
  110. package/src/lineage/graph-builder.js +1114 -0
  111. package/src/lineage/graph-diff.js +431 -0
  112. package/src/lineage/graph-snapshot.js +180 -0
  113. package/src/lineage/handling-analyzer.js +168 -0
  114. package/src/lineage/ids.js +349 -0
  115. package/src/lineage/impact-assessment.js +76 -0
  116. package/src/lineage/impact-engine.js +268 -0
  117. package/src/lineage/index.js +281 -0
  118. package/src/lineage/language-coverage-tiers.js +58 -0
  119. package/src/lineage/obligation-mapping.js +126 -0
  120. package/src/lineage/obligation-predicates.js +235 -0
  121. package/src/lineage/observation-adapters.js +282 -0
  122. package/src/lineage/observation-correlation.js +622 -0
  123. package/src/lineage/observation-store.js +497 -0
  124. package/src/lineage/path-query.js +410 -0
  125. package/src/lineage/path-store.js +400 -0
  126. package/src/lineage/protection.js +53 -0
  127. package/src/lineage/recipient-profile.js +192 -0
  128. package/src/lineage/recipient-registry.js +394 -0
  129. package/src/lineage/redact-graph.js +224 -0
  130. package/src/lineage/remediation.js +417 -0
  131. package/src/lineage/resolve-destination.js +91 -0
  132. package/src/lineage/runtime-observation.js +464 -0
  133. package/src/lineage/scenario-diff.js +84 -0
  134. package/src/lineage/scenario-engine.js +251 -0
  135. package/src/lineage/scenario.js +101 -0
  136. package/src/lineage/schema.js +167 -0
  137. package/src/lineage/sink-registry.js +427 -0
  138. package/src/lineage/source-registry.js +357 -0
  139. package/src/lineage/source-seeding.js +212 -0
  140. package/src/lineage/summaries.js +590 -0
  141. package/src/lineage/transform-catalog.js +397 -0
  142. package/src/lineage/transit-protection.js +150 -0
  143. package/src/lineage/validate.js +285 -0
  144. package/src/lsp/server.js +49 -2
  145. package/src/mcp/CLAUDE.md +7 -1
  146. package/src/mcp/dataflow-tools.js +160 -0
  147. package/src/mcp/server.js +1 -1
  148. package/src/mcp/tools.js +22 -1
  149. package/src/pipeline/assurance-mode.js +64 -1
  150. package/src/pipeline/finding-schema.js +8 -1
  151. package/src/pipeline/scan-health.js +19 -1
  152. package/src/posture/CLAUDE.md +140 -0
  153. package/src/posture/accuracy-scorecard.js +60 -0
  154. package/src/posture/artifact-registry.js +76 -0
  155. package/src/posture/auditor-walkthrough.js +192 -13
  156. package/src/posture/compliance-frameworks/hipaa-security-rule.json +3 -2
  157. package/src/posture/compliance-policy.js +12 -2
  158. package/src/posture/cross-repo-memory.js +7 -2
  159. package/src/posture/fix-history.js +25 -2
  160. package/src/posture/fix-verify.js +9 -1
  161. package/src/posture/fleet.js +0 -0
  162. package/src/posture/git-history.js +13 -5
  163. package/src/posture/material-change.js +21 -2
  164. package/src/posture/mttr.js +75 -12
  165. package/src/posture/obligation-evidence-pack.js +202 -0
  166. package/src/posture/pre-incident-archaeology.js +39 -7
  167. package/src/posture/privacy-framework.js +14 -0
  168. package/src/posture/provenance/ai-authorship.js +68 -0
  169. package/src/posture/provenance/branch-entry.js +80 -0
  170. package/src/posture/provenance/cache.js +143 -0
  171. package/src/posture/provenance/confidence.js +36 -0
  172. package/src/posture/provenance/coordinator.js +786 -0
  173. package/src/posture/provenance/dag-walk.js +249 -0
  174. package/src/posture/provenance/evidence-attribution.js +59 -0
  175. package/src/posture/provenance/git-evidence.js +310 -0
  176. package/src/posture/provenance/lifecycle.js +208 -0
  177. package/src/posture/provenance/missing-control-resolver.js +137 -0
  178. package/src/posture/provenance/origin-resolver.js +342 -0
  179. package/src/posture/provenance/predicate-replay.js +133 -0
  180. package/src/posture/provenance/providers/config.js +39 -0
  181. package/src/posture/provenance/providers/github.js +62 -0
  182. package/src/posture/provenance/providers/gitlab.js +58 -0
  183. package/src/posture/provenance/repo-lineage.js +74 -0
  184. package/src/posture/provenance/sca-origin.js +139 -0
  185. package/src/posture/provenance/schema.js +255 -0
  186. package/src/posture/provenance/transitive-sca.js +147 -0
  187. package/src/posture/provenance/validate.js +30 -0
  188. package/src/posture/provenance-evidence-bundle.js +144 -0
  189. package/src/posture/remediation-ledger.js +337 -0
  190. package/src/posture/sbom-diff.js +15 -2
  191. package/src/posture/secret-history.js +10 -2
  192. package/src/posture/state-dir.js +38 -14
  193. package/src/posture/vuln-archaeology.js +8 -2
  194. package/src/pr-delta.js +25 -4
  195. package/src/report/index.js +197 -3
  196. package/src/runScan.js +34 -5
  197. package/src/sast/rate-limit.js +33 -3
  198. package/src/server/CLAUDE.md +47 -0
  199. package/src/server/graph-loader.js +141 -0
  200. package/src/server/http-server.js +325 -0
  201. package/src/server/routes.js +129 -0
  202. package/src/server/security.js +111 -0
  203. package/src/server/static-assets.js +139 -0
  204. package/src/util/git-hardening.js +128 -0
  205. package/dist/11.index.js +0 -353
  206. package/dist/259.index.js +0 -975
  207. package/dist/317.index.js +0 -300
  208. package/dist/609.index.js +0 -741
  209. package/dist/838.index.js +0 -152
@@ -0,0 +1,702 @@
1
+ export const id = 1122;
2
+ export const ids = [1122,3180];
3
+ export const modules = {
4
+
5
+ /***/ 1122:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+
9
+ // EXPORTS
10
+ __webpack_require__.d(__webpack_exports__, {
11
+ exportPdf: () => (/* binding */ exportPdf),
12
+ exportPng: () => (/* binding */ exportPng),
13
+ exportSvg: () => (/* binding */ exportSvg)
14
+ });
15
+
16
+ // UNUSED EXPORTS: _validTimeoutMs
17
+
18
+ // EXTERNAL MODULE: external "node:fs"
19
+ var external_node_fs_ = __webpack_require__(3024);
20
+ // EXTERNAL MODULE: external "node:os"
21
+ var external_node_os_ = __webpack_require__(8161);
22
+ // EXTERNAL MODULE: external "node:path"
23
+ var external_node_path_ = __webpack_require__(6760);
24
+ // EXTERNAL MODULE: external "node:child_process"
25
+ var external_node_child_process_ = __webpack_require__(1421);
26
+ // EXTERNAL MODULE: external "node:url"
27
+ var external_node_url_ = __webpack_require__(3136);
28
+ // EXTERNAL MODULE: ./scripts/generate-html-report.mjs + 1 modules
29
+ var generate_html_report = __webpack_require__(3180);
30
+ ;// CONCATENATED MODULE: ./src/ir/chrome-probe.mjs
31
+ // chrome-probe.mjs — Milestone 4, sub-project PNG/SVG/PDF export.
32
+ //
33
+ // Chrome/Chromium binary discovery, mirroring parser-py-cst.js's own
34
+ // already-proven probePythonAvailable() pattern exactly — this
35
+ // codebase's established convention for "optional local tool, detect
36
+ // and degrade gracefully" (see that file's own header comment for the
37
+ // full rationale this file inherits without repeating).
38
+
39
+
40
+
41
+
42
+ let _capability = null;
43
+
44
+ // A malformed env var (e.g. AGENTIC_SECURITY_CHROME_PROBE_TIMEOUT_MS=oops)
45
+ // must fall back to the default, never reach spawnSync as NaN — found by
46
+ // the final whole-branch review: `Number('oops')` is NaN, and NaN as a
47
+ // spawnSync `timeout` option throws ERR_OUT_OF_RANGE synchronously.
48
+ // _tryBinary's own try/catch swallows that throw and returns null for
49
+ // EVERY candidate, so probeChromeAvailable would misreport
50
+ // `no-chrome-found` on a machine with a perfectly working Chrome install.
51
+ //
52
+ // Blank/whitespace-only is treated as unset (falls back), not as `0` —
53
+ // found by this fix's own scoped re-review: `Number('')` is `0`, and a
54
+ // naive `n >= 0` guard let an empty-but-exported env var (a blanked
55
+ // .env line, an unset-but-exported CI var) through as a real `timeout:
56
+ // 0`, which spawnSync treats as NO timeout at all — an unbounded hang
57
+ // risk inside this exact capability probe, the failure class
58
+ // parser-py-cst.js's own probePythonAvailable() is already careful
59
+ // about. An explicit `"0"` is still honored (matches this file's
60
+ // pre-fix behavior for that literal value).
61
+ //
62
+ // Must be a safe INTEGER, not merely finite — found by a second scoped
63
+ // re-review, reproduced live: `Number.isFinite(1.5)` is true, so a
64
+ // fractional env var (e.g. "1.5") passed this guard unchanged and then
65
+ // hit spawnSync's own `timeout` option, which throws ERR_OUT_OF_RANGE
66
+ // for any non-integer — the exact class of uncaught throw this whole
67
+ // function exists to prevent, just for a different malformed input
68
+ // than the original NaN case. `-0` is rejected too (Object.is check):
69
+ // it is a safe integer and `-0 >= 0`, so without the explicit check it
70
+ // would silently mean "no timeout", one character away from the "0"
71
+ // this function deliberately allows.
72
+ // Exported test-only — the outcome-based integration tests (a real
73
+ // probe/export succeeding or not) can't distinguish `timeout: 0` from
74
+ // `timeout: <default>` when the underlying command finishes quickly
75
+ // either way, so a direct table of this function's own input/output
76
+ // pairs is the only thing that actually pins the "0"-vs-default
77
+ // boundary — found by a third scoped re-review: the blank-env-var
78
+ // integration test below passed even with the round-2 blank-handling
79
+ // bug fully reintroduced, for exactly this reason.
80
+ function _validTimeoutMs(raw, fallback) {
81
+ const s = String(raw ?? '').trim();
82
+ if (!s) return fallback;
83
+ const n = Number(s);
84
+ if (!Number.isSafeInteger(n) || n < 0 || Object.is(n, -0)) return fallback;
85
+ return n;
86
+ }
87
+ const PROBE_TIMEOUT_MS = _validTimeoutMs(process.env.AGENTIC_SECURITY_CHROME_PROBE_TIMEOUT_MS, 5000);
88
+
89
+ // Real, common install locations, per platform. Deliberately excludes
90
+ // AGENTIC_SECURITY_CHROME_PATH — that override is handled separately in
91
+ // probeChromeAvailable() as an authoritative, non-fallback choice (see
92
+ // the comment there for why).
93
+ //
94
+ // KNOWN-GOOD ABSOLUTE PATHS ARE TRIED FIRST, PATH-resolvable bare names
95
+ // LAST — found by the final whole-branch review, reproduced live: with
96
+ // bare names tried first, a `chrome` shell script anywhere earlier on
97
+ // PATH than the real browser is silently preferred and then executed
98
+ // with this process's own render arguments. The `--version` output
99
+ // check (`_tryBinary` below) is a functional smoke test, not a security
100
+ // gate — it cannot distinguish a real Chrome/Chromium binary from any
101
+ // script that echoes a matching string. Preferring the well-known
102
+ // install locations narrows, though does not eliminate, this exposure
103
+ // (a compromised PATH entry named exactly `chrome`/`google-chrome`/etc.
104
+ // is still tried if no absolute-path candidate exists on this machine —
105
+ // this feature already assumes local machine trust, same as every other
106
+ // "optional local tool" this codebase shells out to). The `linux` list
107
+ // below was empty in this fix's first pass — found by the scoped
108
+ // re-review to make the whole reordering a no-op on the platform CI
109
+ // images actually run on, since every Linux candidate was a bare PATH
110
+ // name. Package-manager and snap install locations are now included.
111
+ function _candidatePaths() {
112
+ const onPath = ['google-chrome-stable', 'google-chrome', 'chromium-browser', 'chromium', 'chrome'];
113
+ const platformPaths = {
114
+ darwin: [
115
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
116
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
117
+ `${process.env.HOME || ''}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
118
+ ],
119
+ win32: [
120
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
121
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
122
+ `${process.env.LOCALAPPDATA || ''}\\Google\\Chrome\\Application\\chrome.exe`,
123
+ ],
124
+ linux: [
125
+ '/usr/bin/google-chrome-stable',
126
+ '/usr/bin/google-chrome',
127
+ '/usr/bin/chromium-browser',
128
+ '/usr/bin/chromium',
129
+ '/opt/google/chrome/chrome',
130
+ '/snap/bin/chromium',
131
+ ],
132
+ };
133
+ const platformSpecific = platformPaths[process.platform] || [];
134
+ return [...platformSpecific, ...onPath];
135
+ }
136
+
137
+ // Tries one candidate binary. Returns {ok:true, chrome:bin} on a real,
138
+ // working Chrome/Chromium, or null on any failure (missing binary, spawn
139
+ // error, non-zero exit, unrecognized --version output) — never throws.
140
+ function _tryBinary(bin) {
141
+ if (!bin) return null;
142
+ // An absolute-path candidate that doesn't exist can't spawn — skip the
143
+ // spawnSync call entirely rather than let it throw ENOENT.
144
+ if (bin.includes('/') || bin.includes('\\')) {
145
+ if (!external_node_fs_.existsSync(bin)) return null;
146
+ }
147
+ let r;
148
+ try {
149
+ r = external_node_child_process_.spawnSync(bin, ['--version'], { encoding: 'utf8', timeout: PROBE_TIMEOUT_MS });
150
+ } catch { return null; }
151
+ if (r.error || r.status !== 0) return null;
152
+ const out = (r.stdout || r.stderr || '');
153
+ if (!/Chrom(e|ium)/i.test(out)) return null;
154
+ return { ok: true, chrome: bin };
155
+ }
156
+
157
+ function resetChromeProbe() { _capability = null; }
158
+
159
+ function probeChromeAvailable() {
160
+ if (_capability) return _capability;
161
+
162
+ // An explicit override is authoritative, not just "try this one first
163
+ // then fall back": an operator (or CI environment) who deliberately
164
+ // pointed AGENTIC_SECURITY_CHROME_PATH at a binary wants a failure
165
+ // reported when that binary doesn't work — not a silent switch to
166
+ // whatever else this process happens to auto-detect on the machine.
167
+ const fromEnv = process.env.AGENTIC_SECURITY_CHROME_PATH;
168
+ if (fromEnv) {
169
+ const hit = _tryBinary(fromEnv);
170
+ _capability = hit || { ok: false, reason: 'chrome-path-invalid' };
171
+ return _capability;
172
+ }
173
+
174
+ for (const bin of _candidatePaths()) {
175
+ const hit = _tryBinary(bin);
176
+ if (hit) { _capability = hit; return _capability; }
177
+ }
178
+ _capability = { ok: false, reason: 'no-chrome-found' };
179
+ return _capability;
180
+ }
181
+
182
+ ;// CONCATENATED MODULE: ./scripts/export-image.mjs
183
+ // export-image.mjs — Milestone 4, sub-project PNG/SVG/PDF export.
184
+ //
185
+ // Builds on the already-shipped self-contained HTML report
186
+ // (generate-html-report.mjs) and a real, locally discovered Chrome
187
+ // binary's own native headless flags — empirically confirmed this
188
+ // sub-project's own scoping investigation to produce deterministic PNG
189
+ // (byte-identical across repeated runs), valid PDF, and (via DOM
190
+ // extraction) valid SVG, with zero new code needed to render anything
191
+ // and zero new npm dependency.
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+ // A malformed env var (e.g. AGENTIC_SECURITY_CHROME_RENDER_TIMEOUT_MS=oops)
202
+ // must fall back to the default, never reach spawnSync as NaN — NaN as a
203
+ // spawnSync `timeout` option throws ERR_OUT_OF_RANGE synchronously, which
204
+ // none of the three export functions below catch (their try/finally only
205
+ // guards cleanup, not this construction-time computation).
206
+ //
207
+ // Blank/whitespace-only is treated as unset (falls back), not as `0` —
208
+ // found by this fix's own scoped re-review: `Number('')` is `0`, and a
209
+ // naive `n >= 0` guard let an empty-but-exported env var through as a
210
+ // real `timeout: 0`, which spawnSync treats as NO timeout at all — an
211
+ // unbounded-hang risk on a wedged Chrome. An explicit `"0"` is still
212
+ // honored (matches this file's pre-fix behavior for that literal
213
+ // value).
214
+ //
215
+ // Must be a safe INTEGER, not merely finite — found by a second scoped
216
+ // re-review, reproduced live: `Number.isFinite(1.5)` is true, so a
217
+ // fractional env var (e.g. "1.5") passed this guard unchanged and then
218
+ // hit spawnSync's own `timeout` option, which throws ERR_OUT_OF_RANGE
219
+ // for any non-integer. `-0` is rejected too (Object.is check): it is a
220
+ // safe integer and `-0 >= 0`, so it would otherwise silently mean "no
221
+ // timeout", one character away from the "0" this function deliberately
222
+ // allows. Kept identical to chrome-probe.mjs's own copy of this
223
+ // function rather than factored into a shared module — two small
224
+ // functions, not worth a new shared file for.
225
+ // Exported test-only — see chrome-probe.mjs's own copy of this
226
+ // function for why a direct input/output table, not an integration
227
+ // test, is what actually pins the "0"-vs-default boundary.
228
+ function export_image_validTimeoutMs(raw, fallback) {
229
+ const s = String(raw ?? '').trim();
230
+ if (!s) return fallback;
231
+ const n = Number(s);
232
+ if (!Number.isSafeInteger(n) || n < 0 || Object.is(n, -0)) return fallback;
233
+ return n;
234
+ }
235
+ const RENDER_TIMEOUT_MS = export_image_validTimeoutMs(process.env.AGENTIC_SECURITY_CHROME_RENDER_TIMEOUT_MS, 15000);
236
+
237
+ function _writeTempHtml(graph, opts) {
238
+ const html = (0,generate_html_report.generateHtmlReport)(graph, opts);
239
+ const dir = external_node_fs_.mkdtempSync(external_node_path_.join(external_node_os_.tmpdir(), 'agsec-export-'));
240
+ const file = external_node_path_.join(dir, 'report.html');
241
+ external_node_fs_.writeFileSync(file, html);
242
+ return { dir, file };
243
+ }
244
+
245
+ function _hashUrl(file, opts) {
246
+ // #view=<name> — the shell's own already-shipped hash-state mechanism
247
+ // (frontend/src/lib/state.js's parseStateFromHash, read at mount time
248
+ // by shell.js) — confirmed empirically this sub-project's own scoping
249
+ // investigation to select the initial active view with zero new
250
+ // frontend code. No `#` fragment when no view is requested (the shell
251
+ // defaults to 'architecture' on its own).
252
+ //
253
+ // pathToFileURL, never hand-built string concatenation: the temp
254
+ // directory comes from os.tmpdir(), which on some machines/CI images
255
+ // contains a literal `#` (found live by the final whole-branch
256
+ // review) — `file://${file}` truncates at that character, and Chrome
257
+ // silently renders its own internal error page for the broken URL
258
+ // instead of failing. pathToFileURL percent-encodes it correctly.
259
+ // Also the only correct way to build a file: URL on Windows, whose
260
+ // paths are never valid appended directly after `file://`.
261
+ const hash = opts.view ? `#view=${encodeURIComponent(opts.view)}` : '';
262
+ return (0,external_node_url_.pathToFileURL)(file).href + hash;
263
+ }
264
+
265
+ function _cleanup(dir) {
266
+ try { external_node_fs_.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ }
267
+ }
268
+
269
+ // Confirmed this sub-project's own scoping investigation: exactly ONE
270
+ // real `<svg>` element exists in the whole rendered page
271
+ // (architecture-view.js's own root, class="arch-view") — a direct
272
+ // source grep confirms svgEl()/createElementNS is used nowhere else.
273
+ // Only present when the Architecture View is the active view, though
274
+ // (see _verifyRealRender below for the view-agnostic check used for
275
+ // PNG/PDF, which can capture any view).
276
+ function _extractArchSvg(dom) {
277
+ const start = dom.indexOf('<svg class="arch-view"');
278
+ if (start === -1) return { ok: false, reason: 'no <svg class="arch-view"> element found in the rendered page' };
279
+ const end = dom.indexOf('</svg>', start);
280
+ if (end === -1) return { ok: false, reason: 'found <svg class="arch-view"> but no matching </svg> close tag' };
281
+ return { ok: true, markup: dom.slice(start, end + '</svg>'.length) };
282
+ }
283
+
284
+ // role="tablist" — shell.js's own view-switcher tab bar (frontend/src/shell.js),
285
+ // present in EVERY view (unlike the architecture-only <svg class="arch-view">
286
+ // _extractArchSvg looks for), and part of the shell chrome, not
287
+ // view-specific content. Its presence in a --dump-dom capture is this
288
+ // module's positive signal that Chrome actually rendered the real
289
+ // report and not its own internal error page (which exits 0 and still
290
+ // writes a screenshot/PDF file — found live by the final whole-branch
291
+ // review: a `#` in the temp path broke the URL, and the resulting
292
+ // correctly-dimensioned-but-wrong PNG was Chrome's own error page).
293
+ function _hasRealShellChrome(dom) {
294
+ return dom.includes('role="tablist"');
295
+ }
296
+
297
+ function _dumpDom(chrome, url, opts) {
298
+ const r = external_node_child_process_.spawnSync(chrome.chrome, [
299
+ '--headless=new', '--disable-gpu',
300
+ `--window-size=${opts.width || 1680},${opts.height || 945}`,
301
+ '--virtual-time-budget=5000',
302
+ '--dump-dom',
303
+ url,
304
+ ], { timeout: RENDER_TIMEOUT_MS, encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 });
305
+ if (r.error || r.status !== 0) {
306
+ return { ok: false, reason: `chrome dump-dom failed: ${r.error?.message || r.stderr || 'unknown error'}` };
307
+ }
308
+ return { ok: true, dom: r.stdout };
309
+ }
310
+
311
+ // Verifies the page actually rendered the real report (not Chrome's own
312
+ // error page for a broken URL) before PNG/PDF capture is trusted. Costs
313
+ // one extra Chrome invocation per PNG/PDF export — deliberate: a
314
+ // screenshot/PDF file gives no positive signal of its own content, and
315
+ // Chrome exits 0 with a written output file for its own error page too.
316
+ function _verifyRealRender(chrome, url, opts) {
317
+ const dumped = _dumpDom(chrome, url, opts);
318
+ if (!dumped.ok) return dumped;
319
+ if (!_hasRealShellChrome(dumped.dom)) {
320
+ return { ok: false, reason: 'rendered page has no real report shell (role="tablist") — likely a broken URL or a Chrome error page' };
321
+ }
322
+ return { ok: true };
323
+ }
324
+
325
+ async function exportPng(graph, opts = {}) {
326
+ const chrome = probeChromeAvailable();
327
+ if (!chrome.ok) return { ok: false, reason: `chrome not available: ${chrome.reason}` };
328
+ const { dir, file } = _writeTempHtml(graph, opts);
329
+ const width = opts.width || 1680;
330
+ const height = opts.height || 945;
331
+ const outPng = external_node_path_.join(dir, 'out.png');
332
+ try {
333
+ const url = _hashUrl(file, opts);
334
+ const verified = _verifyRealRender(chrome, url, opts);
335
+ if (!verified.ok) return verified;
336
+ const r = external_node_child_process_.spawnSync(chrome.chrome, [
337
+ '--headless=new', '--disable-gpu',
338
+ `--window-size=${width},${height}`,
339
+ '--force-device-scale-factor=1',
340
+ '--virtual-time-budget=5000',
341
+ `--screenshot=${outPng}`,
342
+ url,
343
+ ], { timeout: RENDER_TIMEOUT_MS, encoding: 'utf8' });
344
+ if (r.error || r.status !== 0 || !external_node_fs_.existsSync(outPng)) {
345
+ return { ok: false, reason: `chrome screenshot failed: ${r.error?.message || r.stderr || 'unknown error'}` };
346
+ }
347
+ return { ok: true, data: external_node_fs_.readFileSync(outPng) };
348
+ } finally {
349
+ _cleanup(dir);
350
+ }
351
+ }
352
+
353
+ async function exportPdf(graph, opts = {}) {
354
+ const chrome = probeChromeAvailable();
355
+ if (!chrome.ok) return { ok: false, reason: `chrome not available: ${chrome.reason}` };
356
+ const { dir, file } = _writeTempHtml(graph, opts);
357
+ const outPdf = external_node_path_.join(dir, 'out.pdf');
358
+ try {
359
+ const url = _hashUrl(file, opts);
360
+ const verified = _verifyRealRender(chrome, url, opts);
361
+ if (!verified.ok) return verified;
362
+ const r = external_node_child_process_.spawnSync(chrome.chrome, [
363
+ '--headless=new', '--disable-gpu',
364
+ '--virtual-time-budget=5000',
365
+ `--print-to-pdf=${outPdf}`,
366
+ url,
367
+ ], { timeout: RENDER_TIMEOUT_MS, encoding: 'utf8' });
368
+ if (r.error || r.status !== 0 || !external_node_fs_.existsSync(outPdf)) {
369
+ return { ok: false, reason: `chrome print-to-pdf failed: ${r.error?.message || r.stderr || 'unknown error'}` };
370
+ }
371
+ return { ok: true, data: external_node_fs_.readFileSync(outPdf) };
372
+ } finally {
373
+ _cleanup(dir);
374
+ }
375
+ }
376
+
377
+ async function exportSvg(graph, opts = {}) {
378
+ const chrome = probeChromeAvailable();
379
+ if (!chrome.ok) return { ok: false, reason: `chrome not available: ${chrome.reason}` };
380
+ const { dir, file } = _writeTempHtml(graph, opts);
381
+ try {
382
+ const url = _hashUrl(file, opts);
383
+ const dumped = _dumpDom(chrome, url, opts);
384
+ if (!dumped.ok) return dumped;
385
+ const extracted = _extractArchSvg(dumped.dom);
386
+ if (!extracted.ok) return extracted;
387
+ // Real, standalone SVG documents need an explicit xmlns — the
388
+ // in-page element inherits it implicitly from its HTML parent
389
+ // document, which a standalone .svg file does not have.
390
+ const standalone = extracted.markup.replace('<svg class="arch-view"', '<svg xmlns="http://www.w3.org/2000/svg" class="arch-view"');
391
+ return { ok: true, data: Buffer.from(standalone, 'utf8') };
392
+ } finally {
393
+ _cleanup(dir);
394
+ }
395
+ }
396
+
397
+
398
+ /***/ }),
399
+
400
+ /***/ 3180:
401
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
402
+
403
+
404
+ // EXPORTS
405
+ __webpack_require__.d(__webpack_exports__, {
406
+ generateHtmlReport: () => (/* binding */ generateHtmlReport)
407
+ });
408
+
409
+ // EXTERNAL MODULE: external "node:fs"
410
+ var external_node_fs_ = __webpack_require__(3024);
411
+ // EXTERNAL MODULE: external "node:path"
412
+ var external_node_path_ = __webpack_require__(6760);
413
+ // EXTERNAL MODULE: external "node:url"
414
+ var external_node_url_ = __webpack_require__(3136);
415
+ ;// CONCATENATED MODULE: ./scripts/bundle-frontend.mjs
416
+ // bundle-frontend.mjs — Milestone 4, sub-project Self-contained HTML
417
+ // report. A minimal, hand-rolled ES-module bundler, deliberately NOT a
418
+ // general one — see this sub-project's own scoping doc for why a real
419
+ // bundler dependency (esbuild etc.) was considered and NOT chosen:
420
+ // frontend/src/'s real module graph (confirmed by a full-tree grep before
421
+ // this file was written) uses ONLY named imports/exports, no default
422
+ // exports, no `export *`, no namespace or side-effect-only imports. This
423
+ // bundler supports exactly that subset and throws a clear error on
424
+ // anything else, rather than silently mishandling an unsupported form.
425
+ //
426
+ // Algorithm: parse each file's own import specifiers + exported top-level
427
+ // names via regex (safe here because the supported subset's syntax is
428
+ // simple and unambiguous — a full parser is not needed for this bounded,
429
+ // fully-enumerated 21-file input); topologically order every reachable
430
+ // module; wrap EVERY NON-ENTRY module in its own IIFE (this is what
431
+ // prevents two dependency files' same-named PRIVATE top-level bindings
432
+ // from colliding when concatenated — a real risk with naive flat
433
+ // concatenation, proven by this file's own test suite); each module's
434
+ // IIFE destructures its own imports from its dependencies' already-
435
+ // evaluated namespace objects (`const { a, b } = __NS_shared;`) and
436
+ // returns its own exports as a plain object.
437
+ //
438
+ // The ENTRY module is deliberately NOT wrapped in an IIFE: its body is
439
+ // emitted directly at the top level of the output (after its own
440
+ // destructured imports), so its top-level declarations — functions the
441
+ // caller needs to invoke directly, e.g. `bootstrap()` in a `<script>`
442
+ // tag, or (as this file's own tests require) a bare exported name called
443
+ // straight off the bundled output — become real top-level bindings
444
+ // rather than being trapped inside a closure whose return value is
445
+ // discarded. Wrapping the entry in an IIFE too (as an earlier draft of
446
+ // this algorithm did) type-checks and looks symmetrical, but it is a
447
+ // correctness bug: it makes every exported name of the entry module
448
+ // unreachable from outside the bundle, which is never what a bundle's
449
+ // caller wants. Only the entry is unwrapped — every other module is
450
+ // still fully IIFE-isolated, so the collision guarantee above is
451
+ // unaffected (there is exactly one unwrapped scope per bundle, by
452
+ // construction, since the entry is the unique DFS root and cannot be
453
+ // re-entered — a cycle back to it is rejected below as a circular
454
+ // import).
455
+
456
+
457
+
458
+
459
+ const IMPORT_RE = /import\s*\{([\s\S]*?)\}\s*from\s*['"](\.[^'"]+)['"];?/g;
460
+ const UNSUPPORTED_IMPORT_RE = /^import\s+(?!\{)/m;
461
+ const EXPORT_STAR_RE = /^export\s*\*/m;
462
+ const EXPORT_DEFAULT_RE = /^export\s+default\b/m;
463
+ // Three more real, unsupported forms — added after the final whole-branch
464
+ // review found each builds with NO error (`EXPORT_NAMED_RE` below requires
465
+ // `function`/`class`/`const`/`let`/`var` immediately after `export `, so
466
+ // `export async function` matches none of these guards OR the named-export
467
+ // extractor, and is silently dropped as neither a caught error nor a real
468
+ // export) and only fails later as a page-load SyntaxError, blanking the
469
+ // whole page with no visible error — `export async function` already
470
+ // exists in the real tree today (frontend/src/lib/api-client.js), outside
471
+ // today's bundled entry point's own reachable graph only by luck.
472
+ const EXPORT_ASYNC_RE = /^export\s+async\s+function/m;
473
+ const EXPORT_LIST_RE = /^export\s*\{/m;
474
+ const IMPORT_RENAME_RE = /\bas\b/;
475
+ const EXPORT_NAMED_RE = /^export\s+(?:function\*?|class|const|let|var)\s+([A-Za-z0-9_$]+)/gm;
476
+
477
+ function _nsName(absPath, seen) {
478
+ // A stable, collision-free JS identifier per module, derived from its
479
+ // path. seen is a Map<absPath, name> shared across the whole bundle run.
480
+ if (seen.has(absPath)) return seen.get(absPath);
481
+ const base = absPath.replace(/[^A-Za-z0-9_$]/g, '_');
482
+ const name = `__NS_${base}`;
483
+ seen.set(absPath, name);
484
+ return name;
485
+ }
486
+
487
+ function _parseModule(absPath) {
488
+ const src = external_node_fs_.readFileSync(absPath, 'utf8');
489
+ if (UNSUPPORTED_IMPORT_RE.test(src) || EXPORT_STAR_RE.test(src) || EXPORT_DEFAULT_RE.test(src) || EXPORT_ASYNC_RE.test(src) || EXPORT_LIST_RE.test(src)) {
490
+ throw new Error(`bundleFrontendModules: unsupported import/export form in ${absPath} — only named imports/exports are supported (no default/namespace/side-effect imports, no export */export default/export {}-list/export async function)`);
491
+ }
492
+ const imports = [];
493
+ let m;
494
+ IMPORT_RE.lastIndex = 0;
495
+ while ((m = IMPORT_RE.exec(src))) {
496
+ if (IMPORT_RENAME_RE.test(m[1])) {
497
+ throw new Error(`bundleFrontendModules: unsupported \`import { x as y }\` rename in ${absPath} — only bare named imports are supported`);
498
+ }
499
+ const names = m[1].split(',').map((s) => s.trim()).filter(Boolean);
500
+ const resolved = external_node_path_.resolve(external_node_path_.dirname(absPath), m[2]);
501
+ imports.push({ names, resolved });
502
+ }
503
+ const exportNames = [];
504
+ EXPORT_NAMED_RE.lastIndex = 0;
505
+ while ((m = EXPORT_NAMED_RE.exec(src))) exportNames.push(m[1]);
506
+ // Strip import lines entirely; strip the leading `export ` keyword from
507
+ // each exported declaration (the declaration itself — function/const/
508
+ // class — is kept verbatim, so the body is otherwise untouched).
509
+ const body = src
510
+ .replace(IMPORT_RE, '')
511
+ .replace(/^export\s+(?=(?:function\*?|class|const|let|var)\s)/gm, '');
512
+ return { absPath, body, imports, exportNames };
513
+ }
514
+
515
+ function bundleFrontendModules(entryAbsPath) {
516
+ const nsNames = new Map();
517
+ const parsed = new Map(); // absPath -> parsed module
518
+ const order = []; // topological, dependency-first
519
+ const visiting = new Set();
520
+
521
+ function visit(absPath) {
522
+ // `visiting` (currently-on-the-DFS-stack) must be checked BEFORE
523
+ // `parsed` (fully-resolved, safe to skip) — found by this task's own
524
+ // review: the original ordering set `parsed` before recursing into a
525
+ // module's own imports, so a cycle back to an ancestor hit the
526
+ // `parsed.has()` check first and returned silently, making the
527
+ // `visiting`-based throw below unreachable for ANY cycle. Reproduced
528
+ // live: a real entry<->b.js cycle built successfully at bundle time
529
+ // and only failed later, as a confusing `ReferenceError` (TDZ) when
530
+ // the broken output was executed, instead of this function's own
531
+ // clear build-time error.
532
+ if (visiting.has(absPath)) {
533
+ throw new Error(`bundleFrontendModules: circular import detected at ${absPath} — this bundler does not support cycles`);
534
+ }
535
+ if (parsed.has(absPath)) return;
536
+ visiting.add(absPath);
537
+ const mod = _parseModule(absPath);
538
+ parsed.set(absPath, mod);
539
+ for (const imp of mod.imports) visit(imp.resolved);
540
+ visiting.delete(absPath);
541
+ order.push(absPath);
542
+ }
543
+ visit(entryAbsPath);
544
+
545
+ const chunks = [];
546
+ for (const absPath of order) {
547
+ const mod = parsed.get(absPath);
548
+ const destructures = mod.imports
549
+ .map((imp) => ` const { ${imp.names.join(', ')} } = ${_nsName(imp.resolved, nsNames)};`)
550
+ .join('\n');
551
+
552
+ if (absPath === entryAbsPath) {
553
+ // Entry: emit unwrapped at top level so its declarations become
554
+ // real top-level bindings the caller can invoke — see the header
555
+ // comment for why this must not be an IIFE like every other module.
556
+ chunks.push(`${destructures}\n${mod.body}`);
557
+ continue;
558
+ }
559
+
560
+ const ns = _nsName(absPath, nsNames);
561
+ const returnObj = mod.exportNames.length ? ` return { ${mod.exportNames.join(', ')} };` : ' return {};';
562
+ chunks.push(`const ${ns} = (function() {\n${destructures}\n${mod.body}\n${returnObj}\n})();`);
563
+ }
564
+ const bundled = chunks.join('\n\n');
565
+
566
+ // Real gap found by the final whole-branch review, live-reproduced: the
567
+ // UNSUPPORTED_IMPORT_RE/EXPORT_STAR_RE/EXPORT_DEFAULT_RE guards above
568
+ // only cover THREE specific unsupported forms — real ES syntax this
569
+ // bundler doesn't handle (`export async function`, `export { g };`
570
+ // re-export lists, `import { h as hh }` renames) builds with NO error
571
+ // and only fails later, as a page-load-time SyntaxError that silently
572
+ // blanks the whole page (export-entry.js's own showError() never runs,
573
+ // since the entire inline <script> fails to parse before any of its
574
+ // code executes) — directly contradicting this file's own documented
575
+ // "throws a clear error on anything else" promise. `export async
576
+ // function` already exists in the real tree today
577
+ // (frontend/src/lib/api-client.js) — outside today's bundled entry
578
+ // point's own reachable graph only by luck. Rather than enumerating
579
+ // every unsupported form by name (the exact approach that missed these
580
+ // three), validate the FINAL bundle is syntactically valid JS before
581
+ // returning it — this converts any parse failure, including forms not
582
+ // yet imagined, into a clear build-time error. `new Function` compiles
583
+ // without executing (measured ~1ms on the real ~131KB bundle — cheap).
584
+ try {
585
+ // eslint-disable-next-line no-new-func
586
+ new Function(bundled);
587
+ } catch (e) {
588
+ throw new Error(`bundleFrontendModules: the assembled bundle is not valid JavaScript (${e.message}) — one of the ${order.length} modules likely uses an ES form this bundler doesn't support (only named imports/exports, no async/generator export shorthand ambiguity, no \`export { x };\` lists, no \`import { x as y }\` renames)`);
589
+ }
590
+ return bundled;
591
+ }
592
+
593
+ // EXTERNAL MODULE: ./src/lineage/export-json.js
594
+ var export_json = __webpack_require__(859);
595
+ ;// CONCATENATED MODULE: ./scripts/generate-html-report.mjs
596
+ // generate-html-report.mjs — Milestone 4, sub-project Self-contained
597
+ // HTML report. Assembles ONE offline-safe HTML document: inline CSS,
598
+ // one bundled inline <script> (Task 1's bundler over Task 2's
599
+ // export-entry.js), and the graph embedded via exportGraphJSON
600
+ // (redacted by default) as a global the bundled script reads.
601
+
602
+
603
+
604
+
605
+
606
+
607
+
608
+ const HERE = external_node_path_.dirname((0,external_node_url_.fileURLToPath)(import.meta.url));
609
+ const FRONTEND_ROOT = external_node_path_.resolve(HERE, '../../frontend');
610
+ const STYLES_DIR = external_node_path_.join(FRONTEND_ROOT, 'styles');
611
+ const ENTRY_PATH = external_node_path_.join(FRONTEND_ROOT, 'src', 'export-entry.js');
612
+
613
+ // NOT alphabetical — confirmed this session by reading the real
614
+ // frontend/index.html directly: its own <link> order is
615
+ // tokens.css, shell.css, architecture-view.css, inspector.css,
616
+ // privacy-view.css, trace-view.css, filter-rail.css, inventory-view.css,
617
+ // query-bar.css. `tokens.css` first is load-bearing (CSS custom
618
+ // properties the other 8 files consume via `var(--...)` — an alphabetical
619
+ // sort would put it 8th, after 7 files that reference undefined custom
620
+ // properties on first paint). Matching real cascade order avoids a real
621
+ // specificity/undefined-custom-property regression alphabetical sorting
622
+ // would introduce silently. Re-confirmed against the real index.html
623
+ // directly at implementation time (unchanged from the task-3 brief's own
624
+ // citation).
625
+ const CSS_LOAD_ORDER = [
626
+ 'tokens.css', 'shell.css', 'architecture-view.css', 'inspector.css',
627
+ 'privacy-view.css', 'trace-view.css', 'filter-rail.css',
628
+ 'inventory-view.css', 'query-bar.css',
629
+ ];
630
+
631
+ function _inlineCss() {
632
+ const onDisk = new Set(external_node_fs_.readdirSync(STYLES_DIR).filter((f) => f.endsWith('.css')));
633
+ const missing = CSS_LOAD_ORDER.filter((f) => !onDisk.has(f));
634
+ if (missing.length) throw new Error(`generate-html-report: CSS_LOAD_ORDER names files not found in ${STYLES_DIR}: ${missing.join(', ')} — frontend/index.html's own <link> list may have changed; re-sync CSS_LOAD_ORDER with it`);
635
+ const extra = [...onDisk].filter((f) => !CSS_LOAD_ORDER.includes(f));
636
+ if (extra.length) throw new Error(`generate-html-report: real CSS files not in CSS_LOAD_ORDER: ${extra.join(', ')} — a new stylesheet was added to frontend/styles/ without updating this list's load order`);
637
+ return CSS_LOAD_ORDER.map((f) => external_node_fs_.readFileSync(external_node_path_.join(STYLES_DIR, f), 'utf8')).join('\n');
638
+ }
639
+
640
+ function generateHtmlReport(graph, opts = {}) {
641
+ const exported = (0,export_json.exportGraphJSON)(graph, opts);
642
+ const css = _inlineCss();
643
+ const bundledJs = bundleFrontendModules(ENTRY_PATH);
644
+ // Escape `<` so scanned-source-derived content (a node/flow/data-element
645
+ // label — none of which _redactGraph covers, since labels aren't a
646
+ // secret-shaped surface, but CAN carry arbitrary scanned identifier
647
+ // text) can never contain a literal `</script>` that breaks out of this
648
+ // inline data script and injects a second one. Same mitigation, same
649
+ // reasoning, as the existing SAST/SCA HTML report's own precedent
650
+ // (scanner/src/report/index.js's toHTML) — found missing here and
651
+ // fixed by this sub-project's own final task review, which reproduced
652
+ // the injection live before this fix landed.
653
+ const envelopeJson = JSON.stringify(exported).replace(/</g, '\\u003c');
654
+ return `<!DOCTYPE html>
655
+ <html lang="en" data-theme="dark">
656
+ <head>
657
+ <meta charset="UTF-8" />
658
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
659
+ <title>Data Flow Explorer — self-contained report</title>
660
+ <style>
661
+ ${css}
662
+ </style>
663
+ </head>
664
+ <body>
665
+ <div id="app-root"></div>
666
+ <script>
667
+ // The full exportGraphJSON envelope (digest/scope/coverage/limitations/
668
+ // confidential + the redacted graph body) is embedded whole here, under
669
+ // its own name, so this report carries the same tamper-evident/disclosure
670
+ // metadata the JSON exporter produces (PRD §17.5) — never dropped just
671
+ // because this is an HTML report rather than a .json file.
672
+ //
673
+ // __AGENTIC_SECURITY_EXPORTED_GRAPH__ is then bound to the envelope's own
674
+ // \`.graph\` body, NOT the envelope itself — this is a deliberate,
675
+ // necessary departure from an earlier draft of this generator (which
676
+ // assigned the whole envelope directly to that name). frontend/src/
677
+ // export-entry.js (Task 2, already shipped and covered by its own golden
678
+ // DOM-equivalence test, test/bundle-frontend-golden.test.js) reads this
679
+ // EXACT global and passes it straight through to bootstrap(rootEl, graph)
680
+ // -> mountShell(rootEl, graph)/computeFilterFacets(graph), both of which
681
+ // expect a real DataFlowGraph v1 document (top-level nodes/edges/flows/...),
682
+ // never the exportGraphJSON envelope shape (which nests the graph one
683
+ // level down, under .graph, alongside digest/scope/confidential). Handing
684
+ // bootstrap() the envelope instead of the graph would silently break real
685
+ // rendering — exactly what this sub-project's own real-Chrome acceptance
686
+ // proof (task-3 brief, Step 5) exists to catch. Confirmed against
687
+ // export-entry.js's and Task 2's golden test's real source before this
688
+ // generator was written, not assumed.
689
+ window.__AGENTIC_SECURITY_EXPORT_ENVELOPE__ = ${envelopeJson};
690
+ window.__AGENTIC_SECURITY_EXPORTED_GRAPH__ = window.__AGENTIC_SECURITY_EXPORT_ENVELOPE__.graph;
691
+ </script>
692
+ <script>
693
+ ${bundledJs}
694
+ </script>
695
+ </body>
696
+ </html>`;
697
+ }
698
+
699
+
700
+ /***/ })
701
+
702
+ };