@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.
- package/config/pricing.json +39 -9
- package/dist/build-config.d.ts.map +1 -1
- package/dist/build-config.js +101 -2
- package/dist/build-config.js.map +1 -1
- package/dist/check-library.d.ts +25 -0
- package/dist/check-library.d.ts.map +1 -1
- package/dist/check-library.js +172 -1
- package/dist/check-library.js.map +1 -1
- package/dist/ci-metadata.d.ts +42 -0
- package/dist/ci-metadata.d.ts.map +1 -0
- package/dist/ci-metadata.js +131 -0
- package/dist/ci-metadata.js.map +1 -0
- package/dist/claude-code-provider.d.ts +7 -0
- package/dist/claude-code-provider.d.ts.map +1 -1
- package/dist/claude-code-provider.js +65 -1
- package/dist/claude-code-provider.js.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/concurrency.d.ts +18 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +39 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/cost-tracker.d.ts +45 -0
- package/dist/cost-tracker.d.ts.map +1 -0
- package/dist/cost-tracker.js +56 -0
- package/dist/cost-tracker.js.map +1 -0
- package/dist/discoveries/glob-discovery.d.ts +21 -0
- package/dist/discoveries/glob-discovery.d.ts.map +1 -0
- package/dist/discoveries/glob-discovery.js +215 -0
- package/dist/discoveries/glob-discovery.js.map +1 -0
- package/dist/discoveries/script-discovery.d.ts +117 -0
- package/dist/discoveries/script-discovery.d.ts.map +1 -0
- package/dist/discoveries/script-discovery.js +593 -0
- package/dist/discoveries/script-discovery.js.map +1 -0
- package/dist/docs-url.d.ts +14 -0
- package/dist/docs-url.d.ts.map +1 -0
- package/dist/docs-url.js +17 -0
- package/dist/docs-url.js.map +1 -0
- package/dist/error-codes.d.ts +12 -1
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +16 -2
- package/dist/error-codes.js.map +1 -1
- package/dist/formatters/csv-formatter.d.ts +45 -0
- package/dist/formatters/csv-formatter.d.ts.map +1 -0
- package/dist/formatters/csv-formatter.js +176 -0
- package/dist/formatters/csv-formatter.js.map +1 -0
- package/dist/formatters/html-formatter.d.ts +39 -0
- package/dist/formatters/html-formatter.d.ts.map +1 -0
- package/dist/formatters/html-formatter.js +432 -0
- package/dist/formatters/html-formatter.js.map +1 -0
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +6 -0
- package/dist/formatters/index.js.map +1 -1
- package/dist/formatters/markdown-formatter.d.ts +61 -0
- package/dist/formatters/markdown-formatter.d.ts.map +1 -0
- package/dist/formatters/markdown-formatter.js +557 -0
- package/dist/formatters/markdown-formatter.js.map +1 -0
- package/dist/formatters/sarif-formatter.d.ts +7 -0
- package/dist/formatters/sarif-formatter.d.ts.map +1 -1
- package/dist/formatters/sarif-formatter.js +80 -0
- package/dist/formatters/sarif-formatter.js.map +1 -1
- package/dist/formatters/types.d.ts +24 -0
- package/dist/formatters/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +365 -10
- package/dist/index.js.map +1 -1
- package/dist/issue-file-writer.d.ts +21 -0
- package/dist/issue-file-writer.d.ts.map +1 -0
- package/dist/issue-file-writer.js +307 -0
- package/dist/issue-file-writer.js.map +1 -0
- package/dist/judge.d.ts +62 -0
- package/dist/judge.d.ts.map +1 -0
- package/dist/judge.js +305 -0
- package/dist/judge.js.map +1 -0
- package/dist/mock-agent-provider.d.ts +3 -0
- package/dist/mock-agent-provider.d.ts.map +1 -1
- package/dist/mock-agent-provider.js +13 -0
- package/dist/mock-agent-provider.js.map +1 -1
- package/dist/new-check.d.ts.map +1 -1
- package/dist/new-check.js +334 -22
- package/dist/new-check.js.map +1 -1
- package/dist/opencode-provider.d.ts.map +1 -1
- package/dist/opencode-provider.js +33 -0
- package/dist/opencode-provider.js.map +1 -1
- package/dist/repo-scan.d.ts +80 -0
- package/dist/repo-scan.d.ts.map +1 -0
- package/dist/repo-scan.js +250 -0
- package/dist/repo-scan.js.map +1 -0
- package/dist/result-handlers/pr-comment-handler.d.ts +136 -0
- package/dist/result-handlers/pr-comment-handler.d.ts.map +1 -0
- package/dist/result-handlers/pr-comment-handler.js +381 -0
- package/dist/result-handlers/pr-comment-handler.js.map +1 -0
- package/dist/retry.d.ts +128 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +261 -0
- package/dist/retry.js.map +1 -0
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +62 -0
- package/dist/runtime-config.js.map +1 -1
- package/dist/scan-runner.d.ts +31 -22
- package/dist/scan-runner.d.ts.map +1 -1
- package/dist/scan-runner.js +145 -100
- package/dist/scan-runner.js.map +1 -1
- package/dist/semgrep-runner.js +6 -4
- package/dist/semgrep-runner.js.map +1 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +4 -1
- package/dist/stats.js.map +1 -1
- package/dist/types.d.ts +149 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- 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, '&')
|
|
22
|
+
.replace(/</g, '<')
|
|
23
|
+
.replace(/>/g, '>')
|
|
24
|
+
.replace(/"/g, '"')
|
|
25
|
+
.replace(/'/g, ''');
|
|
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> ·
|
|
233
|
+
Generated: ${escapeHtml(results.timestamp)} ·
|
|
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} · ${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;
|
|
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"}
|
package/dist/formatters/index.js
CHANGED
|
@@ -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;
|
|
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"}
|