@owasp-aghast/aghast 0.8.3 → 0.9.0-rc.1

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 (113) hide show
  1. package/config/pricing.json +39 -9
  2. package/dist/build-config.d.ts.map +1 -1
  3. package/dist/build-config.js +101 -2
  4. package/dist/build-config.js.map +1 -1
  5. package/dist/check-library.d.ts +25 -0
  6. package/dist/check-library.d.ts.map +1 -1
  7. package/dist/check-library.js +172 -1
  8. package/dist/check-library.js.map +1 -1
  9. package/dist/ci-metadata.d.ts +42 -0
  10. package/dist/ci-metadata.d.ts.map +1 -0
  11. package/dist/ci-metadata.js +131 -0
  12. package/dist/ci-metadata.js.map +1 -0
  13. package/dist/claude-code-provider.d.ts +7 -0
  14. package/dist/claude-code-provider.d.ts.map +1 -1
  15. package/dist/claude-code-provider.js +65 -1
  16. package/dist/claude-code-provider.js.map +1 -1
  17. package/dist/cli.js +4 -1
  18. package/dist/cli.js.map +1 -1
  19. package/dist/concurrency.d.ts +18 -0
  20. package/dist/concurrency.d.ts.map +1 -0
  21. package/dist/concurrency.js +39 -0
  22. package/dist/concurrency.js.map +1 -0
  23. package/dist/cost-tracker.d.ts +45 -0
  24. package/dist/cost-tracker.d.ts.map +1 -0
  25. package/dist/cost-tracker.js +56 -0
  26. package/dist/cost-tracker.js.map +1 -0
  27. package/dist/discoveries/glob-discovery.d.ts +21 -0
  28. package/dist/discoveries/glob-discovery.d.ts.map +1 -0
  29. package/dist/discoveries/glob-discovery.js +215 -0
  30. package/dist/discoveries/glob-discovery.js.map +1 -0
  31. package/dist/discoveries/script-discovery.d.ts +117 -0
  32. package/dist/discoveries/script-discovery.d.ts.map +1 -0
  33. package/dist/discoveries/script-discovery.js +593 -0
  34. package/dist/discoveries/script-discovery.js.map +1 -0
  35. package/dist/docs-url.d.ts +14 -0
  36. package/dist/docs-url.d.ts.map +1 -0
  37. package/dist/docs-url.js +17 -0
  38. package/dist/docs-url.js.map +1 -0
  39. package/dist/error-codes.d.ts +12 -1
  40. package/dist/error-codes.d.ts.map +1 -1
  41. package/dist/error-codes.js +16 -2
  42. package/dist/error-codes.js.map +1 -1
  43. package/dist/formatters/csv-formatter.d.ts +45 -0
  44. package/dist/formatters/csv-formatter.d.ts.map +1 -0
  45. package/dist/formatters/csv-formatter.js +176 -0
  46. package/dist/formatters/csv-formatter.js.map +1 -0
  47. package/dist/formatters/html-formatter.d.ts +39 -0
  48. package/dist/formatters/html-formatter.d.ts.map +1 -0
  49. package/dist/formatters/html-formatter.js +432 -0
  50. package/dist/formatters/html-formatter.js.map +1 -0
  51. package/dist/formatters/index.d.ts.map +1 -1
  52. package/dist/formatters/index.js +6 -0
  53. package/dist/formatters/index.js.map +1 -1
  54. package/dist/formatters/markdown-formatter.d.ts +61 -0
  55. package/dist/formatters/markdown-formatter.d.ts.map +1 -0
  56. package/dist/formatters/markdown-formatter.js +557 -0
  57. package/dist/formatters/markdown-formatter.js.map +1 -0
  58. package/dist/formatters/sarif-formatter.d.ts +7 -0
  59. package/dist/formatters/sarif-formatter.d.ts.map +1 -1
  60. package/dist/formatters/sarif-formatter.js +80 -0
  61. package/dist/formatters/sarif-formatter.js.map +1 -1
  62. package/dist/formatters/types.d.ts +24 -0
  63. package/dist/formatters/types.d.ts.map +1 -1
  64. package/dist/index.d.ts +4 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +365 -10
  67. package/dist/index.js.map +1 -1
  68. package/dist/issue-file-writer.d.ts +21 -0
  69. package/dist/issue-file-writer.d.ts.map +1 -0
  70. package/dist/issue-file-writer.js +307 -0
  71. package/dist/issue-file-writer.js.map +1 -0
  72. package/dist/judge.d.ts +62 -0
  73. package/dist/judge.d.ts.map +1 -0
  74. package/dist/judge.js +305 -0
  75. package/dist/judge.js.map +1 -0
  76. package/dist/mock-agent-provider.d.ts +3 -0
  77. package/dist/mock-agent-provider.d.ts.map +1 -1
  78. package/dist/mock-agent-provider.js +13 -0
  79. package/dist/mock-agent-provider.js.map +1 -1
  80. package/dist/new-check.d.ts.map +1 -1
  81. package/dist/new-check.js +334 -22
  82. package/dist/new-check.js.map +1 -1
  83. package/dist/opencode-provider.d.ts.map +1 -1
  84. package/dist/opencode-provider.js +33 -0
  85. package/dist/opencode-provider.js.map +1 -1
  86. package/dist/repo-scan.d.ts +80 -0
  87. package/dist/repo-scan.d.ts.map +1 -0
  88. package/dist/repo-scan.js +250 -0
  89. package/dist/repo-scan.js.map +1 -0
  90. package/dist/result-handlers/pr-comment-handler.d.ts +136 -0
  91. package/dist/result-handlers/pr-comment-handler.d.ts.map +1 -0
  92. package/dist/result-handlers/pr-comment-handler.js +381 -0
  93. package/dist/result-handlers/pr-comment-handler.js.map +1 -0
  94. package/dist/retry.d.ts +128 -0
  95. package/dist/retry.d.ts.map +1 -0
  96. package/dist/retry.js +261 -0
  97. package/dist/retry.js.map +1 -0
  98. package/dist/runtime-config.d.ts.map +1 -1
  99. package/dist/runtime-config.js +62 -0
  100. package/dist/runtime-config.js.map +1 -1
  101. package/dist/scan-runner.d.ts +31 -22
  102. package/dist/scan-runner.d.ts.map +1 -1
  103. package/dist/scan-runner.js +145 -100
  104. package/dist/scan-runner.js.map +1 -1
  105. package/dist/semgrep-runner.js +6 -4
  106. package/dist/semgrep-runner.js.map +1 -1
  107. package/dist/stats.d.ts.map +1 -1
  108. package/dist/stats.js +4 -1
  109. package/dist/stats.js.map +1 -1
  110. package/dist/types.d.ts +149 -2
  111. package/dist/types.d.ts.map +1 -1
  112. package/dist/types.js.map +1 -1
  113. package/package.json +1 -1
@@ -0,0 +1,432 @@
1
+ /**
2
+ * HTML output formatter — self-contained interactive report.
3
+ *
4
+ * Emits a single .html file with inline CSS + JS. Includes:
5
+ * - Header with title, scan timestamp, repository info
6
+ * - Summary statistics (totals + per-status counters)
7
+ * - Filterable, severity-badged issues table
8
+ * - Expandable per-check sections with optional code snippets
9
+ * - The full `ScanResults` embedded as a JSON island for client-side filtering
10
+ *
11
+ * All user-controlled strings are HTML-escaped via `escapeHtml`. The embedded
12
+ * JSON has `</` sequences neutralised so an attacker-controlled string cannot
13
+ * break out of the `<script type="application/json">` tag.
14
+ */
15
+ /** Escapes the five HTML special characters. */
16
+ export function escapeHtml(value) {
17
+ if (value === undefined || value === null)
18
+ return '';
19
+ const str = String(value);
20
+ return str
21
+ .replace(/&/g, '&amp;')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;')
24
+ .replace(/"/g, '&quot;')
25
+ .replace(/'/g, '&#39;');
26
+ }
27
+ /**
28
+ * Embeds JSON inside a `<script type="application/json">` block safely.
29
+ *
30
+ * Two patterns matter for HTML's script-data tokenizer:
31
+ * 1. `</` — replaced with `<\/` so a stray `</script>` cannot terminate the
32
+ * script element early. `\/` is valid JSON for `/`, so JSON.parse
33
+ * round-trips faithfully.
34
+ * 2. `<!--` — the script-data tokenizer enters a "script data escaped"
35
+ * state on `<!--`, in which a nested `<script>...</script>` pair can
36
+ * terminate the outer element ahead of the literal closing tag we control.
37
+ * Encoding `<` as the Unicode escape `<` is unambiguous JSON
38
+ * (parses back to `<`) and prevents the tokenizer from ever entering the
39
+ * escaped state.
40
+ */
41
+ export function escapeJsonForScriptTag(json) {
42
+ return json
43
+ .replace(/<\//g, '<\\/')
44
+ .replace(/<!--/g, '\\u003c!--');
45
+ }
46
+ function severityClass(severity) {
47
+ switch (severity) {
48
+ case 'critical':
49
+ case 'high':
50
+ return 'sev-high';
51
+ case 'medium':
52
+ return 'sev-medium';
53
+ case 'low':
54
+ case 'informational':
55
+ return 'sev-low';
56
+ default:
57
+ return 'sev-unknown';
58
+ }
59
+ }
60
+ function statusClass(status) {
61
+ switch (status) {
62
+ case 'PASS':
63
+ return 'status-pass';
64
+ case 'FAIL':
65
+ return 'status-fail';
66
+ case 'FLAG':
67
+ return 'status-flag';
68
+ case 'ERROR':
69
+ return 'status-error';
70
+ default:
71
+ return 'status-unknown';
72
+ }
73
+ }
74
+ const CSS = `
75
+ :root {
76
+ color-scheme: light dark;
77
+ --bg: #f6f7f9;
78
+ --fg: #1f2329;
79
+ --muted: #6a7280;
80
+ --card: #ffffff;
81
+ --border: #e2e5ea;
82
+ --pass: #15803d;
83
+ --fail: #b91c1c;
84
+ --flag: #b45309;
85
+ --error: #6b21a8;
86
+ --sev-high: #b91c1c;
87
+ --sev-medium: #b45309;
88
+ --sev-low: #2563eb;
89
+ --sev-unknown: #6a7280;
90
+ }
91
+ @media (prefers-color-scheme: dark) {
92
+ :root {
93
+ --bg: #14171c;
94
+ --fg: #e6e8eb;
95
+ --muted: #9ba1aa;
96
+ --card: #1c2026;
97
+ --border: #2a2f37;
98
+ }
99
+ }
100
+ * { box-sizing: border-box; }
101
+ body {
102
+ margin: 0;
103
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
104
+ background: var(--bg);
105
+ color: var(--fg);
106
+ line-height: 1.5;
107
+ }
108
+ .container { max-width: 1200px; margin: 0 auto; padding: 24px; }
109
+ header { margin-bottom: 24px; }
110
+ header h1 { margin: 0 0 4px; font-size: 1.6rem; }
111
+ header .meta { color: var(--muted); font-size: 0.9rem; }
112
+ .muted { color: var(--muted); }
113
+ .card {
114
+ background: var(--card);
115
+ border: 1px solid var(--border);
116
+ border-radius: 8px;
117
+ padding: 16px;
118
+ margin-bottom: 16px;
119
+ }
120
+ .summary-grid {
121
+ display: grid;
122
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
123
+ gap: 12px;
124
+ }
125
+ .stat {
126
+ background: var(--card);
127
+ border: 1px solid var(--border);
128
+ border-radius: 8px;
129
+ padding: 12px;
130
+ text-align: center;
131
+ }
132
+ .stat .num { font-size: 1.6rem; font-weight: 600; }
133
+ .stat .label { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }
134
+ .badge {
135
+ display: inline-block;
136
+ padding: 2px 8px;
137
+ border-radius: 999px;
138
+ font-size: 0.75rem;
139
+ font-weight: 600;
140
+ text-transform: uppercase;
141
+ letter-spacing: 0.04em;
142
+ color: #fff;
143
+ }
144
+ .badge.sev-high { background: var(--sev-high); }
145
+ .badge.sev-medium { background: var(--sev-medium); }
146
+ .badge.sev-low { background: var(--sev-low); }
147
+ .badge.sev-unknown { background: var(--sev-unknown); }
148
+ .badge.status-pass { background: var(--pass); }
149
+ .badge.status-fail { background: var(--fail); }
150
+ .badge.status-flag { background: var(--flag); }
151
+ .badge.status-error { background: var(--error); }
152
+ .badge.status-unknown { background: var(--sev-unknown); }
153
+ .controls { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
154
+ .controls input, .controls select {
155
+ background: var(--card);
156
+ color: var(--fg);
157
+ border: 1px solid var(--border);
158
+ border-radius: 6px;
159
+ padding: 6px 10px;
160
+ font: inherit;
161
+ }
162
+ table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
163
+ th, td {
164
+ text-align: left;
165
+ padding: 8px 10px;
166
+ border-bottom: 1px solid var(--border);
167
+ vertical-align: top;
168
+ }
169
+ th { background: var(--card); font-weight: 600; }
170
+ tr.hidden { display: none; }
171
+ details { margin-top: 8px; }
172
+ details summary { cursor: pointer; font-weight: 600; }
173
+ pre.snippet {
174
+ background: var(--bg);
175
+ border: 1px solid var(--border);
176
+ border-radius: 6px;
177
+ padding: 8px;
178
+ overflow-x: auto;
179
+ font-size: 0.8rem;
180
+ margin: 8px 0 0;
181
+ }
182
+ .empty { color: var(--muted); font-style: italic; }
183
+ footer { color: var(--muted); font-size: 0.8rem; text-align: center; margin: 32px 0 16px; }
184
+ `;
185
+ const CLIENT_JS = `
186
+ (function () {
187
+ var data;
188
+ try {
189
+ data = JSON.parse(document.getElementById('aghast-results').textContent);
190
+ } catch (e) {
191
+ return;
192
+ }
193
+ var search = document.getElementById('filter-search');
194
+ var severity = document.getElementById('filter-severity');
195
+ var status = document.getElementById('filter-status');
196
+ var rows = Array.prototype.slice.call(document.querySelectorAll('#issues-table tbody tr'));
197
+
198
+ function applyFilters() {
199
+ var q = (search.value || '').toLowerCase();
200
+ var sev = severity.value;
201
+ var st = status.value;
202
+ rows.forEach(function (row) {
203
+ var text = row.getAttribute('data-search') || '';
204
+ var rowSev = row.getAttribute('data-severity') || '';
205
+ var rowStatus = row.getAttribute('data-status') || '';
206
+ var matches = (!q || text.indexOf(q) !== -1)
207
+ && (!sev || rowSev === sev)
208
+ && (!st || rowStatus === st);
209
+ row.classList.toggle('hidden', !matches);
210
+ });
211
+ }
212
+
213
+ if (search) search.addEventListener('input', applyFilters);
214
+ if (severity) severity.addEventListener('change', applyFilters);
215
+ if (status) status.addEventListener('change', applyFilters);
216
+
217
+ // Expose data for debugging / extensibility.
218
+ window.aghastResults = data;
219
+ })();
220
+ `;
221
+ function renderHeader(results) {
222
+ const repo = results.repository;
223
+ const repoLabel = repo.remoteUrl
224
+ ? `${escapeHtml(repo.remoteUrl)}`
225
+ : escapeHtml(repo.path);
226
+ const branch = repo.branch ? ` <span class="muted">on ${escapeHtml(repo.branch)}</span>` : '';
227
+ const commit = repo.commit ? ` <span class="muted">@ ${escapeHtml(repo.commit.slice(0, 12))}</span>` : '';
228
+ return `
229
+ <header>
230
+ <h1>aghast Security Scan Report</h1>
231
+ <div class="meta">
232
+ Scan ID: <code>${escapeHtml(results.scanId)}</code> &middot;
233
+ Generated: ${escapeHtml(results.timestamp)} &middot;
234
+ Provider: ${escapeHtml(results.agentProvider.name)}
235
+ </div>
236
+ <div class="meta">
237
+ Repository: ${repoLabel}${branch}${commit}
238
+ </div>
239
+ </header>
240
+ `;
241
+ }
242
+ /**
243
+ * Format a cost for display. Sub-cent runs are common with cheap models, and
244
+ * rounding them to "$0.00" reads as "free" rather than "very small".
245
+ */
246
+ function formatCost(amount, currency) {
247
+ const decimals = amount > 0 && amount < 0.01 ? 4 : 2;
248
+ const value = amount.toFixed(decimals);
249
+ return currency === 'USD' ? `$${value}` : `${value} ${escapeHtml(currency)}`;
250
+ }
251
+ function renderSummary(results, _repo) {
252
+ const { summary } = results;
253
+ const extra = [];
254
+ // Judge tiles appear only when the stage ran. `judgedIssues` is the marker:
255
+ // every other counter is legitimately zero on a clean scan and cannot
256
+ // distinguish "judge ran and found nothing" from "judge never ran".
257
+ if (summary.judgedIssues !== undefined) {
258
+ extra.push(`<div class="stat"><div class="num">${summary.judgedIssues}</div><div class="label">Judged</div></div>`);
259
+ if (summary.falsePositives !== undefined) {
260
+ extra.push(`<div class="stat"><div class="num">${summary.falsePositives}</div><div class="label">False positives</div></div>`);
261
+ }
262
+ if (summary.uncertainJudgements !== undefined) {
263
+ extra.push(`<div class="stat"><div class="num">${summary.uncertainJudgements}</div><div class="label">Uncertain</div></div>`);
264
+ }
265
+ }
266
+ const cost = results.metadata?.cost;
267
+ if (cost) {
268
+ extra.push(`<div class="stat"><div class="num">${escapeHtml(formatCost(cost.totalCostUsd, cost.currency))}</div>`
269
+ + `<div class="label">Est. cost</div></div>`);
270
+ }
271
+ return `
272
+ <section class="summary-grid">
273
+ <div class="stat"><div class="num">${summary.totalChecks}</div><div class="label">Checks</div></div>
274
+ <div class="stat"><div class="num">${summary.passedChecks}</div><div class="label">Passed</div></div>
275
+ <div class="stat"><div class="num">${summary.failedChecks}</div><div class="label">Failed</div></div>
276
+ <div class="stat"><div class="num">${summary.flaggedChecks}</div><div class="label">Flagged</div></div>
277
+ <div class="stat"><div class="num">${summary.errorChecks}</div><div class="label">Errors</div></div>
278
+ <div class="stat"><div class="num">${summary.totalIssues}</div><div class="label">Issues</div></div>
279
+ ${extra.join('\n ')}
280
+ </section>
281
+ `;
282
+ }
283
+ function renderIssuesTable(issues, statusByCheck) {
284
+ if (issues.length === 0) {
285
+ return `<section class="card"><h2>Issues</h2><p class="empty">No issues detected.</p></section>`;
286
+ }
287
+ // Only carry a Verdict column when the judge actually annotated something.
288
+ // A permanently empty column on every non-judged scan is worse than none.
289
+ const hasVerdicts = issues.some((i) => i.judge);
290
+ const rows = issues.map((issue) => {
291
+ const sev = issue.severity ?? '';
292
+ const sevCls = severityClass(issue.severity);
293
+ const sevBadge = `<span class="badge ${sevCls}">${escapeHtml(sev || 'unknown')}</span>`;
294
+ const verdictCell = hasVerdicts
295
+ ? `<td>${issue.judge
296
+ ? `<span class="badge">${escapeHtml(issue.judge.verdict)}</span> `
297
+ + `<span title="${escapeHtml(issue.judge.rationale ?? '')}">`
298
+ + `${Math.round(issue.judge.confidence * 100)}%</span>`
299
+ : ''}</td>`
300
+ : '';
301
+ // Fall back to 'UNKNOWN' (not 'FAIL') for orphaned issues so the row's
302
+ // status badge and the data-status filter don't lie when an issue
303
+ // references a checkId that isn't in the checks list.
304
+ const status = statusByCheck.get(issue.checkId) ?? 'UNKNOWN';
305
+ const statusCls = statusClass(status);
306
+ const statusBadge = `<span class="badge ${statusCls}">${escapeHtml(status)}</span>`;
307
+ const searchBlob = `${issue.checkId} ${issue.checkName} ${issue.file} ${issue.description}`.toLowerCase();
308
+ return `
309
+ <tr data-severity="${escapeHtml(sev)}" data-status="${escapeHtml(status)}" data-search="${escapeHtml(searchBlob)}">
310
+ <td>${escapeHtml(issue.checkId)}</td>
311
+ <td>${escapeHtml(issue.checkName)}</td>
312
+ <td>${statusBadge}</td>
313
+ <td>${sevBadge}</td>
314
+ <td><code>${escapeHtml(issue.file)}</code>:${escapeHtml(issue.startLine)}-${escapeHtml(issue.endLine)}</td>
315
+ <td>${escapeHtml(issue.description)}</td>
316
+ ${verdictCell}
317
+ </tr>
318
+ `;
319
+ }).join('');
320
+ return `
321
+ <section class="card">
322
+ <h2>Issues</h2>
323
+ <div class="controls">
324
+ <input id="filter-search" type="search" placeholder="Filter by text..." />
325
+ <select id="filter-severity">
326
+ <option value="">All severities</option>
327
+ <option value="critical">Critical</option>
328
+ <option value="high">High</option>
329
+ <option value="medium">Medium</option>
330
+ <option value="low">Low</option>
331
+ <option value="informational">Informational</option>
332
+ </select>
333
+ <select id="filter-status">
334
+ <option value="">All statuses</option>
335
+ <option value="PASS">Pass</option>
336
+ <option value="FAIL">Fail</option>
337
+ <option value="FLAG">Flag</option>
338
+ <option value="ERROR">Error</option>
339
+ </select>
340
+ </div>
341
+ <table id="issues-table" aria-label="Security issues">
342
+ <thead>
343
+ <tr><th>Check ID</th><th>Check</th><th>Status</th><th>Severity</th><th>Location</th><th>Description</th>${hasVerdicts ? '<th>Verdict</th>' : ''}</tr>
344
+ </thead>
345
+ <tbody>${rows}</tbody>
346
+ </table>
347
+ </section>
348
+ `;
349
+ }
350
+ function renderCheckDetails(checks, issuesByCheck) {
351
+ if (checks.length === 0) {
352
+ return '';
353
+ }
354
+ const sections = checks.map((check) => {
355
+ const checkIssues = issuesByCheck.get(check.checkId) ?? [];
356
+ const statusBadge = `<span class="badge ${statusClass(check.status)}">${escapeHtml(check.status)}</span>`;
357
+ const errorBlock = check.error
358
+ ? `<p><strong>Error:</strong> ${escapeHtml(check.error)}</p>`
359
+ : '';
360
+ const issuesBlock = checkIssues.length === 0
361
+ ? '<p class="empty">No issues for this check.</p>'
362
+ : checkIssues.map((issue) => {
363
+ const snippet = issue.codeSnippet
364
+ ? `<pre class="snippet">${escapeHtml(issue.codeSnippet)}</pre>`
365
+ : '';
366
+ const recommendation = issue.recommendation
367
+ ? `<p><strong>Recommendation:</strong> ${escapeHtml(issue.recommendation)}</p>`
368
+ : '';
369
+ return `
370
+ <div class="card">
371
+ <div><strong><code>${escapeHtml(issue.file)}</code>:${escapeHtml(issue.startLine)}-${escapeHtml(issue.endLine)}</strong>
372
+ ${issue.severity ? `<span class="badge ${severityClass(issue.severity)}">${escapeHtml(issue.severity)}</span>` : ''}
373
+ </div>
374
+ <p>${escapeHtml(issue.description)}</p>
375
+ ${recommendation}
376
+ ${snippet}
377
+ </div>
378
+ `;
379
+ }).join('');
380
+ return `
381
+ <details>
382
+ <summary>${escapeHtml(check.checkName)} (${escapeHtml(check.checkId)}) ${statusBadge} &middot; ${check.issuesFound} issue(s)</summary>
383
+ ${errorBlock}
384
+ ${issuesBlock}
385
+ </details>
386
+ `;
387
+ }).join('');
388
+ return `<section class="card"><h2>Checks</h2>${sections}</section>`;
389
+ }
390
+ export class HtmlFormatter {
391
+ id = 'html';
392
+ fileExtension = '.html';
393
+ format(results) {
394
+ const statusByCheck = new Map();
395
+ const issuesByCheck = new Map();
396
+ for (const check of results.checks) {
397
+ statusByCheck.set(check.checkId, check.status);
398
+ issuesByCheck.set(check.checkId, []);
399
+ }
400
+ for (const issue of results.issues) {
401
+ const arr = issuesByCheck.get(issue.checkId);
402
+ if (arr)
403
+ arr.push(issue);
404
+ else
405
+ issuesByCheck.set(issue.checkId, [issue]);
406
+ }
407
+ const embeddedJson = escapeJsonForScriptTag(JSON.stringify(results));
408
+ const title = `aghast scan ${escapeHtml(results.scanId)}`;
409
+ return `<!DOCTYPE html>
410
+ <html lang="en">
411
+ <head>
412
+ <meta charset="utf-8" />
413
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
414
+ <title>${title}</title>
415
+ <style>${CSS}</style>
416
+ </head>
417
+ <body>
418
+ <div class="container">
419
+ ${renderHeader(results)}
420
+ ${renderSummary(results, results.repository)}
421
+ ${renderIssuesTable(results.issues, statusByCheck)}
422
+ ${renderCheckDetails(results.checks, issuesByCheck)}
423
+ <footer>Generated by aghast v${escapeHtml(results.version)}</footer>
424
+ </div>
425
+ <script id="aghast-results" type="application/json">${embeddedJson}</script>
426
+ <script>${CLIENT_JS}</script>
427
+ </body>
428
+ </html>
429
+ `;
430
+ }
431
+ }
432
+ //# sourceMappingURL=html-formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-formatter.js","sourceRoot":"","sources":["../../src/formatters/html-formatter.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAOlD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,CAOxD;AAED,yCAAyC;AACzC,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAOlD,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAUlD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,CAOxD;AAED,yCAAyC;AACzC,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C"}
@@ -3,9 +3,15 @@
3
3
  */
4
4
  import { JsonFormatter } from './json-formatter.js';
5
5
  import { SarifFormatter } from './sarif-formatter.js';
6
+ import { CsvFormatter } from './csv-formatter.js';
7
+ import { HtmlFormatter } from './html-formatter.js';
8
+ import { MarkdownFormatter } from './markdown-formatter.js';
6
9
  const formatters = new Map([
7
10
  ['json', new JsonFormatter()],
8
11
  ['sarif', new SarifFormatter()],
12
+ ['csv', new CsvFormatter()],
13
+ ['html', new HtmlFormatter()],
14
+ ['markdown', new MarkdownFormatter()],
9
15
  ]);
10
16
  /** Returns the formatter for the given ID, or throws listing available formats. */
11
17
  export function getFormatter(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAItD,MAAM,UAAU,GAAG,IAAI,GAAG,CAA0B;IAClD,CAAC,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;IAC7B,CAAC,OAAO,EAAE,IAAI,cAAc,EAAE,CAAC;CAChC,CAAC,CAAC;AAEH,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,0BAA0B,EAAE,yBAAyB,SAAS,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,mBAAmB;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5D,MAAM,UAAU,GAAG,IAAI,GAAG,CAA0B;IAClD,CAAC,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;IAC7B,CAAC,OAAO,EAAE,IAAI,cAAc,EAAE,CAAC;IAC/B,CAAC,KAAK,EAAE,IAAI,YAAY,EAAE,CAAC;IAC3B,CAAC,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;IAC7B,CAAC,UAAU,EAAE,IAAI,iBAAiB,EAAE,CAAC;CACtC,CAAC,CAAC;AAEH,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,0BAA0B,EAAE,yBAAyB,SAAS,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,mBAAmB;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Markdown output formatter — produces a human-readable security report.
3
+ * See SPECIFICATION.md Appendix E.3.1.
4
+ *
5
+ * Sections (in order):
6
+ * 1. Header — Title, date, AI provider/model, repository info
7
+ * 2. Executive Summary — One-paragraph overview of the scan outcome
8
+ * 3. Summary Table — All checks with PASS/FAIL/FLAG/ERROR status
9
+ * 4. Detailed Findings — Failed checks with descriptions, snippets, recs
10
+ * 5. Flagged Items — Checks needing human review (only when present)
11
+ * 6. Errors — Checks with ERROR status (only when present)
12
+ * 7. Statistics — Totals for the scan
13
+ * 8. CI Metadata — Job URL / branch / pipeline info (only when set)
14
+ */
15
+ import type { ScanResults } from '../types.js';
16
+ import type { OutputFormatter } from './types.js';
17
+ /** Resolves the fenced-code-block language tag from a file path. */
18
+ export declare function languageForFile(filePath: string): string;
19
+ /** Wraps code in a fenced block with a language tag, escaping nested fences safely. */
20
+ export declare function fencedCode(code: string, language: string): string;
21
+ /**
22
+ * Wraps `value` in a backtick fence long enough to survive any backticks
23
+ * already inside `value`. Per CommonMark, an inline-code span uses a backtick
24
+ * run of length N around a body that contains no run of length N — minimum
25
+ * length 1 (unlike fenced code blocks, which require minimum 3). Pad with a
26
+ * leading/trailing space if the body itself starts or ends with a backtick.
27
+ * Newlines are collapsed to spaces because inline-code spans must be
28
+ * single-line (and table cells must not contain raw newlines).
29
+ */
30
+ export declare function inlineCode(value: string): string;
31
+ /**
32
+ * Escapes AI-authored free text (issue `description` / `recommendation`) for
33
+ * safe rendering as a Markdown *block*.
34
+ *
35
+ * Threat model: these fields come from the LLM's analysis of the scanned
36
+ * repository, whose source can contain attacker-crafted strings. Rendered raw
37
+ * they can inject active HTML that executes when the report is viewed on GitHub,
38
+ * a wiki, or any Markdown viewer permitting inline HTML (`<script>`,
39
+ * `<img onerror=...>`), and can break the report's own structure (table cells,
40
+ * headings, code fences, lists, blockquotes). The HTML formatter already defends
41
+ * the same fields via `escapeHtml`; this is the Markdown-side equivalent.
42
+ *
43
+ * Unlike {@link escapeInlineText}, newlines are *preserved* — these are prose
44
+ * paragraphs, not single-line bullets — so the escaping is applied per line:
45
+ * 1. Inline pass: backslash-escape every character that can open inline
46
+ * formatting or a raw HTML tag (`\` first so we don't re-escape our own
47
+ * escapes). Escaping `<` is what neutralises the inline-HTML vector — per
48
+ * CommonMark/GFM, `\<script>` renders as literal text.
49
+ * 2. Block pass: backslash-escape a leading marker (after optional
50
+ * indentation) that would open a block construct — ATX heading (`#`),
51
+ * list/thematic-break (`-`/`+`), setext underline (`=`), or an ordered-list
52
+ * marker (`1.` / `1)`). Blockquote (`>`) and fences (`` ` ``/`~`) are
53
+ * already handled by the inline pass.
54
+ */
55
+ export declare function escapeMarkdownText(value: string): string;
56
+ export declare class MarkdownFormatter implements OutputFormatter {
57
+ readonly id = "markdown";
58
+ readonly fileExtension = ".md";
59
+ format(results: ScanResults): string;
60
+ }
61
+ //# sourceMappingURL=markdown-formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-formatter.d.ts","sourceRoot":"","sources":["../../src/formatters/markdown-formatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA0ClD,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxD;AAeD,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAoBhD;AA0BD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBxD;AAsWD,qBAAa,iBAAkB,YAAW,eAAe;IACvD,QAAQ,CAAC,EAAE,cAAc;IACzB,QAAQ,CAAC,aAAa,SAAS;IAE/B,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM;CAkBrC"}