0sec-cli 0.22.0 → 0.22.4

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 (107) hide show
  1. package/0.js +56 -45
  2. package/README.md +12 -0
  3. package/chunks/{adapt-loop-7I57LXXR.js → adapt-loop-3CCOWE43.js} +4 -4
  4. package/chunks/{artifact-scraper-PYZ3RU3O.js → artifact-scraper-Z4UUIOH3.js} +2 -2
  5. package/chunks/{assumption-mining-MFLF43IR.js → assumption-mining-2UAKQB6V.js} +18 -14
  6. package/chunks/{chunk-RE5EZDG2.js → chunk-2WWAPKAW.js} +126 -1089
  7. package/chunks/{chunk-LPTRPRZR.js → chunk-3BPX75OR.js} +1 -1
  8. package/chunks/chunk-4ZYYX5GY.js +74 -0
  9. package/chunks/{chunk-RSIUX3RW.js → chunk-5JNTMWPK.js} +2 -2
  10. package/chunks/{chunk-RRD4XXPU.js → chunk-5WAJGLCW.js} +3 -3
  11. package/chunks/{chunk-76KSBWOQ.js → chunk-6KFB6AYN.js} +515 -20
  12. package/chunks/{chunk-HR2TU42B.js → chunk-6WQDXWQC.js} +169 -15
  13. package/chunks/chunk-6YZE552K.js +5603 -0
  14. package/chunks/chunk-BGX53365.js +309 -0
  15. package/chunks/{chunk-7DEHG7YT.js → chunk-C6JPA32H.js} +1 -1
  16. package/chunks/chunk-CF3MNS3O.js +990 -0
  17. package/chunks/{chunk-4VGLO2YS.js → chunk-CPCHLL6A.js} +5 -2
  18. package/chunks/{chunk-DM5C5L5D.js → chunk-DPUXXMJW.js} +1 -1
  19. package/chunks/{chunk-LYUAM3TG.js → chunk-E3OOBVOK.js} +30 -12
  20. package/chunks/{chunk-HUNAPTKV.js → chunk-E74LXQFY.js} +1 -1
  21. package/chunks/{chunk-UU7PQP6N.js → chunk-ELI3BVIK.js} +3 -3
  22. package/chunks/chunk-FFHRANYC.js +547 -0
  23. package/chunks/{chunk-IO7R7WBM.js → chunk-G2BE2FC7.js} +1 -1
  24. package/chunks/chunk-GQ3DGHWE.js +1659 -0
  25. package/chunks/{chunk-V36HVTUL.js → chunk-GVY3S5E5.js} +21 -18
  26. package/chunks/{chunk-UV5QWZGE.js → chunk-H4DH7COV.js} +192 -40
  27. package/chunks/{chunk-SJBCK2EI.js → chunk-H6Q2QAIY.js} +23475 -22747
  28. package/chunks/{chunk-VMRRNRMR.js → chunk-IHPTOTLL.js} +1 -1
  29. package/chunks/chunk-IXAHQICR.js +31 -0
  30. package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
  31. package/chunks/{chunk-VNRY6AWF.js → chunk-L2CCO4KE.js} +282 -34
  32. package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
  33. package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
  34. package/chunks/{chunk-EGZGZFAH.js → chunk-OAS5SAVZ.js} +6768 -2100
  35. package/chunks/chunk-PS5E7LUQ.js +277 -0
  36. package/chunks/{chunk-2GV4HQKE.js → chunk-QVIRWJ45.js} +257 -145
  37. package/chunks/{chunk-OWLJYJ5O.js → chunk-QWK4R3DB.js} +9 -9
  38. package/chunks/{chunk-XFCTUKXR.js → chunk-RCRTSKQN.js} +1 -1
  39. package/chunks/chunk-RS4MAN3Q.js +73 -0
  40. package/chunks/{chunk-7UFUALR2.js → chunk-RTBZ36BD.js} +8 -274
  41. package/chunks/{chunk-TZFIAMWZ.js → chunk-SCWWLABR.js} +1 -1
  42. package/chunks/chunk-VDOWARMJ.js +63 -0
  43. package/chunks/{chunk-5MWAG3FT.js → chunk-WBRGXCU2.js} +79 -18
  44. package/chunks/{chunk-LCRTAQN2.js → chunk-WHUAUFSX.js} +1 -1
  45. package/chunks/{chunk-YIQSGNAA.js → chunk-XODDLNBI.js} +1535 -1306
  46. package/chunks/chunk-YAZARMJY.js +355 -0
  47. package/chunks/{chunk-OAYUPGF6.js → chunk-YED25PA4.js} +1 -1
  48. package/chunks/{codex-models-7TLJSY3L.js → codex-models-Z43V3427.js} +9 -3
  49. package/chunks/{commands-NEI7L3QA.js → commands-EUGF4FUZ.js} +5844 -4095
  50. package/chunks/{db-5AP3JB4W.js → db-NVAW4WWR.js} +3 -3
  51. package/chunks/{dist-3FNZIBB4.js → dist-27BNG6WD.js} +195 -112
  52. package/chunks/{dist-5YHYNLI3.js → dist-7RUOKPRJ.js} +12 -2
  53. package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
  54. package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
  55. package/chunks/{dist-DD4H6HMQ.js → dist-NG2HT5XU.js} +27 -1
  56. package/chunks/{eval-runner-43BO2C4T.js → eval-runner-6Q3H52SP.js} +13 -9
  57. package/chunks/{exploit-agent-N4A5ELBK.js → exploit-agent-FCCR2HYX.js} +1 -1
  58. package/chunks/{exploit-autoclimb-N7P6KIF5.js → exploit-autoclimb-X5ZS5FAO.js} +1 -1
  59. package/chunks/{exploit-climb-WNEJUB5W.js → exploit-climb-3JHGEO36.js} +2 -2
  60. package/chunks/{fix-Z452M4EC.js → fix-NSNPU33B.js} +12 -6
  61. package/chunks/{harness-QJJJVSG4.js → harness-KXQOVIDQ.js} +3 -3
  62. package/chunks/{hunt-scan-K6D2V3PX.js → hunt-scan-ELTKGKC4.js} +16 -12
  63. package/chunks/{kernel-primitive-TKUACPKD.js → kernel-primitive-MT7YZSIG.js} +2 -2
  64. package/chunks/{kernel-vm-runner-RAAO2PGT.js → kernel-vm-runner-YFRHYC6G.js} +2 -2
  65. package/chunks/{native-loop-2ZRJENZ3.js → native-loop-OFJDRHPI.js} +18 -8
  66. package/chunks/orchestrate-DRZ2SVCP.js +62 -0
  67. package/chunks/{prepare-KYHGFWCJ.js → prepare-72GWTRVI.js} +6 -2
  68. package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
  69. package/chunks/{run-U3XU4CTC.js → run-WTJCZZHV.js} +3091 -6037
  70. package/chunks/{runtime-G75TEWQY.js → runtime-FCKOUB7V.js} +10 -4
  71. package/chunks/{runtime-XFWJTBUO.js → runtime-LCJ4XSKE.js} +5 -1
  72. package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-GT6GKQ3U.js} +7 -1
  73. package/chunks/{scope-WXI46UHJ.js → scope-E5QZUOOH.js} +2 -2
  74. package/chunks/service-plugins-MSK2EQP6.js +62 -0
  75. package/chunks/{session-store-27L3ZU73.js → session-store-H3Q7KCYG.js} +6 -4
  76. package/chunks/{variant-candidates-YWZORZMK.js → variant-candidates-SZKDCOLK.js} +9 -3
  77. package/chunks/{web-recon-prepass-YVMFXCIK.js → web-recon-prepass-IO3GXQMW.js} +11 -6
  78. package/dashboard/apple-touch-icon.png +0 -0
  79. package/dashboard/assets/operations-BvB5Qpmk.css +1 -0
  80. package/dashboard/assets/operations-D8Ii01SV.js +114 -0
  81. package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
  82. package/dashboard/favicon.ico +0 -0
  83. package/dashboard/favicon.svg +10 -0
  84. package/dashboard/index.html +5 -3
  85. package/package.json +1 -1
  86. package/chunks/chunk-6GHSR47F.js +0 -147
  87. package/chunks/chunk-CAYANSH6.js +0 -3524
  88. package/chunks/chunk-GHWODZMR.js +0 -16
  89. package/chunks/chunk-MICHDTBK.js +0 -193
  90. package/chunks/orchestrate-2GWGNTBF.js +0 -58
  91. package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
  92. package/dashboard/assets/dist-B_dDCPbu.js +0 -1
  93. package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
  94. package/dashboard/assets/format--susmHX7.js +0 -1
  95. package/dashboard/assets/input-TuVPR3Zl.js +0 -1
  96. package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
  97. package/dashboard/assets/live-page-HWWHX99U.js +0 -1
  98. package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
  99. package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
  100. package/dashboard/assets/operations-CMFVrXEe.css +0 -2
  101. package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
  102. package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
  103. package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
  104. package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
  105. package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
  106. package/dashboard/assets/table-8EDklulN.js +0 -1
  107. package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
@@ -0,0 +1,1659 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __0CreateRequire } from "node:module";
3
+ const require = __0CreateRequire(import.meta.url);
4
+ import {
5
+ source_default
6
+ } from "./chunk-HLFS6WV2.js";
7
+ import {
8
+ SEVERITY_RANK,
9
+ VERSION,
10
+ createAuditReportDocument,
11
+ createReviewReportDocument,
12
+ createScanReportDocument,
13
+ severityRank
14
+ } from "./chunk-6KFB6AYN.js";
15
+
16
+ // packages/cli/src/utils.ts
17
+ import { gzipSync } from "zlib";
18
+ async function getRuntimeAvailability() {
19
+ const { detectAvailableRuntimes, LlmApiRuntime } = await import("./dist-27BNG6WD.js");
20
+ const apiRuntimeDiagnostics = new LlmApiRuntime({ type: "api", timeout: 5e3 }).getConfigurationDiagnostics();
21
+ const hasApiKey = apiRuntimeDiagnostics.valid;
22
+ const availableRuntimes = [...await detectAvailableRuntimes()];
23
+ return {
24
+ hasApiKey,
25
+ availableRuntimes,
26
+ apiRuntime: {
27
+ configured: apiRuntimeDiagnostics.reason !== "missing_key",
28
+ valid: apiRuntimeDiagnostics.valid,
29
+ providerLabel: apiRuntimeDiagnostics.providerLabel,
30
+ error: apiRuntimeDiagnostics.fatalError
31
+ }
32
+ };
33
+ }
34
+ async function checkRuntimeAvailability(runtime) {
35
+ const availability = await getRuntimeAvailability();
36
+ const { hasApiKey, availableRuntimes, apiRuntime } = availability;
37
+ if (hasApiKey) return;
38
+ console.log("");
39
+ if ((runtime === "api" || runtime === "auto") && apiRuntime.configured && apiRuntime.error) {
40
+ console.log(source_default.red(` ${apiRuntime.providerLabel} config error: ${apiRuntime.error.split("\n")[0]}`));
41
+ }
42
+ if (runtime === "api") {
43
+ console.log(source_default.yellow(
44
+ apiRuntime.configured ? " Warning: `--runtime api` needs a valid provider configuration. AI analysis will fail until this is fixed." : " Warning: `--runtime api` needs provider credentials. AI analysis may be skipped."
45
+ ));
46
+ } else if (availableRuntimes.length > 0) {
47
+ console.log(source_default.cyan(` Using local runtime(s): ${availableRuntimes.join(", ")}`));
48
+ } else {
49
+ console.log(source_default.yellow(" Warning: No API key or local agent runtime detected. AI analysis will be skipped."));
50
+ }
51
+ console.log(source_default.gray(" Provider credentials: ZERO_CHATGPT_ACCESS_TOKEN | ZERO_CHATGPT_OAUTH_REFRESH_TOKEN, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, OPENAI_API_KEY"));
52
+ console.log("");
53
+ }
54
+ function buildShareUrl(report) {
55
+ const json = JSON.stringify(report);
56
+ const compressed = gzipSync(Buffer.from(json, "utf-8"));
57
+ const b64 = compressed.toString("base64url");
58
+ return `https://0.security/r#${b64}`;
59
+ }
60
+
61
+ // packages/cli/src/formatters/terminal.ts
62
+ var SEVERITY_STYLE = {
63
+ critical: {
64
+ badge: (s) => source_default.bgRed.white.bold(` ${s} `),
65
+ label: "Critical",
66
+ icon: "\u25CF"
67
+ },
68
+ high: {
69
+ badge: (s) => source_default.bgRedBright.white.bold(` ${s} `),
70
+ label: "High",
71
+ icon: "\u25CF"
72
+ },
73
+ medium: {
74
+ badge: (s) => source_default.bgYellow.black.bold(` ${s} `),
75
+ label: "Medium",
76
+ icon: "\u25CF"
77
+ },
78
+ low: {
79
+ badge: (s) => source_default.bgBlue.white(` ${s} `),
80
+ label: "Low",
81
+ icon: "\u25CB"
82
+ },
83
+ info: {
84
+ badge: (s) => source_default.bgGray.white(` ${s} `),
85
+ label: "Info",
86
+ icon: "\xB7"
87
+ }
88
+ };
89
+ var SEVERITY_COUNT_COLOR = {
90
+ critical: source_default.red.bold,
91
+ high: source_default.redBright.bold,
92
+ medium: source_default.yellow.bold,
93
+ low: source_default.blue,
94
+ info: source_default.gray
95
+ };
96
+ function formatTerminal(report) {
97
+ const W = 60;
98
+ const lines = [];
99
+ lines.push("");
100
+ if (report.warnings.length > 0) {
101
+ lines.push(` ${source_default.yellow.bold("WARNINGS")}`);
102
+ lines.push("");
103
+ for (const warning of report.warnings) {
104
+ lines.push(
105
+ ` ${source_default.yellow.bold("!")} ${source_default.yellow(`[${warning.stage}] ${warning.message}`)}`
106
+ );
107
+ }
108
+ lines.push("");
109
+ }
110
+ if (report.findings.length === 0) {
111
+ lines.push("");
112
+ if (report.warnings.length > 0 || report.executionSuccessful === false) {
113
+ lines.push(
114
+ ` ${source_default.yellow.bold("!")} ${source_default.yellow("No vulnerabilities confirmed. Scan finished with warnings.")}`
115
+ );
116
+ } else if (report.reviewChecks?.some((check) => check.status !== "pass")) {
117
+ lines.push(` ${source_default.yellow.bold("!")} ${source_default.yellow("No security vulnerabilities found. Advisory review checks need attention.")}`);
118
+ } else {
119
+ lines.push(` ${source_default.green.bold("\u2713")} ${source_default.green("No vulnerabilities found.")}`);
120
+ }
121
+ } else {
122
+ lines.push(` ${source_default.bold.white("FINDINGS")}`);
123
+ lines.push("");
124
+ const sorted = [...report.findings].sort(
125
+ (a, b) => severityRank(b.severity) - severityRank(a.severity)
126
+ );
127
+ for (const finding of sorted) {
128
+ lines.push(formatFinding(finding));
129
+ }
130
+ }
131
+ if (report.reviewChecks?.length) {
132
+ lines.push("", ` ${source_default.bold.white("REVIEW CHECKS (advisory)")}`, "");
133
+ for (const check of report.reviewChecks) {
134
+ const color = check.status === "pass" ? source_default.green : source_default.yellow;
135
+ lines.push(` ${color(check.status.toUpperCase())} ${check.name} (${check.id})`);
136
+ lines.push(` Reason: ${check.reason}`);
137
+ if (check.fix) lines.push(` Suggested fix: ${check.fix}`);
138
+ lines.push("");
139
+ }
140
+ }
141
+ lines.push("");
142
+ lines.push(` ${source_default.gray("\u2500".repeat(50))}`);
143
+ lines.push("");
144
+ const { summary } = report;
145
+ const sev = [
146
+ summary.critical > 0 ? source_default.red.bold(`${summary.critical} critical`) : source_default.gray(`${summary.critical} critical`),
147
+ summary.high > 0 ? source_default.hex("#f97316").bold(`${summary.high} high`) : source_default.gray(`${summary.high} high`),
148
+ summary.medium > 0 ? source_default.yellow.bold(`${summary.medium} medium`) : source_default.gray(`${summary.medium} medium`),
149
+ source_default.gray(`${summary.low} low`),
150
+ source_default.gray(`${summary.info} info`)
151
+ ].join(source_default.gray(" "));
152
+ lines.push(` ${sev}`);
153
+ lines.push(` ${source_default.white.bold(String(summary.totalFindings))} findings ${source_default.gray("in")} ${source_default.white(formatDuration(report.durationMs))}`);
154
+ lines.push(` ${source_default.gray("Share:")} ${source_default.cyan(buildShareUrl(report))}`);
155
+ lines.push("");
156
+ return lines.join("\n");
157
+ }
158
+ function formatFinding(finding) {
159
+ const style = SEVERITY_STYLE[finding.severity];
160
+ const lines = [];
161
+ lines.push(` ${style.badge(style.label.toUpperCase().padEnd(8))} ${source_default.bold.white(finding.title)}`);
162
+ const meta = [];
163
+ meta.push(`${source_default.gray("Category:")} ${formatCategory(finding.category)}`);
164
+ if (finding.status === "confirmed") {
165
+ meta.push(source_default.green("\u2713 Confirmed"));
166
+ }
167
+ lines.push(` ${" ".repeat(11)}${meta.join(source_default.gray(" \xB7 "))}`);
168
+ if (finding.description) {
169
+ const desc = finding.description.length > 100 ? finding.description.slice(0, 97) + "..." : finding.description;
170
+ lines.push(` ${" ".repeat(11)}${source_default.gray(desc)}`);
171
+ }
172
+ if (finding.evidence.analysis) {
173
+ const ev = finding.evidence.analysis.length > 100 ? finding.evidence.analysis.slice(0, 97) + "..." : finding.evidence.analysis;
174
+ lines.push(` ${" ".repeat(11)}${source_default.gray.italic("\u21B3 " + ev)}`);
175
+ }
176
+ lines.push("");
177
+ return lines.join("\n");
178
+ }
179
+ function formatCategory(cat) {
180
+ return cat.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
181
+ }
182
+ function formatDuration(ms) {
183
+ if (ms < 1e3) return `${ms}ms`;
184
+ return `${(ms / 1e3).toFixed(1)}s`;
185
+ }
186
+
187
+ // packages/cli/src/formatters/json.ts
188
+ function formatJson(report) {
189
+ return JSON.stringify(report, null, 2);
190
+ }
191
+
192
+ // packages/cli/src/formatters/markdown.ts
193
+ var EVIDENCE_BLOCK_LIMIT = 4e3;
194
+ var POC_STEP_LIMIT = 20;
195
+ function formatMarkdown(report) {
196
+ const lines = [];
197
+ lines.push("# 0 Scan Report");
198
+ lines.push("");
199
+ lines.push(`| Field | Value |`);
200
+ lines.push(`|-------|-------|`);
201
+ lines.push(`| Target | ${report.target} |`);
202
+ lines.push(`| Depth | ${report.scanDepth} |`);
203
+ lines.push(`| Started | ${report.startedAt} |`);
204
+ lines.push(`| Duration | ${(report.durationMs / 1e3).toFixed(1)}s |`);
205
+ lines.push("");
206
+ lines.push("## Summary");
207
+ lines.push("");
208
+ lines.push(`- **Attacks:** ${report.summary.totalAttacks}`);
209
+ lines.push(`- **Findings:** ${report.summary.totalFindings}`);
210
+ if (report.summary.critical > 0)
211
+ lines.push(`- **Critical:** ${report.summary.critical}`);
212
+ if (report.summary.high > 0) lines.push(`- **High:** ${report.summary.high}`);
213
+ if (report.summary.medium > 0) lines.push(`- **Medium:** ${report.summary.medium}`);
214
+ if (report.summary.low > 0) lines.push(`- **Low:** ${report.summary.low}`);
215
+ lines.push("");
216
+ if (report.warnings.length > 0) {
217
+ lines.push("## Warnings");
218
+ lines.push("");
219
+ for (const warning of report.warnings) {
220
+ lines.push(`- **${warning.stage}:** ${warning.message}`);
221
+ }
222
+ lines.push("");
223
+ }
224
+ if (report.reviewChecks?.length) {
225
+ lines.push("## Review Checks (Advisory)", "");
226
+ const literal = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/([\\`*_{}\[\]()#+.!|~-])/g, "\\$1");
227
+ for (const check of report.reviewChecks) {
228
+ lines.push(`### ${check.status.toUpperCase()}: ${literal(check.name)}`, "");
229
+ lines.push(`- **ID:** ${literal(check.id)}`);
230
+ lines.push(`- **Reason:** ${literal(check.reason)}`);
231
+ if (check.fix) lines.push(`- **Suggested fix:** ${literal(check.fix)}`);
232
+ lines.push("");
233
+ }
234
+ }
235
+ if (report.findings.length > 0) {
236
+ lines.push("## Findings");
237
+ lines.push("");
238
+ for (const finding of report.findings) {
239
+ lines.push(formatFinding2(finding));
240
+ }
241
+ } else {
242
+ lines.push(report.warnings.length > 0 ? "## No Confirmed Vulnerabilities" : "## No Vulnerabilities Found");
243
+ lines.push("");
244
+ lines.push(
245
+ report.warnings.length > 0 ? "The scanner did not confirm vulnerabilities, but target validation or probe execution produced warnings." : report.reviewChecks?.length ? "No security vulnerabilities were found in the inspected scope. Advisory review-check results are reported separately above." : "The target passed all tests."
246
+ );
247
+ }
248
+ return lines.join("\n");
249
+ }
250
+ function evidenceBlock(label, raw) {
251
+ const lines = [];
252
+ lines.push(`**${label}:**`);
253
+ if (!raw) {
254
+ lines.push("");
255
+ lines.push("_(not captured)_");
256
+ return lines;
257
+ }
258
+ const truncated = raw.length > EVIDENCE_BLOCK_LIMIT;
259
+ const body = truncated ? raw.slice(0, EVIDENCE_BLOCK_LIMIT) : raw;
260
+ lines.push("```");
261
+ lines.push(body);
262
+ lines.push("```");
263
+ if (truncated) {
264
+ lines.push(
265
+ `_Truncated for readability: ${raw.length - EVIDENCE_BLOCK_LIMIT} of ${raw.length} characters not shown. Use \`--format json\` or \`--format sarif\` for the complete evidence._`
266
+ );
267
+ }
268
+ return lines;
269
+ }
270
+ function remediationSection(remediation) {
271
+ const lines = [];
272
+ lines.push("**Remediation:**");
273
+ lines.push("");
274
+ lines.push(remediation.summary);
275
+ if (remediation.steps.length > 0) {
276
+ lines.push("");
277
+ for (const step of remediation.steps) lines.push(`1. ${step}`);
278
+ }
279
+ if (remediation.codeExample) {
280
+ const { before, after, language } = remediation.codeExample;
281
+ lines.push("");
282
+ lines.push("<details>");
283
+ lines.push("<summary>Suggested change</summary>");
284
+ lines.push("");
285
+ lines.push("Before:");
286
+ lines.push("");
287
+ lines.push("```" + language);
288
+ lines.push(before);
289
+ lines.push("```");
290
+ lines.push("");
291
+ lines.push("After:");
292
+ lines.push("");
293
+ lines.push("```" + language);
294
+ lines.push(after);
295
+ lines.push("```");
296
+ lines.push("</details>");
297
+ }
298
+ if (remediation.references.length > 0) {
299
+ lines.push("");
300
+ lines.push("References:");
301
+ for (const ref of remediation.references) lines.push(`- ${ref}`);
302
+ }
303
+ lines.push("");
304
+ return lines;
305
+ }
306
+ function reproductionSection(steps) {
307
+ const lines = [];
308
+ lines.push("**Reproduction steps:**");
309
+ lines.push("");
310
+ const shown = steps.slice(0, POC_STEP_LIMIT);
311
+ shown.forEach((step, i) => {
312
+ lines.push(`${i + 1}. **[${step.kind}]** ${step.summary}`);
313
+ });
314
+ if (steps.length > shown.length) {
315
+ lines.push("");
316
+ lines.push(
317
+ `_${steps.length - shown.length} further step(s) omitted; the full graph is in \`--format json\`._`
318
+ );
319
+ }
320
+ lines.push("");
321
+ return lines;
322
+ }
323
+ function formatFinding2(finding) {
324
+ const lines = [];
325
+ const badge = severityBadge(finding.severity);
326
+ lines.push(`### ${badge} ${finding.title}`);
327
+ lines.push("");
328
+ lines.push(`- **Category:** ${finding.category}`);
329
+ lines.push(`- **Status:** ${finding.status}`);
330
+ if (finding.cvssScore !== void 0 || finding.cvssVector) {
331
+ const score = finding.cvssScore !== void 0 ? `${finding.cvssScore}` : "n/a";
332
+ const vector = finding.cvssVector ? ` (\`${finding.cvssVector}\`)` : "";
333
+ lines.push(`- **CVSS:** ${score}${vector}`);
334
+ }
335
+ if (finding.confidence !== void 0) {
336
+ lines.push(`- **Confidence:** ${(finding.confidence * 100).toFixed(0)}%`);
337
+ }
338
+ if (finding.triageNote) {
339
+ lines.push(`- **Triage:** ${finding.triageNote}`);
340
+ }
341
+ lines.push(`- **Description:** ${finding.description}`);
342
+ lines.push("");
343
+ if (finding.evidence.analysis) {
344
+ lines.push(`**Evidence:** ${finding.evidence.analysis}`);
345
+ lines.push("");
346
+ }
347
+ if (finding.pocSteps && finding.pocSteps.length > 0) {
348
+ lines.push(...reproductionSection(finding.pocSteps));
349
+ }
350
+ if (finding.remediation) {
351
+ lines.push(...remediationSection(finding.remediation));
352
+ }
353
+ lines.push("<details>");
354
+ lines.push("<summary>Request / Response</summary>");
355
+ lines.push("");
356
+ lines.push(...evidenceBlock("Request", finding.evidence.request));
357
+ lines.push("");
358
+ lines.push(...evidenceBlock("Response", finding.evidence.response));
359
+ lines.push("</details>");
360
+ lines.push("");
361
+ return lines.join("\n");
362
+ }
363
+ function severityBadge(severity) {
364
+ const badges = {
365
+ critical: "[CRITICAL]",
366
+ high: "[HIGH]",
367
+ medium: "[MEDIUM]",
368
+ low: "[LOW]",
369
+ info: "[INFO]"
370
+ };
371
+ return badges[severity] ?? `[${severity.toUpperCase()}]`;
372
+ }
373
+
374
+ // packages/cli/src/formatters/html.ts
375
+ function escapeHtml(str) {
376
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
377
+ }
378
+ var SEVERITY_COLORS = {
379
+ critical: { bg: "#991b1b", text: "#ffffff", accent: "#f87171" },
380
+ high: { bg: "#c2410c", text: "#ffffff", accent: "#fb923c" },
381
+ medium: { bg: "#a16207", text: "#000000", accent: "#facc15" },
382
+ low: { bg: "#1d4ed8", text: "#ffffff", accent: "#60a5fa" },
383
+ info: { bg: "#4b5563", text: "#ffffff", accent: "#9ca3af" }
384
+ };
385
+ var SEVERITY_ORDER = Object.keys(SEVERITY_RANK).sort(
386
+ (a, b) => SEVERITY_RANK[b] - SEVERITY_RANK[a]
387
+ );
388
+ function formatCategory2(cat) {
389
+ return cat.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
390
+ }
391
+ function formatDuration2(ms) {
392
+ if (ms < 1e3) return `${ms}ms`;
393
+ return `${(ms / 1e3).toFixed(1)}s`;
394
+ }
395
+ function riskLevel(summary) {
396
+ if (summary.critical > 0) return { label: "CRITICAL", color: "#ef4444" };
397
+ if (summary.high > 0) return { label: "HIGH", color: "#f97316" };
398
+ if (summary.medium > 0) return { label: "MEDIUM", color: "#eab308" };
399
+ if (summary.low > 0) return { label: "LOW", color: "#3b82f6" };
400
+ return { label: "CLEAN", color: "#22c55e" };
401
+ }
402
+ function renderFinding(finding, index) {
403
+ const sev = SEVERITY_COLORS[finding.severity];
404
+ const confidence = finding.confidence != null ? `${Math.round(finding.confidence * 100)}%` : null;
405
+ const cvss = finding.cvssScore != null ? finding.cvssScore.toFixed(1) : null;
406
+ const rank = finding.findingRank != null ? `Rank: ${finding.findingRank}` : null;
407
+ const dedupe = finding.semanticDedupe ? `Canonical: ${escapeHtml(finding.semanticDedupe.canonicalId.slice(0, 12))}\u2026` : null;
408
+ return `
409
+ <div class="finding-card">
410
+ <div class="finding-header">
411
+ <span class="severity-badge" style="background:${sev.bg};color:${sev.text}">${finding.severity.toUpperCase()}</span>
412
+ <span class="finding-title">${escapeHtml(finding.title)}</span>
413
+ </div>
414
+ <div class="finding-meta">
415
+ <span class="meta-tag">Category: ${formatCategory2(finding.category)}</span>
416
+ ${finding.status === "confirmed" ? '<span class="meta-tag confirmed">Confirmed</span>' : `<span class="meta-tag">${escapeHtml(finding.status)}</span>`}
417
+ ${confidence ? `<span class="meta-tag">Confidence: ${confidence}</span>` : ""}
418
+ ${cvss ? `<span class="meta-tag">CVSS: ${cvss}</span>` : ""}
419
+ ${rank ? `<span class="meta-tag">${rank}</span>` : ""}
420
+ ${dedupe ? `<span class="meta-tag">${dedupe}</span>` : ""}
421
+ </div>
422
+ <p class="finding-desc">${escapeHtml(finding.description)}</p>
423
+ ${finding.evidence.analysis ? `<div class="evidence-analysis"><strong>Analysis:</strong> ${escapeHtml(finding.evidence.analysis)}</div>` : ""}
424
+ <details class="evidence-details">
425
+ <summary>Request / Response</summary>
426
+ <div class="evidence-block">
427
+ <div class="evidence-label">Request</div>
428
+ <pre><code>${escapeHtml(finding.evidence.request.slice(0, 2e3))}</code></pre>
429
+ </div>
430
+ <div class="evidence-block">
431
+ <div class="evidence-label">Response</div>
432
+ <pre><code>${escapeHtml(finding.evidence.response.slice(0, 2e3))}</code></pre>
433
+ </div>
434
+ </details>
435
+ </div>`;
436
+ }
437
+ function renderSeverityBar(summary) {
438
+ const total = summary.critical + summary.high + summary.medium + summary.low + summary.info;
439
+ if (total === 0) return "";
440
+ const segments = SEVERITY_ORDER.filter((s) => summary[s] > 0).map((s) => {
441
+ const pct = summary[s] / total * 100;
442
+ return `<div class="bar-segment" style="width:${pct}%;background:${SEVERITY_COLORS[s].accent}" title="${summary[s]} ${s}"></div>`;
443
+ }).join("");
444
+ return `<div class="severity-bar">${segments}</div>`;
445
+ }
446
+ function formatHtml(report) {
447
+ const risk = riskLevel(report.summary);
448
+ const sorted = [...report.findings].sort(
449
+ (a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity)
450
+ );
451
+ const findingsHtml = sorted.length > 0 ? sorted.map((f, i) => renderFinding(f, i)).join("\n") : `<div class="no-findings">${report.warnings.length > 0 || report.executionSuccessful === false ? "No vulnerabilities confirmed. Scan finished with warnings." : report.reviewChecks?.length ? "No security vulnerabilities found. See advisory review-check results below." : "No vulnerabilities found. Target passed all tests."}</div>`;
452
+ const warningsHtml = report.warnings.length > 0 ? `<div class="section">
453
+ <h2 class="section-title warning-title">Warnings</h2>
454
+ ${report.warnings.map((w) => `<div class="warning-item"><span class="warning-stage">${escapeHtml(w.stage)}</span> ${escapeHtml(w.message)}</div>`).join("\n")}
455
+ </div>` : "";
456
+ const checksHtml = report.reviewChecks?.length ? `<div class="section"><h2 class="section-title">Review Checks (Advisory)</h2>
457
+ ${report.reviewChecks.map((check) => `<div class="warning-item">
458
+ <h3>${escapeHtml(check.status.toUpperCase())}: ${escapeHtml(check.name)}</h3>
459
+ <p><strong>ID:</strong> ${escapeHtml(check.id)}</p>
460
+ <p style="white-space:pre-wrap"><strong>Reason:</strong> ${escapeHtml(check.reason)}</p>
461
+ ${check.fix ? `<p style="white-space:pre-wrap"><strong>Suggested fix:</strong> ${escapeHtml(check.fix)}</p>` : ""}
462
+ </div>`).join("\n")}</div>` : "";
463
+ const sevCountsHtml = SEVERITY_ORDER.map((s) => {
464
+ const count = report.summary[s];
465
+ return `<div class="sev-count"><span class="sev-dot" style="background:${SEVERITY_COLORS[s].accent}"></span><span class="sev-label">${s}</span><span class="sev-num">${count}</span></div>`;
466
+ }).join("");
467
+ return `<!DOCTYPE html>
468
+ <html lang="en">
469
+ <head>
470
+ <meta charset="UTF-8">
471
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
472
+ <title>0 Report \u2014 ${escapeHtml(report.target)}</title>
473
+ <style>
474
+ * { margin: 0; padding: 0; box-sizing: border-box; }
475
+ body {
476
+ background: #09090b;
477
+ color: #d4d4d8;
478
+ font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', monospace;
479
+ font-size: 14px;
480
+ line-height: 1.6;
481
+ padding: 2rem;
482
+ max-width: 960px;
483
+ margin: 0 auto;
484
+ }
485
+ a { color: #f87171; text-decoration: none; }
486
+ a:hover { text-decoration: underline; }
487
+
488
+ /* Header */
489
+ .header {
490
+ border-bottom: 1px solid #27272a;
491
+ padding-bottom: 1.5rem;
492
+ margin-bottom: 2rem;
493
+ }
494
+ .header-top {
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: space-between;
498
+ flex-wrap: wrap;
499
+ gap: 1rem;
500
+ }
501
+ .brand {
502
+ color: #f87171;
503
+ font-size: 1.6rem;
504
+ font-weight: bold;
505
+ letter-spacing: 0.15em;
506
+ }
507
+ .brand-sub { color: #52525b; font-size: 0.8rem; }
508
+ .risk-badge {
509
+ font-size: 0.85rem;
510
+ font-weight: bold;
511
+ padding: 0.3rem 1rem;
512
+ border-radius: 4px;
513
+ letter-spacing: 0.1em;
514
+ }
515
+ .meta-grid {
516
+ display: grid;
517
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
518
+ gap: 0.5rem;
519
+ margin-top: 1rem;
520
+ font-size: 0.85rem;
521
+ color: #71717a;
522
+ }
523
+ .meta-grid span { display: block; }
524
+ .meta-grid strong { color: #a1a1aa; }
525
+
526
+ /* Summary */
527
+ .summary-section {
528
+ display: grid;
529
+ grid-template-columns: 1fr 1fr;
530
+ gap: 1.5rem;
531
+ margin-bottom: 2rem;
532
+ padding-bottom: 1.5rem;
533
+ border-bottom: 1px solid #27272a;
534
+ }
535
+ @media (max-width: 600px) {
536
+ .summary-section { grid-template-columns: 1fr; }
537
+ }
538
+ .stat-box {
539
+ background: #18181b;
540
+ border: 1px solid #27272a;
541
+ border-radius: 6px;
542
+ padding: 1rem;
543
+ }
544
+ .stat-box h3 {
545
+ color: #71717a;
546
+ font-size: 0.75rem;
547
+ letter-spacing: 0.1em;
548
+ text-transform: uppercase;
549
+ margin-bottom: 0.5rem;
550
+ }
551
+ .stat-box .big-num {
552
+ font-size: 2rem;
553
+ font-weight: bold;
554
+ color: #fafafa;
555
+ }
556
+ .sev-counts {
557
+ display: flex;
558
+ flex-wrap: wrap;
559
+ gap: 0.75rem;
560
+ margin-top: 0.5rem;
561
+ }
562
+ .sev-count {
563
+ display: flex;
564
+ align-items: center;
565
+ gap: 0.35rem;
566
+ font-size: 0.8rem;
567
+ }
568
+ .sev-dot {
569
+ width: 8px;
570
+ height: 8px;
571
+ border-radius: 50%;
572
+ display: inline-block;
573
+ }
574
+ .sev-label { color: #71717a; }
575
+ .sev-num { color: #d4d4d8; font-weight: bold; }
576
+
577
+ .severity-bar {
578
+ display: flex;
579
+ height: 6px;
580
+ border-radius: 3px;
581
+ overflow: hidden;
582
+ margin-top: 0.75rem;
583
+ }
584
+ .bar-segment { min-width: 4px; }
585
+
586
+ /* Findings */
587
+ .section { margin-bottom: 2rem; }
588
+ .section-title {
589
+ color: #f87171;
590
+ font-size: 0.9rem;
591
+ letter-spacing: 0.1em;
592
+ text-transform: uppercase;
593
+ margin-bottom: 1rem;
594
+ }
595
+ .warning-title { color: #facc15; }
596
+
597
+ .finding-card {
598
+ background: #18181b;
599
+ border: 1px solid #27272a;
600
+ border-radius: 6px;
601
+ padding: 1rem;
602
+ margin-bottom: 0.75rem;
603
+ }
604
+ .finding-header {
605
+ display: flex;
606
+ align-items: center;
607
+ gap: 0.75rem;
608
+ margin-bottom: 0.5rem;
609
+ }
610
+ .severity-badge {
611
+ font-size: 0.7rem;
612
+ font-weight: bold;
613
+ padding: 0.15rem 0.5rem;
614
+ border-radius: 3px;
615
+ letter-spacing: 0.05em;
616
+ flex-shrink: 0;
617
+ }
618
+ .finding-title {
619
+ color: #fafafa;
620
+ font-weight: bold;
621
+ font-size: 0.95rem;
622
+ }
623
+ .finding-meta {
624
+ display: flex;
625
+ flex-wrap: wrap;
626
+ gap: 0.5rem;
627
+ margin-bottom: 0.5rem;
628
+ }
629
+ .meta-tag {
630
+ font-size: 0.75rem;
631
+ color: #71717a;
632
+ background: #27272a;
633
+ padding: 0.1rem 0.5rem;
634
+ border-radius: 3px;
635
+ }
636
+ .meta-tag.confirmed { color: #4ade80; background: #052e16; }
637
+ .finding-desc {
638
+ color: #a1a1aa;
639
+ font-size: 0.85rem;
640
+ margin-bottom: 0.5rem;
641
+ }
642
+ .evidence-analysis {
643
+ color: #a1a1aa;
644
+ font-size: 0.85rem;
645
+ font-style: italic;
646
+ padding: 0.5rem;
647
+ background: #1c1c1e;
648
+ border-left: 3px solid #3f3f46;
649
+ border-radius: 2px;
650
+ margin-bottom: 0.5rem;
651
+ }
652
+ .evidence-details {
653
+ margin-top: 0.5rem;
654
+ }
655
+ .evidence-details summary {
656
+ cursor: pointer;
657
+ color: #71717a;
658
+ font-size: 0.8rem;
659
+ padding: 0.3rem 0;
660
+ }
661
+ .evidence-details summary:hover { color: #a1a1aa; }
662
+ .evidence-block {
663
+ margin-top: 0.5rem;
664
+ }
665
+ .evidence-label {
666
+ font-size: 0.75rem;
667
+ color: #71717a;
668
+ text-transform: uppercase;
669
+ letter-spacing: 0.05em;
670
+ margin-bottom: 0.25rem;
671
+ }
672
+ .evidence-details pre {
673
+ background: #0a0a0a;
674
+ border: 1px solid #27272a;
675
+ border-radius: 4px;
676
+ padding: 0.75rem;
677
+ overflow-x: auto;
678
+ font-size: 0.8rem;
679
+ line-height: 1.5;
680
+ color: #a1a1aa;
681
+ white-space: pre-wrap;
682
+ word-break: break-word;
683
+ }
684
+
685
+ .no-findings {
686
+ background: #052e16;
687
+ color: #4ade80;
688
+ padding: 1rem;
689
+ border-radius: 6px;
690
+ border: 1px solid #166534;
691
+ font-size: 0.9rem;
692
+ }
693
+
694
+ .warning-item {
695
+ padding: 0.4rem 0.5rem;
696
+ margin-bottom: 0.25rem;
697
+ background: #1c1307;
698
+ border-left: 3px solid #facc15;
699
+ border-radius: 2px;
700
+ font-size: 0.85rem;
701
+ }
702
+ .warning-stage {
703
+ color: #facc15;
704
+ font-weight: bold;
705
+ margin-right: 0.5rem;
706
+ }
707
+
708
+ /* Timeline */
709
+ .timeline {
710
+ font-size: 0.8rem;
711
+ color: #71717a;
712
+ }
713
+ .timeline-row {
714
+ display: flex;
715
+ gap: 0.5rem;
716
+ padding: 0.2rem 0;
717
+ }
718
+ .timeline-label { color: #52525b; min-width: 80px; }
719
+ .timeline-value { color: #a1a1aa; }
720
+
721
+ /* Footer */
722
+ .footer {
723
+ margin-top: 2rem;
724
+ padding-top: 1rem;
725
+ border-top: 1px solid #27272a;
726
+ color: #3f3f46;
727
+ font-size: 0.75rem;
728
+ text-align: center;
729
+ }
730
+ </style>
731
+ </head>
732
+ <body>
733
+ <div class="header">
734
+ <div class="header-top">
735
+ <div>
736
+ <div class="brand">ZERO</div>
737
+ <div class="brand-sub">Security Scan Report</div>
738
+ </div>
739
+ <div class="risk-badge" style="background:${risk.color};color:#fff">${risk.label} RISK</div>
740
+ </div>
741
+ <div class="meta-grid">
742
+ <span><strong>Target:</strong> ${escapeHtml(report.target)}</span>
743
+ <span><strong>Depth:</strong> ${report.scanDepth}</span>
744
+ <span><strong>Duration:</strong> ${formatDuration2(report.durationMs)}</span>
745
+ <span><strong>Started:</strong> ${escapeHtml(report.startedAt)}</span>
746
+ </div>
747
+ </div>
748
+
749
+ <div class="summary-section">
750
+ <div class="stat-box">
751
+ <h3>Findings</h3>
752
+ <div class="big-num">${report.summary.totalFindings}</div>
753
+ <div class="sev-counts">
754
+ ${sevCountsHtml}
755
+ </div>
756
+ ${renderSeverityBar(report.summary)}
757
+ </div>
758
+ <div class="stat-box">
759
+ <h3>Attacks Tested</h3>
760
+ <div class="big-num">${report.summary.totalAttacks}</div>
761
+ <div class="timeline">
762
+ <div class="timeline-row"><span class="timeline-label">Started</span><span class="timeline-value">${escapeHtml(report.startedAt)}</span></div>
763
+ <div class="timeline-row"><span class="timeline-label">Completed</span><span class="timeline-value">${escapeHtml(report.completedAt)}</span></div>
764
+ <div class="timeline-row"><span class="timeline-label">Duration</span><span class="timeline-value">${formatDuration2(report.durationMs)}</span></div>
765
+ </div>
766
+ </div>
767
+ </div>
768
+
769
+ ${warningsHtml}
770
+
771
+ <div class="section">
772
+ <h2 class="section-title">Findings</h2>
773
+ ${findingsHtml}
774
+ </div>
775
+
776
+ ${checksHtml}
777
+
778
+ <div class="footer">
779
+ Generated by 0 &middot; ${escapeHtml((/* @__PURE__ */ new Date()).toISOString())}
780
+ </div>
781
+ </body>
782
+ </html>`;
783
+ }
784
+
785
+ // packages/cli/src/formatters/sarif.ts
786
+ function severityToLevel(severity) {
787
+ switch (severity) {
788
+ case "critical":
789
+ case "high":
790
+ return "error";
791
+ case "medium":
792
+ return "warning";
793
+ case "low":
794
+ case "info":
795
+ return "note";
796
+ }
797
+ }
798
+ function stableFingerprint(finding, target) {
799
+ return [
800
+ finding.fingerprint,
801
+ finding.templateId,
802
+ finding.category,
803
+ finding.title,
804
+ target
805
+ ].filter(Boolean).join("|");
806
+ }
807
+ function actionSummary(step) {
808
+ switch (step.action.type) {
809
+ case "shell":
810
+ return { type: "shell", command: step.action.cmd, cwd: step.action.cwd };
811
+ case "http":
812
+ return {
813
+ type: "http",
814
+ method: step.action.method,
815
+ url: step.action.url,
816
+ headers: step.action.headers
817
+ };
818
+ case "docker":
819
+ return { type: "docker", image: step.action.image, args: step.action.args };
820
+ case "note":
821
+ return { type: "note" };
822
+ }
823
+ }
824
+ function pocStepsToCodeFlows(pocSteps, target) {
825
+ if (!pocSteps || pocSteps.length === 0) return void 0;
826
+ return [
827
+ {
828
+ threadFlows: [
829
+ {
830
+ locations: pocSteps.map((step) => ({
831
+ location: {
832
+ physicalLocation: {
833
+ artifactLocation: { uri: target }
834
+ },
835
+ message: { text: `${step.kind}: ${step.summary}` },
836
+ properties: {
837
+ stepId: step.id,
838
+ kind: step.kind,
839
+ action: actionSummary(step),
840
+ expect: step.expect
841
+ }
842
+ }
843
+ }))
844
+ }
845
+ ]
846
+ }
847
+ ];
848
+ }
849
+ function findingToResult(finding, target) {
850
+ const result = {
851
+ ruleId: finding.templateId,
852
+ level: severityToLevel(finding.severity),
853
+ message: { text: finding.description },
854
+ locations: [
855
+ {
856
+ physicalLocation: {
857
+ artifactLocation: { uri: target }
858
+ }
859
+ }
860
+ ],
861
+ partialFingerprints: {
862
+ primary: stableFingerprint(finding, target)
863
+ }
864
+ };
865
+ const codeFlows = pocStepsToCodeFlows(finding.pocSteps, target);
866
+ if (codeFlows) result.codeFlows = codeFlows;
867
+ result.properties = {
868
+ findingId: finding.id,
869
+ category: finding.category,
870
+ severity: finding.severity,
871
+ status: finding.status,
872
+ evidence: finding.evidence
873
+ };
874
+ if (finding.cvssScore !== void 0) result.properties["cvssScore"] = finding.cvssScore;
875
+ if (finding.cvssVector) result.properties["cvssVector"] = finding.cvssVector;
876
+ if (finding.confidence !== void 0) result.properties["confidence"] = finding.confidence;
877
+ if (finding.publishability) result.properties["publishability"] = finding.publishability;
878
+ if (finding.noveltyVerdict) result.properties["noveltyVerdict"] = finding.noveltyVerdict;
879
+ if (finding.dedupRefs) result.properties["dedupRefs"] = finding.dedupRefs;
880
+ if (finding.advisoryMatches) result.properties["advisoryMatches"] = finding.advisoryMatches;
881
+ if (finding.verification_result) result.properties["verificationResult"] = finding.verification_result;
882
+ if (finding.inlineValidation) result.properties["inlineValidation"] = finding.inlineValidation;
883
+ if (finding.supplyChain) result.properties["supplyChain"] = finding.supplyChain;
884
+ if (finding.kernelExploit) result.properties["kernelExploit"] = finding.kernelExploit;
885
+ if (finding.semanticDedupe) result.properties["semanticDedupe"] = finding.semanticDedupe;
886
+ if (finding.findingRank !== void 0) result.properties["findingRank"] = finding.findingRank;
887
+ return result;
888
+ }
889
+ function findingToRule(finding) {
890
+ return {
891
+ id: finding.templateId,
892
+ name: finding.title,
893
+ shortDescription: { text: finding.title },
894
+ defaultConfiguration: { level: severityToLevel(finding.severity) },
895
+ properties: {
896
+ category: finding.category,
897
+ severity: finding.severity
898
+ }
899
+ };
900
+ }
901
+ function formatSarif(report) {
902
+ const rulesMap = /* @__PURE__ */ new Map();
903
+ for (const finding of report.findings) {
904
+ if (!rulesMap.has(finding.templateId)) {
905
+ rulesMap.set(finding.templateId, findingToRule(finding));
906
+ }
907
+ }
908
+ for (const check of report.reviewChecks ?? []) {
909
+ rulesMap.set(`review-check/${check.id}`, {
910
+ id: `review-check/${check.id}`,
911
+ name: check.name,
912
+ shortDescription: { text: check.name },
913
+ defaultConfiguration: { level: "note" },
914
+ properties: { advisory: true }
915
+ });
916
+ }
917
+ const sarif = {
918
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
919
+ version: "2.1.0",
920
+ runs: [
921
+ {
922
+ tool: {
923
+ driver: {
924
+ name: "0",
925
+ version: VERSION,
926
+ informationUri: "https://github.com/0sec-labs/0",
927
+ rules: Array.from(rulesMap.values())
928
+ }
929
+ },
930
+ results: [
931
+ ...report.findings.map((f) => findingToResult(f, report.target)),
932
+ ...(report.reviewChecks ?? []).map((check) => ({
933
+ ruleId: `review-check/${check.id}`,
934
+ kind: check.status === "pass" ? "pass" : check.status === "issue" ? "fail" : "open",
935
+ level: "note",
936
+ message: { text: `${check.name}: ${check.reason}${check.fix ? `
937
+ Suggested fix: ${check.fix}` : ""}` },
938
+ properties: { advisory: true, reviewCheck: check }
939
+ }))
940
+ ],
941
+ invocations: [
942
+ {
943
+ executionSuccessful: report.executionSuccessful !== false,
944
+ startTimeUtc: report.startedAt,
945
+ endTimeUtc: report.completedAt
946
+ }
947
+ ]
948
+ }
949
+ ]
950
+ };
951
+ return JSON.stringify(sarif, null, 2);
952
+ }
953
+
954
+ // packages/cli/src/formatters/pdf.ts
955
+ import { createRequire } from "node:module";
956
+ import { createWriteStream } from "node:fs";
957
+ var COLORS = {
958
+ primary: "#1a1a2e",
959
+ accent: "#e94560",
960
+ background: "#f8f9fa",
961
+ text: "#2d2d2d",
962
+ textLight: "#6c757d",
963
+ white: "#ffffff",
964
+ severity: {
965
+ critical: "#dc2626",
966
+ high: "#ea580c",
967
+ medium: "#ca8a04",
968
+ low: "#2563eb",
969
+ info: "#6b7280"
970
+ }
971
+ };
972
+ var PAGE_MARGIN = 50;
973
+ var CONTENT_WIDTH = 612 - PAGE_MARGIN * 2;
974
+ function severityLabel(s) {
975
+ return s.charAt(0).toUpperCase() + s.slice(1);
976
+ }
977
+ function formatDate(iso) {
978
+ return new Date(iso).toLocaleDateString("en-US", {
979
+ year: "numeric",
980
+ month: "long",
981
+ day: "numeric",
982
+ hour: "2-digit",
983
+ minute: "2-digit"
984
+ });
985
+ }
986
+ function formatDuration3(ms) {
987
+ if (ms < 1e3) return `${ms}ms`;
988
+ const seconds = Math.floor(ms / 1e3);
989
+ if (seconds < 60) return `${seconds}s`;
990
+ const minutes = Math.floor(seconds / 60);
991
+ const remainingSec = seconds % 60;
992
+ return `${minutes}m ${remainingSec}s`;
993
+ }
994
+ function riskScore(summary) {
995
+ const score = summary.critical * 40 + summary.high * 20 + summary.medium * 10 + summary.low * 3 + summary.info * 0;
996
+ return Math.min(100, score);
997
+ }
998
+ function riskRating(score) {
999
+ if (score >= 80) return "Critical";
1000
+ if (score >= 60) return "High";
1001
+ if (score >= 30) return "Medium";
1002
+ if (score >= 10) return "Low";
1003
+ return "Informational";
1004
+ }
1005
+ function truncate(text, maxLen) {
1006
+ if (text.length <= maxLen) return text;
1007
+ return text.slice(0, maxLen - 3) + "...";
1008
+ }
1009
+ async function generatePdfReport(report, outputPath) {
1010
+ const require_ = createRequire(import.meta.url);
1011
+ const PDFDocument = require_("pdfkit");
1012
+ return new Promise((resolve, reject) => {
1013
+ const doc = new PDFDocument({
1014
+ size: "LETTER",
1015
+ margins: { top: PAGE_MARGIN, bottom: PAGE_MARGIN, left: PAGE_MARGIN, right: PAGE_MARGIN },
1016
+ info: {
1017
+ Title: `0 Pentest Report - ${report.target}`,
1018
+ Author: "0",
1019
+ Subject: "Automated Penetration Test Report",
1020
+ CreationDate: /* @__PURE__ */ new Date()
1021
+ },
1022
+ bufferPages: true
1023
+ });
1024
+ const stream = createWriteStream(outputPath);
1025
+ stream.on("error", reject);
1026
+ stream.on("finish", resolve);
1027
+ doc.pipe(stream);
1028
+ renderCoverPage(doc, report);
1029
+ renderExecutiveSummary(doc, report);
1030
+ renderFindingsTable(doc, report);
1031
+ renderFindingDetails(doc, report);
1032
+ renderReviewChecks(doc, report);
1033
+ renderMethodology(doc, report);
1034
+ renderFooters(doc);
1035
+ doc.end();
1036
+ });
1037
+ }
1038
+ function renderCoverPage(doc, report) {
1039
+ doc.rect(0, 0, 612, 792).fill(COLORS.primary);
1040
+ doc.fontSize(48).font("Helvetica-Bold").fillColor(COLORS.accent).text("0", PAGE_MARGIN, 200, { align: "center" });
1041
+ doc.fontSize(14).font("Helvetica").fillColor(COLORS.white).text("Automated Penetration Test Report", PAGE_MARGIN, 260, { align: "center" });
1042
+ doc.moveTo(200, 300).lineTo(412, 300).strokeColor(COLORS.accent).lineWidth(2).stroke();
1043
+ doc.fontSize(18).font("Helvetica-Bold").fillColor(COLORS.white).text(report.target, PAGE_MARGIN, 330, { align: "center" });
1044
+ doc.fontSize(12).font("Helvetica").fillColor(COLORS.textLight).text(`Scan Depth: ${report.scanDepth}`, PAGE_MARGIN, 370, { align: "center" }).text(`Date: ${formatDate(report.startedAt)}`, PAGE_MARGIN, 390, { align: "center" }).text(`Duration: ${formatDuration3(report.durationMs)}`, PAGE_MARGIN, 410, { align: "center" });
1045
+ const score = riskScore(report.summary);
1046
+ const rating = riskRating(score);
1047
+ doc.fontSize(14).font("Helvetica-Bold").fillColor(COLORS.accent).text(`Risk Score: ${score}/100 (${rating})`, PAGE_MARGIN, 460, { align: "center" });
1048
+ doc.fontSize(9).font("Helvetica").fillColor(COLORS.textLight).text("CONFIDENTIAL - For authorized recipients only", PAGE_MARGIN, 700, { align: "center" });
1049
+ }
1050
+ function renderExecutiveSummary(doc, report) {
1051
+ doc.addPage();
1052
+ sectionTitle(doc, "Executive Summary");
1053
+ const score = riskScore(report.summary);
1054
+ const rating = riskRating(score);
1055
+ const { summary } = report;
1056
+ doc.fontSize(11).font("Helvetica").fillColor(COLORS.text).text(
1057
+ `An automated penetration test was conducted against ${report.target} on ${formatDate(report.startedAt)}. The scan ran for ${formatDuration3(report.durationMs)} at "${report.scanDepth}" depth.`,
1058
+ PAGE_MARGIN,
1059
+ doc.y + 10,
1060
+ { width: CONTENT_WIDTH }
1061
+ );
1062
+ doc.moveDown(0.5);
1063
+ doc.text(
1064
+ `The overall risk score is ${score}/100 (${rating}). A total of ${summary.totalFindings} finding(s) were identified across ${summary.totalAttacks} attack(s).`,
1065
+ { width: CONTENT_WIDTH }
1066
+ );
1067
+ doc.moveDown(1);
1068
+ subSectionTitle(doc, "Findings by Severity");
1069
+ const severities = ["critical", "high", "medium", "low", "info"];
1070
+ const sevCounts = {
1071
+ critical: summary.critical,
1072
+ high: summary.high,
1073
+ medium: summary.medium,
1074
+ low: summary.low,
1075
+ info: summary.info
1076
+ };
1077
+ for (const sev of severities) {
1078
+ const count = sevCounts[sev];
1079
+ if (count === 0) continue;
1080
+ const barWidth = Math.max(4, count / Math.max(summary.totalFindings, 1) * (CONTENT_WIDTH - 140));
1081
+ const y = doc.y;
1082
+ doc.fontSize(10).font("Helvetica-Bold").fillColor(COLORS.severity[sev]).text(severityLabel(sev), PAGE_MARGIN, y, { width: 70 });
1083
+ doc.rect(PAGE_MARGIN + 80, y + 2, barWidth, 12).fill(COLORS.severity[sev]);
1084
+ doc.fontSize(10).font("Helvetica").fillColor(COLORS.text).text(String(count), PAGE_MARGIN + 90 + barWidth, y, { width: 40 });
1085
+ doc.y = y + 22;
1086
+ }
1087
+ if (report.warnings.length > 0) {
1088
+ doc.moveDown(1);
1089
+ subSectionTitle(doc, "Warnings");
1090
+ for (const w of report.warnings) {
1091
+ doc.fontSize(9).font("Helvetica").fillColor(COLORS.textLight).text(`[${w.stage}] ${w.message}`, PAGE_MARGIN, doc.y, { width: CONTENT_WIDTH });
1092
+ doc.moveDown(0.3);
1093
+ }
1094
+ }
1095
+ }
1096
+ function renderFindingsTable(doc, report) {
1097
+ if (report.findings.length === 0) return;
1098
+ doc.addPage();
1099
+ sectionTitle(doc, "Findings Overview");
1100
+ const colWidths = { severity: 70, title: 220, category: 120, status: 80 };
1101
+ const tableX = PAGE_MARGIN;
1102
+ const headerY = doc.y + 5;
1103
+ doc.rect(tableX, headerY, CONTENT_WIDTH, 20).fill(COLORS.primary);
1104
+ doc.fontSize(9).font("Helvetica-Bold").fillColor(COLORS.white);
1105
+ doc.text("Severity", tableX + 5, headerY + 5, { width: colWidths.severity });
1106
+ doc.text("Title", tableX + colWidths.severity + 5, headerY + 5, { width: colWidths.title });
1107
+ doc.text("Category", tableX + colWidths.severity + colWidths.title + 5, headerY + 5, { width: colWidths.category });
1108
+ doc.text("Status", tableX + colWidths.severity + colWidths.title + colWidths.category + 5, headerY + 5, { width: colWidths.status });
1109
+ doc.y = headerY + 22;
1110
+ const sorted = [...report.findings].sort(
1111
+ (a, b) => severityRank(b.severity) - severityRank(a.severity)
1112
+ );
1113
+ for (let i = 0; i < sorted.length; i++) {
1114
+ const finding = sorted[i];
1115
+ const rowY = doc.y;
1116
+ if (rowY > 720) {
1117
+ doc.addPage();
1118
+ doc.y = PAGE_MARGIN;
1119
+ }
1120
+ if (i % 2 === 0) {
1121
+ doc.rect(tableX, rowY, CONTENT_WIDTH, 18).fill(COLORS.background);
1122
+ }
1123
+ doc.fontSize(8).font("Helvetica");
1124
+ doc.fillColor(COLORS.severity[finding.severity]).font("Helvetica-Bold").text(severityLabel(finding.severity), tableX + 5, rowY + 5, { width: colWidths.severity });
1125
+ doc.fillColor(COLORS.text).font("Helvetica").text(truncate(finding.title, 45), tableX + colWidths.severity + 5, rowY + 5, { width: colWidths.title });
1126
+ doc.text(finding.category, tableX + colWidths.severity + colWidths.title + 5, rowY + 5, { width: colWidths.category });
1127
+ doc.text(finding.status, tableX + colWidths.severity + colWidths.title + colWidths.category + 5, rowY + 5, { width: colWidths.status });
1128
+ doc.y = rowY + 20;
1129
+ }
1130
+ }
1131
+ function renderFindingDetails(doc, report) {
1132
+ if (report.findings.length === 0) return;
1133
+ const sorted = [...report.findings].sort(
1134
+ (a, b) => severityRank(b.severity) - severityRank(a.severity)
1135
+ );
1136
+ for (const finding of sorted) {
1137
+ doc.addPage();
1138
+ sectionTitle(doc, finding.title);
1139
+ doc.fontSize(10).font("Helvetica-Bold").fillColor(COLORS.severity[finding.severity]).text(`${severityLabel(finding.severity)} Severity`, PAGE_MARGIN, doc.y, { continued: true }).fillColor(COLORS.textLight).font("Helvetica").text(` | ${finding.category} | ${finding.status} | ID: ${finding.id}`);
1140
+ if (finding.cvssScore != null) {
1141
+ doc.fontSize(9).fillColor(COLORS.text).text(`CVSS Score: ${finding.cvssScore}${finding.cvssVector ? ` (${finding.cvssVector})` : ""}`, PAGE_MARGIN, doc.y + 2);
1142
+ }
1143
+ doc.moveDown(1);
1144
+ subSectionTitle(doc, "Description");
1145
+ doc.fontSize(10).font("Helvetica").fillColor(COLORS.text).text(finding.description, PAGE_MARGIN, doc.y, { width: CONTENT_WIDTH });
1146
+ if (finding.evidence.request) {
1147
+ doc.moveDown(1);
1148
+ subSectionTitle(doc, "Request");
1149
+ codeBlock(doc, finding.evidence.request);
1150
+ }
1151
+ if (finding.evidence.response) {
1152
+ doc.moveDown(0.5);
1153
+ subSectionTitle(doc, "Response");
1154
+ codeBlock(doc, finding.evidence.response);
1155
+ }
1156
+ if (finding.evidence.analysis) {
1157
+ doc.moveDown(0.5);
1158
+ subSectionTitle(doc, "Analysis & Remediation");
1159
+ doc.fontSize(10).font("Helvetica").fillColor(COLORS.text).text(finding.evidence.analysis, PAGE_MARGIN, doc.y, { width: CONTENT_WIDTH });
1160
+ }
1161
+ }
1162
+ }
1163
+ function renderReviewChecks(doc, report) {
1164
+ if (!report.reviewChecks?.length) return;
1165
+ doc.addPage();
1166
+ sectionTitle(doc, "Review Checks (Advisory)");
1167
+ for (const check of report.reviewChecks) {
1168
+ if (doc.y > doc.page.height - PAGE_MARGIN - 80) doc.addPage();
1169
+ subSectionTitle(doc, `${check.status.toUpperCase()}: ${check.name}`);
1170
+ doc.fontSize(10).font("Helvetica").fillColor(COLORS.text).text(`ID: ${check.id}`, PAGE_MARGIN, doc.y, { width: CONTENT_WIDTH }).text(`Reason: ${check.reason}`, { width: CONTENT_WIDTH });
1171
+ if (check.fix) doc.text(`Suggested fix: ${check.fix}`, { width: CONTENT_WIDTH });
1172
+ doc.moveDown(1);
1173
+ }
1174
+ }
1175
+ function renderMethodology(doc, report) {
1176
+ doc.addPage();
1177
+ sectionTitle(doc, "Methodology");
1178
+ doc.fontSize(10).font("Helvetica").fillColor(COLORS.text).text(
1179
+ "This report was generated by 0, a fully autonomous agentic pentesting framework. The tool uses AI-driven attack generation and multi-agent consensus verification to identify security vulnerabilities.",
1180
+ PAGE_MARGIN,
1181
+ doc.y + 10,
1182
+ { width: CONTENT_WIDTH }
1183
+ );
1184
+ doc.moveDown(1);
1185
+ subSectionTitle(doc, "Scan Parameters");
1186
+ const params = [
1187
+ ["Target", report.target],
1188
+ ["Scan Depth", report.scanDepth],
1189
+ ["Started", formatDate(report.startedAt)],
1190
+ ["Completed", formatDate(report.completedAt)],
1191
+ ["Duration", formatDuration3(report.durationMs)],
1192
+ ["Total Attacks", String(report.summary.totalAttacks)],
1193
+ ["Total Findings", String(report.summary.totalFindings)]
1194
+ ];
1195
+ for (const [label, value] of params) {
1196
+ doc.fontSize(10).font("Helvetica-Bold").fillColor(COLORS.text).text(`${label}: `, PAGE_MARGIN, doc.y, { continued: true }).font("Helvetica").text(value);
1197
+ doc.moveDown(0.2);
1198
+ }
1199
+ doc.moveDown(1);
1200
+ subSectionTitle(doc, "Severity Definitions");
1201
+ const sevDefs = [
1202
+ ["Critical", "Immediate exploitation possible with severe business impact. Requires urgent remediation."],
1203
+ ["High", "Easily exploitable vulnerability with significant impact. Should be fixed in the next release cycle."],
1204
+ ["Medium", "Exploitable with some prerequisites. Should be addressed in planned maintenance."],
1205
+ ["Low", "Minor issue with limited impact. Fix as part of regular development."],
1206
+ ["Info", "Informational finding or best-practice recommendation. No immediate risk."]
1207
+ ];
1208
+ for (const [sev, desc] of sevDefs) {
1209
+ doc.fontSize(10).font("Helvetica-Bold").fillColor(COLORS.text).text(`${sev}: `, PAGE_MARGIN, doc.y, { continued: true }).font("Helvetica").fillColor(COLORS.textLight).text(desc, { width: CONTENT_WIDTH - 60 });
1210
+ doc.moveDown(0.3);
1211
+ }
1212
+ doc.moveDown(1);
1213
+ subSectionTitle(doc, "Disclaimer");
1214
+ doc.fontSize(9).font("Helvetica").fillColor(COLORS.textLight).text(
1215
+ "This automated scan provides a point-in-time assessment. It may not identify all vulnerabilities. Results should be validated by security professionals. The findings do not guarantee the absence of other security issues. This report is confidential and intended for authorized recipients only.",
1216
+ PAGE_MARGIN,
1217
+ doc.y,
1218
+ { width: CONTENT_WIDTH }
1219
+ );
1220
+ }
1221
+ function sectionTitle(doc, title) {
1222
+ doc.fontSize(20).font("Helvetica-Bold").fillColor(COLORS.primary).text(title, PAGE_MARGIN, PAGE_MARGIN);
1223
+ doc.moveTo(PAGE_MARGIN, doc.y + 4).lineTo(PAGE_MARGIN + CONTENT_WIDTH, doc.y + 4).strokeColor(COLORS.accent).lineWidth(1.5).stroke();
1224
+ doc.y += 12;
1225
+ }
1226
+ function subSectionTitle(doc, title) {
1227
+ doc.fontSize(12).font("Helvetica-Bold").fillColor(COLORS.primary).text(title, PAGE_MARGIN, doc.y);
1228
+ doc.moveDown(0.3);
1229
+ }
1230
+ function codeBlock(doc, text) {
1231
+ const maxChars = 2e3;
1232
+ const displayText = truncate(text, maxChars);
1233
+ doc.font("Courier").fontSize(8);
1234
+ const textHeight = doc.heightOfString(displayText, {
1235
+ width: CONTENT_WIDTH - 20
1236
+ });
1237
+ const blockHeight = Math.min(textHeight + 16, 300);
1238
+ const y = doc.y;
1239
+ if (y + blockHeight > 740) {
1240
+ doc.addPage();
1241
+ }
1242
+ doc.rect(PAGE_MARGIN, doc.y, CONTENT_WIDTH, blockHeight).fill("#f1f3f5");
1243
+ doc.fontSize(8).font("Courier").fillColor(COLORS.text).text(displayText, PAGE_MARGIN + 10, doc.y - blockHeight + 8, {
1244
+ width: CONTENT_WIDTH - 20,
1245
+ height: blockHeight - 16,
1246
+ ellipsis: true
1247
+ });
1248
+ doc.y = doc.y + 4;
1249
+ }
1250
+ function renderFooters(doc) {
1251
+ const pageCount = doc.bufferedPageRange().count;
1252
+ for (let i = 0; i < pageCount; i++) {
1253
+ doc.switchToPage(i);
1254
+ if (i === 0) continue;
1255
+ doc.fontSize(8).font("Helvetica").fillColor(COLORS.textLight).text(
1256
+ `0 Pentest Report | Page ${i + 1} of ${pageCount} | CONFIDENTIAL`,
1257
+ PAGE_MARGIN,
1258
+ 760,
1259
+ { width: CONTENT_WIDTH, align: "center" }
1260
+ );
1261
+ }
1262
+ }
1263
+
1264
+ // packages/cli/src/formatters/replay.ts
1265
+ function sleep(ms) {
1266
+ return new Promise((resolve) => setTimeout(resolve, ms));
1267
+ }
1268
+ function stripAnsi(s) {
1269
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
1270
+ }
1271
+ var BOX_WIDTH = 55;
1272
+ function boxTop(label) {
1273
+ const inner = ` ${label} `;
1274
+ const remaining = BOX_WIDTH - inner.length - 1;
1275
+ return source_default.dim("\u250C\u2500 ") + source_default.bold.white(label) + source_default.dim(" " + "\u2500".repeat(Math.max(0, remaining)) + "\u2510");
1276
+ }
1277
+ function boxRow(content) {
1278
+ const visible = stripAnsi(content);
1279
+ const pad = Math.max(0, BOX_WIDTH - visible.length - 2);
1280
+ return source_default.dim("\u2502") + " " + content + " ".repeat(pad) + source_default.dim("\u2502");
1281
+ }
1282
+ function boxBottom(withArrow) {
1283
+ if (withArrow) {
1284
+ const half = Math.floor((BOX_WIDTH - 1) / 2);
1285
+ const rest = BOX_WIDTH - half - 1;
1286
+ return source_default.dim("\u2514" + "\u2500".repeat(half) + "\u2534" + "\u2500".repeat(rest) + "\u2518");
1287
+ }
1288
+ return source_default.dim("\u2514" + "\u2500".repeat(BOX_WIDTH) + "\u2518");
1289
+ }
1290
+ function connector() {
1291
+ const half = Math.floor((BOX_WIDTH + 1) / 2);
1292
+ return " ".repeat(half) + source_default.dim("\u25BC");
1293
+ }
1294
+ function outcomeLabel(outcome) {
1295
+ switch (outcome) {
1296
+ case "vulnerable":
1297
+ return source_default.red.bold("VULNERABLE");
1298
+ case "leaked":
1299
+ return source_default.red.bold("LEAKED");
1300
+ case "bypassed":
1301
+ return source_default.red.bold("BYPASSED");
1302
+ case "safe":
1303
+ return source_default.green("SAFE");
1304
+ case "error":
1305
+ return source_default.yellow("ERROR");
1306
+ default:
1307
+ return source_default.gray(outcome.toUpperCase());
1308
+ }
1309
+ }
1310
+ function buildReplayLines(data) {
1311
+ const lines = [];
1312
+ const FAST = 50;
1313
+ const MED = 75;
1314
+ const SLOW = 100;
1315
+ const PAUSE = 200;
1316
+ lines.push({ text: boxTop("DISCOVER"), delay: PAUSE });
1317
+ if (data.targetInfo) {
1318
+ const t = data.targetInfo;
1319
+ const truncUrl = t.url.length > 30 ? t.url.slice(0, 27) + "..." : t.url;
1320
+ lines.push({
1321
+ text: boxRow(
1322
+ source_default.dim("\u25B8") + " " + source_default.white(`GET ${truncUrl}`) + source_default.dim(" \u2192 ") + source_default.white(`200`) + source_default.gray(` (${t.type} endpoint detected)`)
1323
+ ),
1324
+ delay: MED
1325
+ });
1326
+ if (t.systemPrompt) {
1327
+ lines.push({
1328
+ text: boxRow(
1329
+ source_default.dim("\u25B8") + " " + source_default.white(`System prompt extracted`) + source_default.gray(` (${t.systemPrompt.length} chars)`)
1330
+ ),
1331
+ delay: MED
1332
+ });
1333
+ }
1334
+ if (t.detectedFeatures && t.detectedFeatures.length > 0) {
1335
+ lines.push({
1336
+ text: boxRow(
1337
+ source_default.dim("\u25B8") + " " + source_default.white(`${t.detectedFeatures.length} features detected: `) + source_default.gray(t.detectedFeatures.slice(0, 3).join(", "))
1338
+ ),
1339
+ delay: MED
1340
+ });
1341
+ }
1342
+ if (t.endpoints && t.endpoints.length > 0) {
1343
+ lines.push({
1344
+ text: boxRow(
1345
+ source_default.dim("\u25B8") + " " + source_default.white(`${t.endpoints.length} endpoints found`)
1346
+ ),
1347
+ delay: MED
1348
+ });
1349
+ }
1350
+ } else {
1351
+ const truncTarget = data.target.length > 30 ? data.target.slice(0, 27) + "..." : data.target;
1352
+ lines.push({
1353
+ text: boxRow(
1354
+ source_default.dim("\u25B8") + " " + source_default.white(`GET ${truncTarget}`) + source_default.dim(" \u2192 ") + source_default.white("200") + source_default.gray(" (LLM API detected)")
1355
+ ),
1356
+ delay: MED
1357
+ });
1358
+ }
1359
+ lines.push({ text: boxBottom(true), delay: FAST });
1360
+ lines.push({ text: connector(), delay: PAUSE });
1361
+ lines.push({ text: boxTop("ATTACK"), delay: PAUSE });
1362
+ const vulnerableFindings = data.findings.filter(
1363
+ (f) => f.status !== "false-positive"
1364
+ );
1365
+ if (vulnerableFindings.length > 0) {
1366
+ for (const finding of vulnerableFindings.slice(0, 8)) {
1367
+ const truncTitle = finding.title.length > 30 ? finding.title.slice(0, 27) + "..." : finding.title;
1368
+ let outcome = "VULNERABLE";
1369
+ const cat = finding.category;
1370
+ if (cat === "system-prompt-extraction") outcome = "LEAKED";
1371
+ if (cat === "jailbreak") outcome = "BYPASSED";
1372
+ lines.push({
1373
+ text: boxRow(
1374
+ source_default.dim("\u25B8") + " " + source_default.white(truncTitle) + source_default.dim(" \u2192 ") + " " + outcomeLabel(outcome.toLowerCase())
1375
+ ),
1376
+ delay: MED
1377
+ });
1378
+ }
1379
+ if (vulnerableFindings.length > 8) {
1380
+ lines.push({
1381
+ text: boxRow(
1382
+ source_default.gray(
1383
+ ` ... and ${vulnerableFindings.length - 8} more attacks`
1384
+ )
1385
+ ),
1386
+ delay: FAST
1387
+ });
1388
+ }
1389
+ } else {
1390
+ lines.push({
1391
+ text: boxRow(
1392
+ source_default.dim("\u25B8") + " " + source_default.white(`${data.summary.totalAttacks} probes executed`) + source_default.dim(" \u2192 ") + source_default.green("ALL SAFE")
1393
+ ),
1394
+ delay: MED
1395
+ });
1396
+ }
1397
+ lines.push({ text: boxBottom(true), delay: FAST });
1398
+ lines.push({ text: connector(), delay: PAUSE });
1399
+ lines.push({ text: boxTop("VERIFY"), delay: PAUSE });
1400
+ const confirmed = data.findings.filter(
1401
+ (f) => f.status === "confirmed" || f.status === "verified" || f.status === "scored" || f.status === "reported"
1402
+ );
1403
+ const falsePositives = data.findings.filter(
1404
+ (f) => f.status === "false-positive"
1405
+ );
1406
+ if (confirmed.length > 0 || data.findings.length > 0) {
1407
+ const reproduced = confirmed.length > 0 ? confirmed.length : data.findings.length;
1408
+ lines.push({
1409
+ text: boxRow(
1410
+ source_default.green("\u2713") + " " + source_default.white(`Reproduced ${reproduced}/${reproduced} findings`)
1411
+ ),
1412
+ delay: MED
1413
+ });
1414
+ }
1415
+ if (falsePositives.length > 0) {
1416
+ lines.push({
1417
+ text: boxRow(
1418
+ source_default.red("\u2717") + " " + source_default.white(`Eliminated ${falsePositives.length} false positives`)
1419
+ ),
1420
+ delay: MED
1421
+ });
1422
+ }
1423
+ if (confirmed.length === 0 && falsePositives.length === 0 && data.findings.length === 0) {
1424
+ lines.push({
1425
+ text: boxRow(source_default.green("\u2713") + " " + source_default.white("No findings to verify")),
1426
+ delay: MED
1427
+ });
1428
+ }
1429
+ lines.push({ text: boxBottom(true), delay: FAST });
1430
+ lines.push({ text: connector(), delay: PAUSE });
1431
+ lines.push({ text: boxTop("REPORT"), delay: PAUSE });
1432
+ const totalFindings = data.summary.totalFindings;
1433
+ if (totalFindings > 0) {
1434
+ const parts = [];
1435
+ if (data.summary.critical > 0) parts.push(source_default.red.bold(`${data.summary.critical} CRITICAL`));
1436
+ if (data.summary.high > 0) parts.push(source_default.redBright(`${data.summary.high} HIGH`));
1437
+ if (data.summary.medium > 0) parts.push(source_default.yellow(`${data.summary.medium} MEDIUM`));
1438
+ if (data.summary.low > 0) parts.push(source_default.blue(`${data.summary.low} LOW`));
1439
+ if (data.summary.info > 0) parts.push(source_default.gray(`${data.summary.info} INFO`));
1440
+ lines.push({
1441
+ text: boxRow(
1442
+ source_default.white(`${totalFindings} verified findings: `) + parts.join(source_default.dim(", "))
1443
+ ),
1444
+ delay: MED
1445
+ });
1446
+ } else {
1447
+ lines.push({
1448
+ text: boxRow(source_default.green.bold("0 findings") + source_default.white(" - target appears secure")),
1449
+ delay: MED
1450
+ });
1451
+ }
1452
+ const duration = data.durationMs < 1e3 ? `${data.durationMs}ms` : `${(data.durationMs / 1e3).toFixed(1)}s`;
1453
+ lines.push({
1454
+ text: boxRow(
1455
+ source_default.white(`Completed in ${duration}`) + source_default.dim(" \u2192 ") + source_default.gray("./0-report.json")
1456
+ ),
1457
+ delay: MED
1458
+ });
1459
+ lines.push({ text: boxBottom(false), delay: FAST });
1460
+ return lines;
1461
+ }
1462
+ async function renderReplay(data) {
1463
+ const lines = buildReplayLines(data);
1464
+ process.stdout.write("\n");
1465
+ process.stdout.write(
1466
+ source_default.red.bold(" \u25C6 0") + source_default.gray(" attack replay") + "\n"
1467
+ );
1468
+ process.stdout.write(
1469
+ source_default.dim(" " + "\u2500".repeat(BOX_WIDTH + 1)) + "\n"
1470
+ );
1471
+ process.stdout.write("\n");
1472
+ await sleep(200);
1473
+ for (const line of lines) {
1474
+ await sleep(line.delay);
1475
+ process.stdout.write(" " + line.text + "\n");
1476
+ }
1477
+ process.stdout.write("\n");
1478
+ }
1479
+
1480
+ // packages/cli/src/formatters/timeline.ts
1481
+ var TIMELINE_FORMATS = ["json", "csv", "markdown"];
1482
+ function isTimelineFormat(value) {
1483
+ return TIMELINE_FORMATS.includes(value);
1484
+ }
1485
+ function formatTimeline(record, format) {
1486
+ switch (format) {
1487
+ case "json":
1488
+ return formatTimelineJson(record);
1489
+ case "csv":
1490
+ return formatTimelineCsv(record);
1491
+ case "markdown":
1492
+ return formatTimelineMarkdown(record);
1493
+ }
1494
+ }
1495
+ function formatTimelineJson(record) {
1496
+ return JSON.stringify(record, null, 2);
1497
+ }
1498
+ var CSV_COLUMNS = [
1499
+ "timestamp",
1500
+ "stage",
1501
+ "eventType",
1502
+ "agentRole",
1503
+ "findingId",
1504
+ "action",
1505
+ "attackTechniqueIds",
1506
+ "attackTactics",
1507
+ // Appended, never interleaved with the ATT&CK columns — an importer that
1508
+ // only knows the ATT&CK pair keeps working, and the two taxonomies stay
1509
+ // visibly distinct in a spreadsheet.
1510
+ "atlasTechniqueIds",
1511
+ "atlasTactics"
1512
+ ];
1513
+ function formatTimelineCsv(record) {
1514
+ const lines = [CSV_COLUMNS.join(",")];
1515
+ for (const entry of record.entries) {
1516
+ lines.push(
1517
+ [
1518
+ entry.timestamp,
1519
+ entry.stage,
1520
+ entry.eventType,
1521
+ entry.agentRole ?? "",
1522
+ entry.findingId ?? "",
1523
+ entry.action,
1524
+ entry.techniques.map((t) => t.id).join(" "),
1525
+ uniq(entry.techniques.map((t) => t.tactic)).join(" "),
1526
+ entry.atlasTechniques.map((t) => t.id).join(" "),
1527
+ uniq(entry.atlasTechniques.map((t) => t.tactic)).join(" ")
1528
+ ].map(csvCell).join(",")
1529
+ );
1530
+ }
1531
+ return lines.join("\n") + "\n";
1532
+ }
1533
+ function csvCell(value) {
1534
+ if (!/[",\r\n]/.test(value)) return value;
1535
+ return `"${value.replace(/"/g, '""')}"`;
1536
+ }
1537
+ function formatTimelineMarkdown(record) {
1538
+ const out = [];
1539
+ out.push(`# Forensic timeline \u2014 scan \`${record.scanId}\``);
1540
+ out.push("");
1541
+ if (record.target) out.push(`- **Target:** ${record.target}`);
1542
+ out.push(`- **Generated:** ${record.generatedAt}`);
1543
+ out.push(`- **Events:** ${record.eventCount} of ${record.totalEventCount} recorded`);
1544
+ if (record.filters.since) out.push(`- **Since:** ${record.filters.since}`);
1545
+ if (record.filters.until) out.push(`- **Until:** ${record.filters.until}`);
1546
+ if (record.filters.attackOnly) {
1547
+ out.push(`- **Filter:** technique-mapped events only (pipeline lifecycle events omitted)`);
1548
+ }
1549
+ out.push("");
1550
+ out.push("All timestamps are UTC (ISO-8601).");
1551
+ out.push("");
1552
+ out.push(
1553
+ "ATT&CK (Enterprise) and ATLAS (AI systems) are separate matrices; a row may carry either, both, or neither."
1554
+ );
1555
+ out.push("");
1556
+ if (record.entries.length === 0) {
1557
+ out.push("_No pipeline events recorded for this scan in the selected window._");
1558
+ out.push("");
1559
+ return out.join("\n");
1560
+ }
1561
+ out.push("| # | Timestamp (UTC) | Stage | Event | Agent | Finding | Action | ATT&CK | ATLAS |");
1562
+ out.push("| --- | --- | --- | --- | --- | --- | --- | --- | --- |");
1563
+ record.entries.forEach((entry, i) => {
1564
+ out.push(
1565
+ "| " + [
1566
+ String(i + 1),
1567
+ entry.timestamp,
1568
+ mdCell(entry.stage),
1569
+ mdCell(entry.eventType),
1570
+ mdCell(entry.agentRole ?? "\u2014"),
1571
+ mdCell(entry.findingId ?? "\u2014"),
1572
+ mdCell(entry.action),
1573
+ mdTechniques(entry.techniques),
1574
+ mdTechniques(entry.atlasTechniques)
1575
+ ].join(" | ") + " |"
1576
+ );
1577
+ });
1578
+ out.push("");
1579
+ return out.join("\n");
1580
+ }
1581
+ function mdTechniques(techniques) {
1582
+ return techniques.length > 0 ? techniques.map(mdTechnique).join(", ") : "\u2014";
1583
+ }
1584
+ function mdTechnique(technique) {
1585
+ const label = mdCell(`${technique.id} ${technique.name}`);
1586
+ return technique.url ? `[${label}](${technique.url})` : label;
1587
+ }
1588
+ function mdCell(value) {
1589
+ return value.replace(/\s+/g, " ").replace(/\|/g, "\\|").trim();
1590
+ }
1591
+ function uniq(values) {
1592
+ return [...new Set(values.filter((v) => v.length > 0))];
1593
+ }
1594
+
1595
+ // packages/cli/src/formatters/index.ts
1596
+ function formatPresentationDocument(document, format) {
1597
+ if (format === "json" && document.documentType !== "scan-report") {
1598
+ return JSON.stringify(document.report, null, 2);
1599
+ }
1600
+ const report = document.documentType === "scan-report" ? document.report : document.documentType === "audit-report" ? {
1601
+ target: `${document.report.package}@${document.report.version}`,
1602
+ scanDepth: "deep",
1603
+ startedAt: document.report.startedAt,
1604
+ completedAt: document.report.completedAt,
1605
+ durationMs: document.report.durationMs,
1606
+ summary: document.report.summary,
1607
+ findings: document.report.findings,
1608
+ warnings: []
1609
+ } : {
1610
+ target: document.report.repo,
1611
+ scanDepth: "deep",
1612
+ startedAt: document.report.startedAt,
1613
+ completedAt: document.report.completedAt,
1614
+ durationMs: document.report.durationMs,
1615
+ summary: document.report.summary,
1616
+ findings: document.report.findings,
1617
+ warnings: document.report.warnings?.map((warning) => ({ stage: "report", message: `[${warning.stage}] ${warning.message}` })) ?? [],
1618
+ executionSuccessful: document.report.researchFailed ? false : void 0,
1619
+ reviewChecks: document.report.reviewChecks
1620
+ };
1621
+ switch (format) {
1622
+ case "terminal":
1623
+ return formatTerminal(report);
1624
+ case "json":
1625
+ return formatJson(report);
1626
+ case "markdown":
1627
+ return formatMarkdown(report);
1628
+ case "html":
1629
+ return formatHtml(report);
1630
+ case "sarif":
1631
+ return formatSarif(report);
1632
+ case "pdf":
1633
+ return "[PDF output requires generatePdfReport()]";
1634
+ }
1635
+ }
1636
+ function formatReport(report, format) {
1637
+ return formatPresentationDocument(createScanReportDocument(report), format);
1638
+ }
1639
+ function formatAuditReport(report, format) {
1640
+ return formatPresentationDocument(createAuditReportDocument(report), format);
1641
+ }
1642
+ function formatReviewReport(report, format) {
1643
+ return formatPresentationDocument(createReviewReportDocument(report), format);
1644
+ }
1645
+
1646
+ export {
1647
+ renderReplay,
1648
+ getRuntimeAvailability,
1649
+ checkRuntimeAvailability,
1650
+ buildShareUrl,
1651
+ formatSarif,
1652
+ generatePdfReport,
1653
+ TIMELINE_FORMATS,
1654
+ isTimelineFormat,
1655
+ formatTimeline,
1656
+ formatReport,
1657
+ formatAuditReport,
1658
+ formatReviewReport
1659
+ };