@secureport/core 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -10
- package/dist/import/burp.d.ts +19 -0
- package/dist/import/burp.d.ts.map +1 -0
- package/dist/import/burp.js +114 -0
- package/dist/import/burp.js.map +1 -0
- package/dist/import/generic.d.ts +90 -0
- package/dist/import/generic.d.ts.map +1 -0
- package/dist/import/generic.js +159 -0
- package/dist/import/generic.js.map +1 -0
- package/dist/import/nessus.d.ts +32 -0
- package/dist/import/nessus.d.ts.map +1 -0
- package/dist/import/nessus.js +125 -0
- package/dist/import/nessus.js.map +1 -0
- package/dist/import/xml.d.ts +47 -0
- package/dist/import/xml.d.ts.map +1 -0
- package/dist/import/xml.js +157 -0
- package/dist/import/xml.js.map +1 -0
- package/dist/import/zap.d.ts +1 -1
- package/dist/import/zap.js +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/report/html.d.ts +21 -0
- package/dist/report/html.d.ts.map +1 -0
- package/dist/report/html.js +324 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/json.d.ts +81 -0
- package/dist/report/json.d.ts.map +1 -0
- package/dist/report/json.js +47 -0
- package/dist/report/json.js.map +1 -0
- package/dist/report/markdown.d.ts +24 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +304 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/model.d.ts +215 -0
- package/dist/report/model.d.ts.map +1 -0
- package/dist/report/model.js +197 -0
- package/dist/report/model.js.map +1 -0
- package/dist/snapshot-builder.d.ts +8 -0
- package/dist/snapshot-builder.d.ts.map +1 -1
- package/dist/snapshot-builder.js +25 -1
- package/dist/snapshot-builder.js.map +1 -1
- package/package.json +3 -3
- package/src/import/burp.ts +126 -0
- package/src/import/generic.ts +258 -0
- package/src/import/nessus.ts +136 -0
- package/src/import/xml.ts +187 -0
- package/src/import/zap.ts +1 -1
- package/src/index.ts +19 -0
- package/src/report/html.ts +449 -0
- package/src/report/json.ts +134 -0
- package/src/report/markdown.ts +435 -0
- package/src/report/model.ts +462 -0
- package/src/snapshot-builder.ts +28 -2
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { SEVERITY_ORDER } from '../severity.js';
|
|
2
|
+
import { buildReportModel, } from './model.js';
|
|
3
|
+
import { formatDate } from './markdown.js';
|
|
4
|
+
/** Escapes text for HTML. Every value from a snapshot goes through this. */
|
|
5
|
+
function esc(value) {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/&/gu, '&')
|
|
8
|
+
.replace(/</gu, '<')
|
|
9
|
+
.replace(/>/gu, '>')
|
|
10
|
+
.replace(/"/gu, '"');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The stylesheet, inlined.
|
|
14
|
+
*
|
|
15
|
+
* **Inlined rather than linked because a report is a single file that gets
|
|
16
|
+
* emailed.** A stylesheet fetched at render time would make the document depend
|
|
17
|
+
* on a network the reader may not have, and on a URL that must then outlive the
|
|
18
|
+
* report.
|
|
19
|
+
*
|
|
20
|
+
* The print rules are here rather than in P7 because P7 renders this through
|
|
21
|
+
* Chromium: a page that only looks right on screen becomes a PDF that only
|
|
22
|
+
* looks right on screen. Severity colours are chosen to survive greyscale — the
|
|
23
|
+
* label carries the meaning, the colour only reinforces it.
|
|
24
|
+
*/
|
|
25
|
+
const STYLES = `
|
|
26
|
+
:root {
|
|
27
|
+
--ink: #14181f; --muted: #5b6472; --rule: #d9dee6; --bg: #ffffff;
|
|
28
|
+
--critical: #7f1d1d; --high: #9a3412; --medium: #854d0e; --low: #1e40af; --advisory: #475569;
|
|
29
|
+
}
|
|
30
|
+
* { box-sizing: border-box; }
|
|
31
|
+
body {
|
|
32
|
+
margin: 0; padding: 2.5rem; background: var(--bg); color: var(--ink);
|
|
33
|
+
font: 15px/1.6 ui-serif, Georgia, "Times New Roman", serif;
|
|
34
|
+
max-width: 60rem; margin-inline: auto;
|
|
35
|
+
}
|
|
36
|
+
h1 { font-size: 1.9rem; margin: 0 0 .25rem; letter-spacing: -0.01em; }
|
|
37
|
+
h2 { font-size: 1.25rem; margin: 2.5rem 0 .75rem; padding-bottom: .3rem; border-bottom: 1px solid var(--rule); }
|
|
38
|
+
h3 { font-size: 1.05rem; margin: 1.75rem 0 .5rem; text-transform: capitalize; }
|
|
39
|
+
h4 { font-size: 1rem; margin: 1.25rem 0 .4rem; }
|
|
40
|
+
p, li { margin: .5rem 0; }
|
|
41
|
+
.lede { color: var(--muted); margin: 0 0 1.5rem; }
|
|
42
|
+
table { border-collapse: collapse; width: 100%; margin: .75rem 0; font-size: .92em; }
|
|
43
|
+
th, td { text-align: left; padding: .45rem .6rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
|
|
44
|
+
th { font-weight: 600; color: var(--muted); font-size: .82em; text-transform: uppercase; letter-spacing: .04em; }
|
|
45
|
+
td code, code { font: .88em ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
|
|
46
|
+
.meta td:first-child { color: var(--muted); width: 12rem; }
|
|
47
|
+
.sev { font-weight: 600; }
|
|
48
|
+
.sev-critical { color: var(--critical); }
|
|
49
|
+
.sev-high { color: var(--high); }
|
|
50
|
+
.sev-medium { color: var(--medium); }
|
|
51
|
+
.sev-low { color: var(--low); }
|
|
52
|
+
.sev-advisory { color: var(--advisory); }
|
|
53
|
+
.breached { color: var(--critical); font-weight: 600; }
|
|
54
|
+
.issue { break-inside: avoid; page-break-inside: avoid; margin-bottom: 1.5rem; }
|
|
55
|
+
.none { color: var(--muted); font-style: italic; }
|
|
56
|
+
footer { margin-top: 3rem; padding-top: .75rem; border-top: 1px solid var(--rule); color: var(--muted); font-size: .85em; }
|
|
57
|
+
|
|
58
|
+
@media print {
|
|
59
|
+
body { padding: 0; font-size: 11pt; max-width: none; }
|
|
60
|
+
h2 { break-after: avoid; page-break-after: avoid; }
|
|
61
|
+
h3, h4 { break-after: avoid; page-break-after: avoid; }
|
|
62
|
+
table { break-inside: auto; }
|
|
63
|
+
tr { break-inside: avoid; page-break-inside: avoid; }
|
|
64
|
+
a { color: inherit; text-decoration: none; }
|
|
65
|
+
/* A printed report is read on paper: a bare URL is unusable, so print it. */
|
|
66
|
+
.refs a::after { content: " (" attr(href) ")"; font-size: .85em; color: var(--muted); }
|
|
67
|
+
}
|
|
68
|
+
@page { margin: 18mm 16mm; }
|
|
69
|
+
`.trim();
|
|
70
|
+
const sevClass = (s) => `sev sev-${s}`;
|
|
71
|
+
/** The change summary, as a table. */
|
|
72
|
+
function changeSummary(model) {
|
|
73
|
+
const { run, baseline } = model.snapshot;
|
|
74
|
+
const rows = SEVERITY_ORDER.map((s) => `<tr><td class="${sevClass(s)}">${s}</td><td>${run.new[s]}</td><td>${run.stillOpen[s]}</td>` +
|
|
75
|
+
`<td>${run.regressed[s]}</td><td>${run.resolved[s]}</td><td>${run.ignored[s]}</td></tr>`).join('');
|
|
76
|
+
return `<h2>What changed</h2>
|
|
77
|
+
<p class="lede">${baseline === undefined
|
|
78
|
+
? 'First run against this target, so every issue is new.'
|
|
79
|
+
: `Compared with the run of ${formatDate(baseline.createdAt)}.`}</p>
|
|
80
|
+
<table><thead><tr><th>Severity</th><th>New</th><th>Still open</th><th>Regressed</th><th>Resolved</th><th>Suppressed</th></tr></thead><tbody>${rows}</tbody></table>
|
|
81
|
+
<p><strong>${model.outstandingTotal} outstanding.</strong> Exposure score ${model.exposureScore}.${model.breached > 0
|
|
82
|
+
? ` <span class="breached">${model.breached} past ${model.breached === 1 ? 'its' : 'their'} remediation deadline.</span>`
|
|
83
|
+
: ''}</p>`;
|
|
84
|
+
}
|
|
85
|
+
/** One issue in full. */
|
|
86
|
+
function issueDetail(entry) {
|
|
87
|
+
const { issue } = entry;
|
|
88
|
+
const first = entry.findings[0];
|
|
89
|
+
const references = [...new Set(entry.findings.flatMap((f) => f.references ?? []))];
|
|
90
|
+
return `<div class="issue">
|
|
91
|
+
<h4>${esc(issue.title)}</h4>
|
|
92
|
+
<table class="meta"><tbody>
|
|
93
|
+
<tr><td>Severity</td><td class="${sevClass(issue.effectiveSeverity)}">${issue.effectiveSeverity}${issue.effectiveSeverity === issue.detectedSeverity
|
|
94
|
+
? ''
|
|
95
|
+
: ` <span class="lede">(detected ${issue.detectedSeverity})</span>`}</td></tr>
|
|
96
|
+
<tr><td>Status</td><td>${issue.status} · ${entry.change.replace('_', ' ')}</td></tr>
|
|
97
|
+
<tr><td>Location</td><td><code>${esc(issue.location)}</code>${issue.parameter === undefined ? '' : ` <code>${esc(issue.parameter)}</code>`}</td></tr>
|
|
98
|
+
<tr><td>First seen</td><td>${formatDate(issue.firstSeen)} (${entry.daysOpen} days)</td></tr>
|
|
99
|
+
<tr><td>Remediation</td><td${entry.slaStatus === 'breached' ? ' class="breached"' : ''}>${issue.slaDueAt === undefined || issue.slaDueAt === null
|
|
100
|
+
? 'no deadline'
|
|
101
|
+
: `${formatDate(issue.slaDueAt)} — ${entry.slaStatus.replace('_', ' ')}`}</td></tr>
|
|
102
|
+
${issue.cwe === undefined ? '' : `<tr><td>Weakness</td><td>${esc(issue.cwe)}</td></tr>`}
|
|
103
|
+
</tbody></table>
|
|
104
|
+
${first?.description === undefined ? '' : `<p>${esc(first.description)}</p>`}
|
|
105
|
+
${first?.recommendation === undefined ? '' : `<p><strong>Recommendation.</strong> ${esc(first.recommendation)}</p>`}
|
|
106
|
+
${entry.findings.length === 0
|
|
107
|
+
? ''
|
|
108
|
+
: `<p class="lede">Evidence: ${entry.findings.length} finding${entry.findings.length === 1 ? '' : 's'} recorded.</p>`}
|
|
109
|
+
${references.length === 0
|
|
110
|
+
? ''
|
|
111
|
+
: `<ul class="refs">${references.map((r) => `<li><a href="${esc(r)}">${esc(r)}</a></li>`).join('')}</ul>`}
|
|
112
|
+
</div>`;
|
|
113
|
+
}
|
|
114
|
+
/** How a verdict is printed. The dashes are an implementation detail. */
|
|
115
|
+
const VERDICT_LABELS = Object.freeze({
|
|
116
|
+
fixed: 'fixed',
|
|
117
|
+
still_present: 'still present',
|
|
118
|
+
returned: 'returned',
|
|
119
|
+
not_retested: 'not retested',
|
|
120
|
+
new: 'new since',
|
|
121
|
+
});
|
|
122
|
+
/** The Retest body: the difference between two runs, and nothing else. */
|
|
123
|
+
function retestBody(model) {
|
|
124
|
+
// `buildReportModel` refuses to build a retest without one.
|
|
125
|
+
const retest = model.retest;
|
|
126
|
+
const { counts } = retest;
|
|
127
|
+
const carried = counts.fixed + counts.still_present + counts.returned + counts.not_retested;
|
|
128
|
+
const verdicts = Object.keys(VERDICT_LABELS)
|
|
129
|
+
.map((v) => `<tr><td>${VERDICT_LABELS[v]}</td><td>${counts[v]}</td></tr>`)
|
|
130
|
+
.join('');
|
|
131
|
+
const rows = retest.entries
|
|
132
|
+
.map((entry) => {
|
|
133
|
+
const { issue } = entry.issue;
|
|
134
|
+
const location = issue.parameter === undefined ? issue.location : `${issue.location} (${issue.parameter})`;
|
|
135
|
+
return `<tr><td>${esc(issue.title)}</td><td class="${sevClass(issue.effectiveSeverity)}">${issue.effectiveSeverity}</td><td>${VERDICT_LABELS[entry.verdict]}</td><td><code>${esc(location)}</code></td><td>${entry.issue.daysOpen} days</td></tr>`;
|
|
136
|
+
})
|
|
137
|
+
.join('');
|
|
138
|
+
return `<h2>Retest verdict</h2>
|
|
139
|
+
<p class="lede">Retested against the run of ${formatDate(retest.baseline.createdAt)}.</p>
|
|
140
|
+
<p>${carried === 0
|
|
141
|
+
? 'No issues were carried into this retest.'
|
|
142
|
+
: `Of the ${carried} issue${carried === 1 ? '' : 's'} carried into this retest, <strong>${counts.fixed} ${counts.fixed === 1 ? 'is' : 'are'} confirmed fixed</strong>, ${counts.still_present} ${counts.still_present === 1 ? 'is' : 'are'} still present, ${counts.returned} returned, and ${counts.not_retested} could not be retested.${counts.new === 0
|
|
143
|
+
? ''
|
|
144
|
+
: ` A further ${counts.new} ${counts.new === 1 ? 'issue was' : 'issues were'} found for the first time by this run.`}`}</p>
|
|
145
|
+
${retest.fixRate === null
|
|
146
|
+
? ''
|
|
147
|
+
: `<p>${Math.round(retest.fixRate * 100)}% of the issues this run could check are fixed.</p>`}
|
|
148
|
+
<table><thead><tr><th>Verdict</th><th>Issues</th></tr></thead><tbody>${verdicts}</tbody></table>
|
|
149
|
+
${counts.not_retested === 0
|
|
150
|
+
? ''
|
|
151
|
+
: `<h2>Not retested</h2><p>${counts.not_retested} issue${counts.not_retested === 1 ? '' : 's'} could not be retested by this run: the run did not cover where they were found, or has not missed them often enough to call them resolved. <strong>They are not fixed.</strong> An issue nobody looked at is neither fixed nor unfixed, and it is excluded from the percentage above rather than counted either way.</p>`}
|
|
152
|
+
<h2>Issue by issue</h2>
|
|
153
|
+
${rows === ''
|
|
154
|
+
? '<p class="none">No issues.</p>'
|
|
155
|
+
: `<table><thead><tr><th>Issue</th><th>Severity</th><th>Verdict</th><th>Location</th><th>Open for</th></tr></thead><tbody>${rows}</tbody></table>`}`;
|
|
156
|
+
}
|
|
157
|
+
/** The Executive Summary body. */
|
|
158
|
+
function executiveBody(model) {
|
|
159
|
+
const resolvedCount = model.resolved.length;
|
|
160
|
+
const regressed = model.sections
|
|
161
|
+
.flatMap((s) => s.issues)
|
|
162
|
+
.filter((i) => i.change === 'regressed').length;
|
|
163
|
+
const urgent = model.outstanding.critical + model.outstanding.high;
|
|
164
|
+
const notes = [];
|
|
165
|
+
if (resolvedCount > 0) {
|
|
166
|
+
notes.push(`<p>${resolvedCount} issue${resolvedCount === 1 ? ' was' : 's were'} resolved since the last run.</p>`);
|
|
167
|
+
}
|
|
168
|
+
if (regressed > 0) {
|
|
169
|
+
notes.push(`<p><strong>${regressed} previously resolved issue${regressed === 1 ? ' has' : 's have'} returned.</strong> A returning issue usually means a fix was reverted or incompletely applied.</p>`);
|
|
170
|
+
}
|
|
171
|
+
if (model.breached > 0) {
|
|
172
|
+
notes.push(`<p class="breached">${model.breached} ${model.breached === 1 ? 'is' : 'are'} past the remediation deadline set by the severity policy.</p>`);
|
|
173
|
+
}
|
|
174
|
+
const rows = SEVERITY_ORDER.filter((sev) => model.outstanding[sev] > 0)
|
|
175
|
+
.map((sev) => `<tr><td class="${sevClass(sev)}">${sev}</td><td>${model.outstanding[sev]}</td></tr>`)
|
|
176
|
+
.join('');
|
|
177
|
+
const notable = model.sections
|
|
178
|
+
.flatMap((s) => s.issues)
|
|
179
|
+
.filter((i) => ['critical', 'high'].includes(i.issue.effectiveSeverity))
|
|
180
|
+
.slice(0, 10);
|
|
181
|
+
return `<h2>Where things stand</h2>
|
|
182
|
+
<p>${model.outstandingTotal === 0
|
|
183
|
+
? 'Nothing is currently outstanding on this target.'
|
|
184
|
+
: `${model.outstandingTotal} issue${model.outstandingTotal === 1 ? '' : 's'} ${model.outstandingTotal === 1 ? 'is' : 'are'} currently outstanding, of which ${urgent} ${urgent === 1 ? 'is' : 'are'} critical or high.`}</p>
|
|
185
|
+
${notes.join('')}
|
|
186
|
+
<h2>Outstanding by severity</h2>
|
|
187
|
+
${rows === ''
|
|
188
|
+
? '<p class="none">Nothing outstanding.</p>'
|
|
189
|
+
: `<table><thead><tr><th>Severity</th><th>Outstanding</th></tr></thead><tbody>${rows}</tbody></table>`}
|
|
190
|
+
${notable.length === 0
|
|
191
|
+
? ''
|
|
192
|
+
: `<h2>What needs attention first</h2><ul>${notable
|
|
193
|
+
.map((e) => `<li><span class="${sevClass(e.issue.effectiveSeverity)}">${e.issue.effectiveSeverity}</span> — ${esc(e.issue.title)} (open ${e.daysOpen} days)</li>`)
|
|
194
|
+
.join('')}</ul><p class="lede">Full detail, including evidence, is in the accompanying technical report.</p>`}
|
|
195
|
+
<p>Exposure score ${model.exposureScore}. This is a single number combining how many issues are open, how severe they are and how long they have been open. It falls as issues are fixed and rises while they are not.</p>`;
|
|
196
|
+
}
|
|
197
|
+
/** The Attestation Letter body. */
|
|
198
|
+
function attestationBody(model) {
|
|
199
|
+
const { snapshot } = model;
|
|
200
|
+
const found = snapshot.issues.length + snapshot.suppressed.length;
|
|
201
|
+
const rows = SEVERITY_ORDER.map((sev) => `<tr><td class="${sevClass(sev)}">${sev}</td><td>${model.outstanding[sev]}</td></tr>`).join('');
|
|
202
|
+
return `<h2>Statement</h2>
|
|
203
|
+
<p>${esc(model.preparedBy ?? 'Secureport')} carried out security testing of <strong>${esc(snapshot.target.name)}</strong> (${esc(snapshot.target.url)}) on ${formatDate(snapshot.run.createdAt)}.</p>
|
|
204
|
+
<p>${esc(model.basis.statement)}</p>
|
|
205
|
+
<p>The testing identified ${found} issue${found === 1 ? '' : 's'} in total, of which ${model.outstandingTotal} ${model.outstandingTotal === 1 ? 'remains' : 'remain'} outstanding at the date of this letter:</p>
|
|
206
|
+
<table><thead><tr><th>Severity</th><th>Outstanding</th></tr></thead><tbody>${rows}</tbody></table>
|
|
207
|
+
<h2>What this letter does not establish</h2>
|
|
208
|
+
<ul>${model.limitations.map((l) => `<li>${esc(l)}</li>`).join('')}</ul>
|
|
209
|
+
<h2>Scope tested</h2>
|
|
210
|
+
<ul>${snapshot.run.coverage.paths.map((p) => `<li><code>${esc(p)}</code></li>`).join('')}</ul>`;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Renders a snapshot as a single self-contained HTML document.
|
|
214
|
+
*
|
|
215
|
+
* No external stylesheet, no script, no fonts to fetch: a report is a file that
|
|
216
|
+
* gets emailed, and one that needs a network to look right is one that
|
|
217
|
+
* eventually does not. The print rules ship here rather than in P7 because P7
|
|
218
|
+
* renders this same document through Chromium.
|
|
219
|
+
*
|
|
220
|
+
* Every value taken from the snapshot is escaped. A scanner's evidence routinely
|
|
221
|
+
* contains the payload that proved the weakness — `<script>alert(1)</script>` is
|
|
222
|
+
* a *normal* finding title — so a report that interpolates it unescaped attacks
|
|
223
|
+
* whoever opens it.
|
|
224
|
+
*
|
|
225
|
+
* @param snapshot - Issue state as of a run.
|
|
226
|
+
* @param options - Which report, attribution, and the render time.
|
|
227
|
+
* @returns A complete HTML document.
|
|
228
|
+
*/
|
|
229
|
+
export function renderHtml(snapshot, options) {
|
|
230
|
+
const model = buildReportModel(snapshot, options);
|
|
231
|
+
const { target } = snapshot;
|
|
232
|
+
const meta = [
|
|
233
|
+
`<tr><td>Generated</td><td>${formatDate(model.generatedAt)}</td></tr>`,
|
|
234
|
+
`<tr><td>Run</td><td>${esc(snapshot.run.runId)} (${esc(snapshot.run.trigger)})</td></tr>`,
|
|
235
|
+
model.preparedFor === undefined
|
|
236
|
+
? ''
|
|
237
|
+
: `<tr><td>Prepared for</td><td>${esc(model.preparedFor)}</td></tr>`,
|
|
238
|
+
model.preparedBy === undefined
|
|
239
|
+
? ''
|
|
240
|
+
: `<tr><td>Prepared by</td><td>${esc(model.preparedBy)}</td></tr>`,
|
|
241
|
+
].join('');
|
|
242
|
+
// The Attestation Letter carries the basis, the scope and the counts itself,
|
|
243
|
+
// in the order a letter reads. Adding the report furniture on top would state
|
|
244
|
+
// each of them twice, and a formal document that repeats itself is one a
|
|
245
|
+
// reader stops trusting.
|
|
246
|
+
const preamble = model.kind === 'attest'
|
|
247
|
+
? ''
|
|
248
|
+
: `<h2>Basis of testing</h2>
|
|
249
|
+
<p>${esc(model.basis.statement)}</p>
|
|
250
|
+
<h2>Scope</h2>
|
|
251
|
+
<p>This report covers only what the run exercised: ${snapshot.run.coverage.paths
|
|
252
|
+
.map((p) => `<code>${esc(p)}</code>`)
|
|
253
|
+
.join(', ')}. Anything outside that was not tested and is not described here.</p>
|
|
254
|
+
${changeSummary(model)}`;
|
|
255
|
+
let findings;
|
|
256
|
+
if (model.kind === 'exec') {
|
|
257
|
+
findings = executiveBody(model);
|
|
258
|
+
}
|
|
259
|
+
else if (model.kind === 'retest') {
|
|
260
|
+
findings = retestBody(model);
|
|
261
|
+
}
|
|
262
|
+
else if (model.kind === 'attest') {
|
|
263
|
+
findings = attestationBody(model);
|
|
264
|
+
}
|
|
265
|
+
else if (model.sections.length === 0) {
|
|
266
|
+
findings = '<h2>Findings</h2><p class="none">No issues were found.</p>';
|
|
267
|
+
}
|
|
268
|
+
else if (model.kind === 'pen') {
|
|
269
|
+
findings =
|
|
270
|
+
'<h2>Findings</h2>' +
|
|
271
|
+
model.sections
|
|
272
|
+
.map((section) => `<h3 class="${sevClass(section.severity)}">${section.severity} (${section.issues.length})</h3>` +
|
|
273
|
+
section.issues.map(issueDetail).join(''))
|
|
274
|
+
.join('');
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
const rows = model.sections
|
|
278
|
+
.flatMap((s) => s.issues)
|
|
279
|
+
.map((entry) => {
|
|
280
|
+
const { issue } = entry;
|
|
281
|
+
const location = issue.parameter === undefined ? issue.location : `${issue.location} (${issue.parameter})`;
|
|
282
|
+
return `<tr><td>${esc(issue.title)}</td><td class="${sevClass(issue.effectiveSeverity)}">${issue.effectiveSeverity}</td><td><code>${esc(location)}</code></td><td>${entry.change.replace('_', ' ')}</td><td>${entry.daysOpen}</td><td${entry.slaStatus === 'breached' ? ' class="breached"' : ''}>${entry.slaStatus.replace('_', ' ')}</td></tr>`;
|
|
283
|
+
})
|
|
284
|
+
.join('');
|
|
285
|
+
findings = `<h2>Findings</h2><table><thead><tr><th>Issue</th><th>Severity</th><th>Location</th><th>Change</th><th>Days open</th><th>Remediation</th></tr></thead><tbody>${rows}</tbody></table>`;
|
|
286
|
+
}
|
|
287
|
+
const resolved = model.kind === 'exec' ||
|
|
288
|
+
model.kind === 'attest' ||
|
|
289
|
+
model.kind === 'retest' ||
|
|
290
|
+
model.resolved.length === 0
|
|
291
|
+
? ''
|
|
292
|
+
: `<h2>Resolved since the last run</h2>
|
|
293
|
+
<p class="lede">${model.resolved.length} issue${model.resolved.length === 1 ? '' : 's'} no longer detected by a run that covered ${model.resolved.length === 1 ? 'it' : 'them'}.</p>
|
|
294
|
+
<table><thead><tr><th>Issue</th><th>Severity</th><th>Location</th><th>Open for</th></tr></thead><tbody>${model.resolved
|
|
295
|
+
.map((entry) => `<tr><td>${esc(entry.issue.title)}</td><td class="${sevClass(entry.issue.effectiveSeverity)}">${entry.issue.effectiveSeverity}</td><td><code>${esc(entry.issue.location)}</code></td><td>${entry.daysOpen} days</td></tr>`)
|
|
296
|
+
.join('')}</tbody></table>`;
|
|
297
|
+
const suppressed = snapshot.suppressed.length === 0
|
|
298
|
+
? '<h2>Suppressed findings</h2><p class="none">None.</p>'
|
|
299
|
+
: `<h2>Suppressed findings</h2>
|
|
300
|
+
<p class="lede">Accepted, out of scope or dismissed. Listed because an accepted risk nobody can see is not an accepted risk.</p>
|
|
301
|
+
<table><thead><tr><th>Issue</th><th>Severity when suppressed</th><th>Reason</th><th>By</th><th>Expires</th><th>Justification</th></tr></thead><tbody>${snapshot.suppressed
|
|
302
|
+
.map((s) => `<tr><td>${esc(s.issue.title)}</td><td class="${sevClass(s.severityAtIgnore)}">${s.severityAtIgnore}</td><td>${s.reason.replace('_', ' ')}</td><td>${esc(s.ignoredBy)}</td><td>${s.expiresAt === undefined ? 'never' : formatDate(s.expiresAt)}</td><td>${esc(s.comment)}</td></tr>`)
|
|
303
|
+
.join('')}</tbody></table>`;
|
|
304
|
+
return `<!doctype html>
|
|
305
|
+
<html lang="en">
|
|
306
|
+
<head>
|
|
307
|
+
<meta charset="utf-8">
|
|
308
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
309
|
+
<title>${esc(model.title)} — ${esc(target.name)}</title>
|
|
310
|
+
<style>${STYLES}</style>
|
|
311
|
+
</head>
|
|
312
|
+
<body>
|
|
313
|
+
<h1>${esc(model.title)}</h1>
|
|
314
|
+
<p class="lede"><strong>${esc(target.name)}</strong> — ${esc(target.url)}</p>
|
|
315
|
+
<table class="meta"><tbody>${meta}</tbody></table>
|
|
316
|
+
${preamble}
|
|
317
|
+
${findings}
|
|
318
|
+
${resolved}
|
|
319
|
+
${suppressed}
|
|
320
|
+
<footer>Generated by Secureport from run ${esc(snapshot.run.runId)}. Issue state as of ${formatDate(snapshot.run.createdAt)}.</footer>
|
|
321
|
+
</body>
|
|
322
|
+
</html>`;
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/report/html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Snapshot } from '../snapshot.js';
|
|
2
|
+
import { type ReportKind, type ReportOptions, type RetestVerdict } from './model.js';
|
|
3
|
+
/**
|
|
4
|
+
* The machine-readable report.
|
|
5
|
+
*
|
|
6
|
+
* A stable, documented shape rather than the raw snapshot: the snapshot is an
|
|
7
|
+
* internal contract that will grow, and a consumer building a dashboard on a
|
|
8
|
+
* report should not have to track that. It carries the derived answers — what
|
|
9
|
+
* changed, what is outstanding, how the findings were produced — beside the
|
|
10
|
+
* snapshot itself, so a consumer can use either.
|
|
11
|
+
*/
|
|
12
|
+
export interface JsonReport {
|
|
13
|
+
/** Format version. Bumped when the shape changes incompatibly. */
|
|
14
|
+
readonly reportVersion: 1;
|
|
15
|
+
/** Which report this is. */
|
|
16
|
+
readonly kind: ReportKind;
|
|
17
|
+
/** The title as printed. */
|
|
18
|
+
readonly title: string;
|
|
19
|
+
/** When it was rendered, ISO 8601. */
|
|
20
|
+
readonly generatedAt: string;
|
|
21
|
+
/** Who prepared it, if stated. */
|
|
22
|
+
readonly preparedBy?: string;
|
|
23
|
+
/** Who it is for, if stated. */
|
|
24
|
+
readonly preparedFor?: string;
|
|
25
|
+
/**
|
|
26
|
+
* How the findings were produced.
|
|
27
|
+
*
|
|
28
|
+
* Present in the JSON as well as the prose because a consumer that renders
|
|
29
|
+
* its own view must not be able to present automated results as manual
|
|
30
|
+
* testing simply by not reading the sentence.
|
|
31
|
+
*/
|
|
32
|
+
readonly basis: {
|
|
33
|
+
readonly automated: boolean;
|
|
34
|
+
readonly uploaded: boolean;
|
|
35
|
+
readonly includesManual: boolean;
|
|
36
|
+
readonly engines: readonly string[];
|
|
37
|
+
readonly statement: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* What the report does not establish.
|
|
41
|
+
*
|
|
42
|
+
* Here for the same reason as {@link JsonReport.basis}: a consumer rendering
|
|
43
|
+
* its own view must not be able to drop the limits by not reading the prose.
|
|
44
|
+
*/
|
|
45
|
+
readonly limitations: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* What this run established about the issues carried into it.
|
|
48
|
+
*
|
|
49
|
+
* Present whenever the snapshot has a baseline. Verdicts are keyed by issue
|
|
50
|
+
* id rather than repeating the issues, which are already in the snapshot.
|
|
51
|
+
*/
|
|
52
|
+
readonly retest?: {
|
|
53
|
+
readonly baselineRunId: string;
|
|
54
|
+
readonly counts: Readonly<Record<RetestVerdict, number>>;
|
|
55
|
+
readonly fixRate: number | null;
|
|
56
|
+
readonly verdicts: Readonly<Record<string, RetestVerdict>>;
|
|
57
|
+
};
|
|
58
|
+
/** Open and regressed issues by severity, and their total. */
|
|
59
|
+
readonly outstanding: Readonly<Record<string, number>> & {
|
|
60
|
+
readonly total: number;
|
|
61
|
+
};
|
|
62
|
+
/** Exposure at the end of the run. */
|
|
63
|
+
readonly exposureScore: number;
|
|
64
|
+
/** Issues past their remediation deadline. */
|
|
65
|
+
readonly breached: number;
|
|
66
|
+
/** The snapshot this was rendered from. */
|
|
67
|
+
readonly snapshot: Snapshot;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Renders a snapshot as JSON.
|
|
71
|
+
*
|
|
72
|
+
* Deterministic and key-ordered, so two renders of one snapshot are
|
|
73
|
+
* byte-identical and a diff between two reports shows only what actually
|
|
74
|
+
* changed.
|
|
75
|
+
*
|
|
76
|
+
* @param snapshot - Issue state as of a run.
|
|
77
|
+
* @param options - Which report, attribution, and the render time.
|
|
78
|
+
* @returns The report as a JSON string.
|
|
79
|
+
*/
|
|
80
|
+
export declare function renderJson(snapshot: Snapshot, options: ReportOptions): string;
|
|
81
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/report/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAE1B,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAE1B,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,kCAAkC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B,gCAAgC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;QAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;QACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;IAEF;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAExC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;QACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;KAC5D,CAAC;IAEF,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAEpF,sCAAsC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAqC7E"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { buildReportModel, } from './model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Renders a snapshot as JSON.
|
|
4
|
+
*
|
|
5
|
+
* Deterministic and key-ordered, so two renders of one snapshot are
|
|
6
|
+
* byte-identical and a diff between two reports shows only what actually
|
|
7
|
+
* changed.
|
|
8
|
+
*
|
|
9
|
+
* @param snapshot - Issue state as of a run.
|
|
10
|
+
* @param options - Which report, attribution, and the render time.
|
|
11
|
+
* @returns The report as a JSON string.
|
|
12
|
+
*/
|
|
13
|
+
export function renderJson(snapshot, options) {
|
|
14
|
+
const model = buildReportModel(snapshot, options);
|
|
15
|
+
const report = {
|
|
16
|
+
reportVersion: 1,
|
|
17
|
+
kind: model.kind,
|
|
18
|
+
title: model.title,
|
|
19
|
+
generatedAt: model.generatedAt.toISOString(),
|
|
20
|
+
...(model.preparedBy === undefined ? {} : { preparedBy: model.preparedBy }),
|
|
21
|
+
...(model.preparedFor === undefined ? {} : { preparedFor: model.preparedFor }),
|
|
22
|
+
basis: {
|
|
23
|
+
automated: model.basis.automated,
|
|
24
|
+
uploaded: model.basis.uploaded,
|
|
25
|
+
includesManual: model.basis.includesManual,
|
|
26
|
+
engines: model.basis.engines,
|
|
27
|
+
statement: model.basis.statement,
|
|
28
|
+
},
|
|
29
|
+
limitations: model.limitations,
|
|
30
|
+
...(model.retest === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: {
|
|
33
|
+
retest: {
|
|
34
|
+
baselineRunId: model.retest.baseline.runId,
|
|
35
|
+
counts: model.retest.counts,
|
|
36
|
+
fixRate: model.retest.fixRate,
|
|
37
|
+
verdicts: Object.fromEntries(model.retest.entries.map((e) => [e.issue.issue.id, e.verdict])),
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
outstanding: { ...model.outstanding, total: model.outstandingTotal },
|
|
41
|
+
exposureScore: model.exposureScore,
|
|
42
|
+
breached: model.breached,
|
|
43
|
+
snapshot,
|
|
44
|
+
};
|
|
45
|
+
return JSON.stringify(report, null, 2);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/report/json.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,GAIjB,MAAM,YAAY,CAAC;AA+EpB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,QAAkB,EAAE,OAAsB;IACnE,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAElD,MAAM,MAAM,GAAe;QACzB,aAAa,EAAE,CAAC;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE;QAC5C,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC;QAC3E,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9E,KAAK,EAAE;YACL,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS;YAChC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ;YAC9B,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,cAAc;YAC1C,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO;YAC5B,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS;SACjC;QACD,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS;YAC5B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,MAAM,EAAE;oBACN,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK;oBAC1C,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;oBAC3B,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;oBAC7B,QAAQ,EAAE,MAAM,CAAC,WAAW,CAC1B,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAC/D;iBACF;aACF,CAAC;QACN,WAAW,EAAE,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,gBAAgB,EAAE;QACpE,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ;KACT,CAAC;IAEF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snapshot } from '../snapshot.js';
|
|
2
|
+
import { type ReportOptions } from './model.js';
|
|
3
|
+
/** A date, printed the same way everywhere, without a locale to disagree about. */
|
|
4
|
+
export declare function formatDate(date: Date): string;
|
|
5
|
+
/**
|
|
6
|
+
* Renders a snapshot as Markdown.
|
|
7
|
+
*
|
|
8
|
+
* The Penetration Test report gives every issue in full with its evidence; the
|
|
9
|
+
* Vulnerability Assessment tabulates the same issues; the Executive Summary
|
|
10
|
+
* says what changed and what it means without technical detail; the Attestation
|
|
11
|
+
* Letter states formally that testing was carried out, and states what it does
|
|
12
|
+
* not establish as prominently as what it found. **They are presentations of one
|
|
13
|
+
* snapshot** — any difference in substance would be a bug.
|
|
14
|
+
*
|
|
15
|
+
* Deterministic: the same snapshot and the same `now` produce byte-identical
|
|
16
|
+
* output, which is what lets a report be compared with the copy somebody was
|
|
17
|
+
* sent, and what makes golden-file testing meaningful.
|
|
18
|
+
*
|
|
19
|
+
* @param snapshot - Issue state as of a run.
|
|
20
|
+
* @param options - Which report, attribution, and the render time.
|
|
21
|
+
* @returns The report as Markdown.
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderMarkdown(snapshot: Snapshot, options: ReportOptions): string;
|
|
24
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/report/markdown.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,YAAY,CAAC;AAEpB,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE7C;AA0TD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CA0FjF"}
|