@secureport/core 0.2.1 → 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 +146 -33
- package/dist/coverage.d.ts +21 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +65 -0
- package/dist/coverage.js.map +1 -0
- package/dist/finding.d.ts +89 -0
- package/dist/finding.d.ts.map +1 -0
- package/dist/finding.js +2 -0
- package/dist/finding.js.map +1 -0
- package/dist/fingerprint.d.ts +185 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +247 -0
- package/dist/fingerprint.js.map +1 -0
- 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/nuclei.d.ts +39 -0
- package/dist/import/nuclei.d.ts.map +1 -0
- package/dist/import/nuclei.js +115 -0
- package/dist/import/nuclei.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 +26 -0
- package/dist/import/zap.d.ts.map +1 -0
- package/dist/import/zap.js +119 -0
- package/dist/import/zap.js.map +1 -0
- package/dist/index.d.ts +41 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -2
- package/dist/index.js.map +1 -1
- package/dist/issue.d.ts +190 -11
- package/dist/issue.d.ts.map +1 -1
- package/dist/reconcile.d.ts +115 -10
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/reconcile.js +306 -12
- package/dist/reconcile.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/run.d.ts +134 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +2 -0
- package/dist/run.js.map +1 -0
- package/dist/severity.d.ts +191 -0
- package/dist/severity.d.ts.map +1 -0
- package/dist/severity.js +171 -0
- package/dist/severity.js.map +1 -0
- package/dist/snapshot-builder.d.ts +78 -0
- package/dist/snapshot-builder.d.ts.map +1 -0
- package/dist/snapshot-builder.js +172 -0
- package/dist/snapshot-builder.js.map +1 -0
- package/dist/snapshot.d.ts +125 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +2 -0
- package/dist/snapshot.js.map +1 -0
- package/package.json +5 -4
- package/src/coverage.ts +65 -0
- package/src/finding.ts +112 -0
- package/src/fingerprint.ts +315 -0
- package/src/import/burp.ts +126 -0
- package/src/import/generic.ts +258 -0
- package/src/import/nessus.ts +136 -0
- package/src/import/nuclei.ts +173 -0
- package/src/import/xml.ts +187 -0
- package/src/import/zap.ts +161 -0
- package/src/index.ts +75 -2
- package/src/issue.ts +244 -11
- package/src/reconcile.ts +421 -17
- 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/run.ts +163 -0
- package/src/severity.ts +250 -0
- package/src/snapshot-builder.ts +225 -0
- package/src/snapshot.ts +146 -0
package/dist/reconcile.js
CHANGED
|
@@ -1,23 +1,317 @@
|
|
|
1
|
+
import { DEFAULT_SLA_POLICY, SEVERITY_ORDER, SEVERITY_WEIGHTS, severityRank, slaDueAt, } from './severity.js';
|
|
2
|
+
import { coversLocation } from './coverage.js';
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
* matches an open issue is folded into it; anything new opens an issue.
|
|
4
|
+
* The key an issue and a finding must share to be the same issue.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* **The fingerprint version is part of it, not decoration.** A fingerprint means
|
|
7
|
+
* nothing without the algorithm that produced it, so an `fp_v1` finding must
|
|
8
|
+
* never match an `fp_v2` issue — reconciling across versions would silently
|
|
9
|
+
* merge two things that were never computed the same way. Crossing a version is
|
|
10
|
+
* what a re-fingerprint migration is for.
|
|
11
|
+
*
|
|
12
|
+
* The separator is an escape rather than a literal control character, so the
|
|
13
|
+
* source stays plain text: an invisible NUL here once passed prettier, eslint,
|
|
14
|
+
* tsc and the whole test suite, and was only caught because `grep` went quiet
|
|
15
|
+
* on a file it could no longer read.
|
|
7
16
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
17
|
+
function issueKey(of) {
|
|
18
|
+
return `${of.fingerprintVersion}\u0000${of.fingerprint}`;
|
|
19
|
+
}
|
|
20
|
+
/** Groups this run's findings by the issue they belong to. */
|
|
21
|
+
function groupByFingerprint(findings) {
|
|
22
|
+
const groups = new Map();
|
|
10
23
|
for (const finding of findings) {
|
|
11
|
-
|
|
24
|
+
// The version is part of the key. A fingerprint means nothing without the
|
|
25
|
+
// algorithm that produced it, so a `fp_v1` finding must never match a
|
|
26
|
+
// `fp_v2` issue — that is what a re-fingerprint migration is for.
|
|
27
|
+
const key = issueKey(finding);
|
|
28
|
+
const existing = groups.get(key);
|
|
29
|
+
if (existing)
|
|
30
|
+
existing.push(finding);
|
|
31
|
+
else
|
|
32
|
+
groups.set(key, [finding]);
|
|
33
|
+
}
|
|
34
|
+
return groups;
|
|
35
|
+
}
|
|
36
|
+
/** The most urgent severity among a group of findings for one issue. */
|
|
37
|
+
function highestSeverity(findings) {
|
|
38
|
+
return findings.reduce((worst, candidate) => severityRank(candidate.detectedSeverity) > severityRank(worst.detectedSeverity)
|
|
39
|
+
? candidate
|
|
40
|
+
: worst);
|
|
41
|
+
}
|
|
42
|
+
/** The default flapping guard for a run kind. */
|
|
43
|
+
function defaultThreshold(kind) {
|
|
44
|
+
// An upload asserts a complete result, so one miss is enough. A scan can miss
|
|
45
|
+
// for reasons that say nothing about the issue.
|
|
46
|
+
return kind === 'upload' ? 1 : 2;
|
|
47
|
+
}
|
|
48
|
+
/** An all-zero count, so a report never has to decide what a missing key means. */
|
|
49
|
+
function emptyCounts() {
|
|
50
|
+
return Object.fromEntries(SEVERITY_ORDER.map((s) => [s, 0]));
|
|
51
|
+
}
|
|
52
|
+
/** Whole days between two moments, floored. */
|
|
53
|
+
function daysBetween(from, to) {
|
|
54
|
+
return Math.max(0, Math.floor((to.getTime() - from.getTime()) / 86_400_000));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Turns a run's findings into issue state.
|
|
58
|
+
*
|
|
59
|
+
* **The only writer of issue state, and a pure function.** Given the same
|
|
60
|
+
* issues, findings, run and clock it produces the same result every time. It
|
|
61
|
+
* performs no I/O, reads no clock and generates no randomness; the caller
|
|
62
|
+
* supplies `now` and `newId` and persists what comes back.
|
|
63
|
+
*
|
|
64
|
+
* This half handles everything a **present** finding causes:
|
|
65
|
+
*
|
|
66
|
+
* - a fingerprint that matches nothing opens an issue (`created`);
|
|
67
|
+
* - a fingerprint that matches attaches evidence and refreshes `lastSeen`,
|
|
68
|
+
* clearing the miss counter (`seen`);
|
|
69
|
+
* - a `resolved` issue seen again becomes `regressed` (`reopened`) — a returning
|
|
70
|
+
* problem is a different and more interesting event than a new one;
|
|
71
|
+
* - a changed detected severity updates the issue, and updates the *effective*
|
|
72
|
+
* severity and the SLA deadline only where nobody has overridden it
|
|
73
|
+
* (`severity_detected_changed`);
|
|
74
|
+
* - an `ignored` issue whose detected severity has risen above what was
|
|
75
|
+
* accepted comes back (`unignored`) — accepting the risk of a medium is not
|
|
76
|
+
* accepting the risk of the critical it turned out to be.
|
|
77
|
+
*
|
|
78
|
+
* And everything an **absent** finding causes:
|
|
79
|
+
*
|
|
80
|
+
* - an issue the run covered but did not see takes a miss, and resolves once it
|
|
81
|
+
* has missed enough consecutive covering runs (`resolved`);
|
|
82
|
+
* - an issue **outside** the run's coverage is untouched — not resolved, and not
|
|
83
|
+
* even counted as a miss, so a sequence of narrow scans cannot accumulate
|
|
84
|
+
* misses against a path none of them looked at (invariant 5);
|
|
85
|
+
* - a **manual-origin** issue never auto-resolves; a human closes what a human
|
|
86
|
+
* opened (invariant 6);
|
|
87
|
+
* - a suppression whose expiry has passed lapses (`unignored`), regardless of
|
|
88
|
+
* coverage — an ignore expiring is a decision timing out, not an observation.
|
|
89
|
+
*
|
|
90
|
+
* Finally it computes the {@link RunSummary}: counts by severity for new, still
|
|
91
|
+
* open, resolved, regressed and ignored, plus the exposure score. Ignored issues
|
|
92
|
+
* are excluded from every count but their own, and from exposure, and never from
|
|
93
|
+
* the suppressed appendix (invariant 7).
|
|
94
|
+
*
|
|
95
|
+
* `firstSeen` is never reset, by anything here (invariant 4).
|
|
96
|
+
*
|
|
97
|
+
* @param input - Issues, findings, the run, the clock and an id source.
|
|
98
|
+
* @returns Every issue for the target, plus the events that explain the changes.
|
|
99
|
+
*/
|
|
100
|
+
export function reconcile(input) {
|
|
101
|
+
const { issues, findings, run, now, newId } = input;
|
|
102
|
+
const actor = input.actor ?? 'reconciler';
|
|
103
|
+
const slaPolicy = input.slaPolicy ?? DEFAULT_SLA_POLICY;
|
|
104
|
+
const events = [];
|
|
105
|
+
const emit = (issueId, type, payload) => {
|
|
106
|
+
events.push({
|
|
107
|
+
id: newId(),
|
|
108
|
+
orgId: run.orgId,
|
|
109
|
+
issueId,
|
|
110
|
+
runId: run.id,
|
|
111
|
+
type,
|
|
112
|
+
actor,
|
|
113
|
+
...(payload === undefined ? {} : { payload }),
|
|
114
|
+
createdAt: now,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
const byKey = new Map(issues.map((i) => [issueKey(i), i]));
|
|
118
|
+
const updated = new Map();
|
|
119
|
+
for (const [key, group] of groupByFingerprint(findings)) {
|
|
120
|
+
const existing = byKey.get(key);
|
|
121
|
+
const worst = highestSeverity(group);
|
|
122
|
+
const findingIds = group.map((f) => f.id);
|
|
12
123
|
if (!existing) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
124
|
+
const id = newId();
|
|
125
|
+
const issue = {
|
|
126
|
+
id,
|
|
127
|
+
orgId: run.orgId,
|
|
128
|
+
targetId: run.targetId,
|
|
129
|
+
fingerprint: worst.fingerprint,
|
|
130
|
+
fingerprintVersion: worst.fingerprintVersion,
|
|
131
|
+
title: worst.title,
|
|
132
|
+
vulnKey: worst.vulnKey,
|
|
133
|
+
...(worst.cwe === undefined ? {} : { cwe: worst.cwe }),
|
|
134
|
+
location: worst.location,
|
|
135
|
+
...(worst.parameter === undefined ? {} : { parameter: worst.parameter }),
|
|
17
136
|
status: 'open',
|
|
137
|
+
detectedSeverity: worst.detectedSeverity,
|
|
138
|
+
effectiveSeverity: worst.detectedSeverity,
|
|
139
|
+
firstSeen: now,
|
|
140
|
+
lastSeen: now,
|
|
141
|
+
consecutiveMisses: 0,
|
|
142
|
+
origin: run.kind,
|
|
143
|
+
slaDueAt: slaDueAt(worst.detectedSeverity, now, slaPolicy),
|
|
144
|
+
};
|
|
145
|
+
updated.set(key, issue);
|
|
146
|
+
emit(id, 'created', { findingIds, severity: worst.detectedSeverity });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
// One `seen` per issue per run, not one per finding. `issue_events` is what
|
|
150
|
+
// every analytic counts, and a weakness two engines both detected would
|
|
151
|
+
// otherwise register as two sightings of one issue.
|
|
152
|
+
let issue = { ...existing, lastSeen: now, consecutiveMisses: 0 };
|
|
153
|
+
emit(issue.id, 'seen', { findingIds });
|
|
154
|
+
if (issue.status === 'resolved') {
|
|
155
|
+
issue = { ...issue, status: 'regressed', reopenedAt: now };
|
|
156
|
+
emit(issue.id, 'reopened', { resolvedAt: existing.resolvedAt?.toISOString() });
|
|
157
|
+
}
|
|
158
|
+
if (worst.detectedSeverity !== issue.detectedSeverity) {
|
|
159
|
+
const overridden = issue.severityOverriddenAt !== undefined;
|
|
160
|
+
const raisedAboveOverride = overridden && severityRank(worst.detectedSeverity) > severityRank(issue.effectiveSeverity);
|
|
161
|
+
emit(issue.id, 'severity_detected_changed', {
|
|
162
|
+
from: issue.detectedSeverity,
|
|
163
|
+
to: worst.detectedSeverity,
|
|
164
|
+
// "Flag for review" in the spec, which names no column to flag it in.
|
|
165
|
+
// Recorded on the event rather than invented as a field: the detected
|
|
166
|
+
// severity has risen above a human's override, and somebody should look.
|
|
167
|
+
needsReview: raisedAboveOverride,
|
|
168
|
+
});
|
|
169
|
+
issue = overridden
|
|
170
|
+
? { ...issue, detectedSeverity: worst.detectedSeverity }
|
|
171
|
+
: {
|
|
172
|
+
...issue,
|
|
173
|
+
detectedSeverity: worst.detectedSeverity,
|
|
174
|
+
effectiveSeverity: worst.detectedSeverity,
|
|
175
|
+
slaDueAt: slaDueAt(worst.detectedSeverity, issue.firstSeen, slaPolicy),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (issue.status === 'ignored' &&
|
|
179
|
+
issue.severityAtIgnore !== undefined &&
|
|
180
|
+
severityRank(worst.detectedSeverity) > severityRank(issue.severityAtIgnore)) {
|
|
181
|
+
emit(issue.id, 'unignored', {
|
|
182
|
+
reason: 'severity increased',
|
|
183
|
+
from: issue.severityAtIgnore,
|
|
184
|
+
to: worst.detectedSeverity,
|
|
18
185
|
});
|
|
186
|
+
issue = {
|
|
187
|
+
...issue,
|
|
188
|
+
status: 'open',
|
|
189
|
+
ignoreReason: undefined,
|
|
190
|
+
ignoreComment: undefined,
|
|
191
|
+
ignoreScope: undefined,
|
|
192
|
+
ignoreExpiresAt: undefined,
|
|
193
|
+
ignoredBy: undefined,
|
|
194
|
+
severityAtIgnore: undefined,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
updated.set(key, issue);
|
|
198
|
+
}
|
|
199
|
+
const created = new Set([...updated.keys()].filter((k) => !byKey.has(k)));
|
|
200
|
+
const regressedNow = new Set([...updated].filter(([k, i]) => !created.has(k) && i.status === 'regressed').map(([k]) => k));
|
|
201
|
+
const resolvedNow = new Set();
|
|
202
|
+
// ---- what an absent finding causes -------------------------------------
|
|
203
|
+
//
|
|
204
|
+
// A run resolves only what it could have found. An issue outside coverage is
|
|
205
|
+
// left entirely alone: not resolved, and not counted as a miss either, so a
|
|
206
|
+
// sequence of narrow scans cannot accumulate misses against a path none of
|
|
207
|
+
// them looked at.
|
|
208
|
+
const threshold = input.autoResolveThreshold ?? defaultThreshold(run.kind);
|
|
209
|
+
for (const original of issues) {
|
|
210
|
+
const key = issueKey(original);
|
|
211
|
+
if (updated.has(key))
|
|
212
|
+
continue; // seen this run
|
|
213
|
+
if (original.status !== 'open' && original.status !== 'regressed')
|
|
214
|
+
continue;
|
|
215
|
+
// A human closes what a human opened (invariant 6).
|
|
216
|
+
if (original.origin === 'manual')
|
|
217
|
+
continue;
|
|
218
|
+
if (!coversLocation(original.location, run.coverage))
|
|
219
|
+
continue;
|
|
220
|
+
const misses = original.consecutiveMisses + 1;
|
|
221
|
+
if (misses < threshold) {
|
|
222
|
+
updated.set(key, { ...original, consecutiveMisses: misses });
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
updated.set(key, {
|
|
226
|
+
...original,
|
|
227
|
+
consecutiveMisses: misses,
|
|
228
|
+
status: 'resolved',
|
|
229
|
+
resolvedAt: now,
|
|
230
|
+
});
|
|
231
|
+
resolvedNow.add(key);
|
|
232
|
+
emit(original.id, 'resolved', { consecutiveMisses: misses, threshold });
|
|
233
|
+
}
|
|
234
|
+
// ---- suppressions that have lapsed --------------------------------------
|
|
235
|
+
//
|
|
236
|
+
// Independent of coverage and of whether the issue was seen: an ignore
|
|
237
|
+
// expiring is a decision timing out, not an observation.
|
|
238
|
+
for (const original of issues) {
|
|
239
|
+
const key = issueKey(original);
|
|
240
|
+
const current = updated.get(key) ?? original;
|
|
241
|
+
if (current.status !== 'ignored')
|
|
242
|
+
continue;
|
|
243
|
+
if (current.ignoreExpiresAt === undefined)
|
|
244
|
+
continue;
|
|
245
|
+
if (current.ignoreExpiresAt.getTime() > now.getTime())
|
|
246
|
+
continue;
|
|
247
|
+
emit(current.id, 'unignored', {
|
|
248
|
+
reason: 'expired',
|
|
249
|
+
expiredAt: current.ignoreExpiresAt.toISOString(),
|
|
250
|
+
});
|
|
251
|
+
updated.set(key, {
|
|
252
|
+
...current,
|
|
253
|
+
status: 'open',
|
|
254
|
+
ignoreReason: undefined,
|
|
255
|
+
ignoreComment: undefined,
|
|
256
|
+
ignoreScope: undefined,
|
|
257
|
+
ignoreExpiresAt: undefined,
|
|
258
|
+
ignoredBy: undefined,
|
|
259
|
+
severityAtIgnore: undefined,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Untouched issues are returned unchanged, in their original order, so a
|
|
263
|
+
// caller can write the whole set back without diffing.
|
|
264
|
+
const result = issues.map((i) => updated.get(issueKey(i)) ?? i);
|
|
265
|
+
for (const [key, issue] of updated)
|
|
266
|
+
if (!byKey.has(key))
|
|
267
|
+
result.push(issue);
|
|
268
|
+
// ---- the summary --------------------------------------------------------
|
|
269
|
+
const counts = {
|
|
270
|
+
new: emptyCounts(),
|
|
271
|
+
stillOpen: emptyCounts(),
|
|
272
|
+
resolved: emptyCounts(),
|
|
273
|
+
regressed: emptyCounts(),
|
|
274
|
+
ignored: emptyCounts(),
|
|
275
|
+
};
|
|
276
|
+
let exposureScore = 0;
|
|
277
|
+
for (const issue of result) {
|
|
278
|
+
const key = issueKey(issue);
|
|
279
|
+
const severity = issue.effectiveSeverity;
|
|
280
|
+
if (issue.status === 'ignored') {
|
|
281
|
+
// Excluded from every other count and from exposure, never from the
|
|
282
|
+
// suppressed appendix (invariant 7).
|
|
283
|
+
counts.ignored[severity]++;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (created.has(key))
|
|
287
|
+
counts.new[severity]++;
|
|
288
|
+
else if (resolvedNow.has(key))
|
|
289
|
+
counts.resolved[severity]++;
|
|
290
|
+
else if (regressedNow.has(key))
|
|
291
|
+
counts.regressed[severity]++;
|
|
292
|
+
else if (updated.has(key))
|
|
293
|
+
counts.stillOpen[severity]++;
|
|
294
|
+
if (issue.status === 'open' || issue.status === 'regressed') {
|
|
295
|
+
exposureScore += SEVERITY_WEIGHTS[severity] * Math.min(daysBetween(issue.firstSeen, now), 90);
|
|
19
296
|
}
|
|
20
297
|
}
|
|
21
|
-
|
|
298
|
+
const summary = {
|
|
299
|
+
runId: run.id,
|
|
300
|
+
orgId: run.orgId,
|
|
301
|
+
targetId: run.targetId,
|
|
302
|
+
kind: run.kind,
|
|
303
|
+
trigger: run.trigger,
|
|
304
|
+
engines: run.coverage.engines,
|
|
305
|
+
coverage: run.coverage,
|
|
306
|
+
durationMs: input.durationMs ?? 0,
|
|
307
|
+
new: counts.new,
|
|
308
|
+
stillOpen: counts.stillOpen,
|
|
309
|
+
resolved: counts.resolved,
|
|
310
|
+
regressed: counts.regressed,
|
|
311
|
+
ignored: counts.ignored,
|
|
312
|
+
exposureScore,
|
|
313
|
+
createdAt: now,
|
|
314
|
+
};
|
|
315
|
+
return { issues: result, events, summary };
|
|
22
316
|
}
|
|
23
317
|
//# sourceMappingURL=reconcile.js.map
|
package/dist/reconcile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snapshot } from '../snapshot.js';
|
|
2
|
+
import { type ReportOptions } from './model.js';
|
|
3
|
+
/**
|
|
4
|
+
* Renders a snapshot as a single self-contained HTML document.
|
|
5
|
+
*
|
|
6
|
+
* No external stylesheet, no script, no fonts to fetch: a report is a file that
|
|
7
|
+
* gets emailed, and one that needs a network to look right is one that
|
|
8
|
+
* eventually does not. The print rules ship here rather than in P7 because P7
|
|
9
|
+
* renders this same document through Chromium.
|
|
10
|
+
*
|
|
11
|
+
* Every value taken from the snapshot is escaped. A scanner's evidence routinely
|
|
12
|
+
* contains the payload that proved the weakness — `<script>alert(1)</script>` is
|
|
13
|
+
* a *normal* finding title — so a report that interpolates it unescaped attacks
|
|
14
|
+
* whoever opens it.
|
|
15
|
+
*
|
|
16
|
+
* @param snapshot - Issue state as of a run.
|
|
17
|
+
* @param options - Which report, attribution, and the render time.
|
|
18
|
+
* @returns A complete HTML document.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderHtml(snapshot: Snapshot, options: ReportOptions): string;
|
|
21
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/report/html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,YAAY,CAAC;AA4SpB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CA4H7E"}
|