@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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { SEVERITY_ORDER } from '../severity.js';
|
|
2
|
+
/** Works out what a run established about the issues carried into it. */
|
|
3
|
+
function describeRetest(snapshot) {
|
|
4
|
+
const { baseline } = snapshot;
|
|
5
|
+
if (baseline === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
const entries = snapshot.issues.map((issue) => ({
|
|
8
|
+
issue,
|
|
9
|
+
verdict: verdictFor(issue),
|
|
10
|
+
}));
|
|
11
|
+
// `SEVERITY_ORDER` rather than `severityRank`, which counts the other way:
|
|
12
|
+
// it returns 4 for `critical` so that a larger number is a worse problem.
|
|
13
|
+
// Sorting on it ascending put the advisories at the top of the table, which
|
|
14
|
+
// is how this shipped until a golden file showed it.
|
|
15
|
+
entries.sort((a, b) => {
|
|
16
|
+
const bySeverity = SEVERITY_ORDER.indexOf(a.issue.issue.effectiveSeverity) -
|
|
17
|
+
SEVERITY_ORDER.indexOf(b.issue.issue.effectiveSeverity);
|
|
18
|
+
return bySeverity === 0
|
|
19
|
+
? b.issue.issue.lastSeen.getTime() - a.issue.issue.lastSeen.getTime()
|
|
20
|
+
: bySeverity;
|
|
21
|
+
});
|
|
22
|
+
const counts = {
|
|
23
|
+
fixed: 0,
|
|
24
|
+
still_present: 0,
|
|
25
|
+
returned: 0,
|
|
26
|
+
not_retested: 0,
|
|
27
|
+
new: 0,
|
|
28
|
+
};
|
|
29
|
+
for (const entry of entries)
|
|
30
|
+
counts[entry.verdict]++;
|
|
31
|
+
const checkable = counts.fixed + counts.still_present + counts.returned;
|
|
32
|
+
return {
|
|
33
|
+
baseline,
|
|
34
|
+
entries,
|
|
35
|
+
counts,
|
|
36
|
+
fixRate: checkable === 0 ? null : counts.fixed / checkable,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The verdict for one issue.
|
|
41
|
+
*
|
|
42
|
+
* Turns on whether this run produced evidence for it, not on its status alone:
|
|
43
|
+
* an issue left open because the run never covered it looks identical to one
|
|
44
|
+
* left open because the run found it again, and telling a reader those are the
|
|
45
|
+
* same thing is the failure this report exists to avoid.
|
|
46
|
+
*/
|
|
47
|
+
function verdictFor(entry) {
|
|
48
|
+
if (entry.change === 'resolved')
|
|
49
|
+
return 'fixed';
|
|
50
|
+
if (entry.change === 'regressed')
|
|
51
|
+
return 'returned';
|
|
52
|
+
if (entry.change === 'new')
|
|
53
|
+
return 'new';
|
|
54
|
+
return entry.findings.length > 0 ? 'still_present' : 'not_retested';
|
|
55
|
+
}
|
|
56
|
+
/** Works out what a report cannot establish, from the run rather than a template. */
|
|
57
|
+
function describeLimitations(snapshot, basis) {
|
|
58
|
+
const limitations = [];
|
|
59
|
+
if (!basis.includesManual) {
|
|
60
|
+
limitations.push('This is evidence of automated security testing. It is not a human-led penetration ' +
|
|
61
|
+
'test and does not carry the assurance of one.');
|
|
62
|
+
}
|
|
63
|
+
limitations.push('Findings should be verified by a qualified technician before this document is relied ' +
|
|
64
|
+
'upon as audit evidence.');
|
|
65
|
+
limitations.push(`Testing covered only ${snapshot.run.coverage.paths.join(', ')}. Anything outside that ` +
|
|
66
|
+
'was not examined, and its absence from this document is not evidence that it is sound.');
|
|
67
|
+
limitations.push(`This reflects the state of the target as of ${snapshot.run.createdAt
|
|
68
|
+
.toISOString()
|
|
69
|
+
.slice(0, 10)}. It says nothing about the target before or after that date.`);
|
|
70
|
+
limitations.push('Automated testing cannot establish the absence of a vulnerability. A clean result means ' +
|
|
71
|
+
'nothing was detected, not that nothing is there.');
|
|
72
|
+
if (snapshot.suppressed.length > 0) {
|
|
73
|
+
limitations.push(`${snapshot.suppressed.length} finding${snapshot.suppressed.length === 1 ? ' has' : 's have'} been ` +
|
|
74
|
+
'suppressed and excluded from the counts above. They are listed in full in the appendix.');
|
|
75
|
+
}
|
|
76
|
+
limitations.push('This document does not certify compliance with any standard, framework or regulation.');
|
|
77
|
+
return limitations;
|
|
78
|
+
}
|
|
79
|
+
const DEFAULT_TITLES = Object.freeze({
|
|
80
|
+
pen: 'Penetration Test Report',
|
|
81
|
+
vap: 'Vulnerability Assessment Report',
|
|
82
|
+
exec: 'Executive Summary',
|
|
83
|
+
attest: 'Attestation of Security Testing',
|
|
84
|
+
retest: 'Retest Report',
|
|
85
|
+
});
|
|
86
|
+
/** Works out how the findings were produced, from the run rather than a claim. */
|
|
87
|
+
function describeBasis(snapshot) {
|
|
88
|
+
const kind = snapshot.run.kind;
|
|
89
|
+
const engines = snapshot.run.engines;
|
|
90
|
+
const includesManual = snapshot.issues.some((i) => i.issue.origin === 'manual');
|
|
91
|
+
const automated = kind === 'scan';
|
|
92
|
+
const uploaded = kind === 'upload';
|
|
93
|
+
const parts = [];
|
|
94
|
+
if (automated) {
|
|
95
|
+
parts.push('Findings were produced by automated vulnerability scanning against the scope described below.');
|
|
96
|
+
}
|
|
97
|
+
if (uploaded) {
|
|
98
|
+
parts.push('Findings were imported from an external testing tool and reconciled against previous runs.');
|
|
99
|
+
}
|
|
100
|
+
if (kind === 'manual')
|
|
101
|
+
parts.push('Findings were recorded manually by a tester.');
|
|
102
|
+
if (includesManual && kind !== 'manual') {
|
|
103
|
+
parts.push('Some findings were recorded manually by a tester.');
|
|
104
|
+
}
|
|
105
|
+
if (!includesManual && kind !== 'manual') {
|
|
106
|
+
parts.push('No part of this assessment constitutes a manual penetration test.');
|
|
107
|
+
}
|
|
108
|
+
if (automated || uploaded) {
|
|
109
|
+
parts.push('Automated findings should be verified by a qualified technician before this report is ' +
|
|
110
|
+
'submitted as audit evidence.');
|
|
111
|
+
}
|
|
112
|
+
return { includesManual, automated, uploaded, engines, statement: parts.join(' ') };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Derives everything a report shows, once, from a snapshot.
|
|
116
|
+
*
|
|
117
|
+
* Both renderers consume this rather than the snapshot, so a number in the
|
|
118
|
+
* Markdown and the same number in the HTML cannot drift apart — they are the
|
|
119
|
+
* same value formatted twice.
|
|
120
|
+
*
|
|
121
|
+
* Suppressed issues are already separated by the snapshot and stay separated
|
|
122
|
+
* here: they never reach {@link ReportModel.sections}, are excluded from
|
|
123
|
+
* {@link ReportModel.outstanding} and from the exposure score, and appear only
|
|
124
|
+
* in the suppressed appendix (invariant 7).
|
|
125
|
+
*
|
|
126
|
+
* @param snapshot - Issue state as of a run.
|
|
127
|
+
* @param options - Title, attribution, and the render time.
|
|
128
|
+
* @returns The model both renderers format.
|
|
129
|
+
*/
|
|
130
|
+
export function buildReportModel(snapshot, options) {
|
|
131
|
+
const outstanding = {
|
|
132
|
+
critical: 0,
|
|
133
|
+
high: 0,
|
|
134
|
+
medium: 0,
|
|
135
|
+
low: 0,
|
|
136
|
+
advisory: 0,
|
|
137
|
+
};
|
|
138
|
+
const bySeverity = new Map();
|
|
139
|
+
const resolved = [];
|
|
140
|
+
let breached = 0;
|
|
141
|
+
for (const entry of snapshot.issues) {
|
|
142
|
+
if (entry.issue.status === 'resolved') {
|
|
143
|
+
resolved.push(entry);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const severity = entry.issue.effectiveSeverity;
|
|
147
|
+
const group = bySeverity.get(severity);
|
|
148
|
+
if (group)
|
|
149
|
+
group.push(entry);
|
|
150
|
+
else
|
|
151
|
+
bySeverity.set(severity, [entry]);
|
|
152
|
+
if (entry.issue.status === 'open' || entry.issue.status === 'regressed') {
|
|
153
|
+
outstanding[severity]++;
|
|
154
|
+
if (entry.slaStatus === 'breached')
|
|
155
|
+
breached++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
resolved.sort((a, b) => b.issue.lastSeen.getTime() - a.issue.lastSeen.getTime());
|
|
159
|
+
const sections = [];
|
|
160
|
+
for (const severity of SEVERITY_ORDER) {
|
|
161
|
+
const issues = bySeverity.get(severity);
|
|
162
|
+
if (issues === undefined || issues.length === 0)
|
|
163
|
+
continue;
|
|
164
|
+
sections.push({
|
|
165
|
+
severity,
|
|
166
|
+
// Most recently seen first: what a reader wants at the top of a section
|
|
167
|
+
// is what the latest run actually found.
|
|
168
|
+
issues: [...issues].sort((a, b) => b.issue.lastSeen.getTime() - a.issue.lastSeen.getTime()),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const outstandingTotal = SEVERITY_ORDER.reduce((sum, s) => sum + outstanding[s], 0);
|
|
172
|
+
const basis = describeBasis(snapshot);
|
|
173
|
+
const retest = describeRetest(snapshot);
|
|
174
|
+
// Rendering a retest of nothing is the over-claim §7 warns about: the
|
|
175
|
+
// document's whole content is a comparison, and one run does not have one.
|
|
176
|
+
if (options.kind === 'retest' && retest === undefined) {
|
|
177
|
+
throw new TypeError('a retest report needs a baseline: build the snapshot with `previous` set to the run being retested');
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
kind: options.kind,
|
|
181
|
+
title: options.title ?? DEFAULT_TITLES[options.kind],
|
|
182
|
+
snapshot,
|
|
183
|
+
generatedAt: options.now,
|
|
184
|
+
...(options.preparedBy === undefined ? {} : { preparedBy: options.preparedBy }),
|
|
185
|
+
...(options.preparedFor === undefined ? {} : { preparedFor: options.preparedFor }),
|
|
186
|
+
basis,
|
|
187
|
+
limitations: describeLimitations(snapshot, basis),
|
|
188
|
+
sections,
|
|
189
|
+
resolved,
|
|
190
|
+
outstanding,
|
|
191
|
+
outstandingTotal,
|
|
192
|
+
exposureScore: snapshot.run.exposureScore,
|
|
193
|
+
breached,
|
|
194
|
+
...(retest === undefined ? {} : { retest }),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/report/model.ts"],"names":[],"mappings":"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"}
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Severity } from './severity.js';
|
|
2
|
+
/**
|
|
3
|
+
* What kind of run this was.
|
|
4
|
+
*
|
|
5
|
+
* - `scan` — Secureport originated the traffic.
|
|
6
|
+
* - `upload` — someone brought results from elsewhere.
|
|
7
|
+
* - `manual` — a human recorded a finding directly.
|
|
8
|
+
*
|
|
9
|
+
* **An upload is first-class, not a lesser scan.** Much of the value is in
|
|
10
|
+
* tracking results a customer already has, and a model that treated uploads as
|
|
11
|
+
* second-class would make that path feel second-class too.
|
|
12
|
+
*/
|
|
13
|
+
export type RunKind = 'scan' | 'upload' | 'manual';
|
|
14
|
+
/**
|
|
15
|
+
* What set the run going.
|
|
16
|
+
*
|
|
17
|
+
* Recorded on every run from the first line of code, because it is what lets
|
|
18
|
+
* you say "the Action ran 340 times this quarter" — the sentence that shows
|
|
19
|
+
* automation is actually being used rather than merely installed.
|
|
20
|
+
*/
|
|
21
|
+
export type RunTrigger = 'github_action' | 'cli' | 'api' | 'scheduled' | 'web';
|
|
22
|
+
/**
|
|
23
|
+
* What a run actually exercised.
|
|
24
|
+
*
|
|
25
|
+
* The reason auto-resolution is safe. **A run only resolves what it could have
|
|
26
|
+
* found** (invariant 5): a quick profile that skipped `/admin` must not close
|
|
27
|
+
* an `/admin` issue, because not looking is not the same as not finding.
|
|
28
|
+
*
|
|
29
|
+
* Scan runs record what they genuinely reached. Upload runs take a declared
|
|
30
|
+
* scope, defaulting to the whole target — the person uploading is asserting
|
|
31
|
+
* what their results cover.
|
|
32
|
+
*/
|
|
33
|
+
export interface Coverage {
|
|
34
|
+
/**
|
|
35
|
+
* Location patterns the run covered, as globs, e.g.
|
|
36
|
+
* `https://app.example.com/**`.
|
|
37
|
+
*
|
|
38
|
+
* An issue whose location matches none of these is untouched by this run,
|
|
39
|
+
* whatever else happened.
|
|
40
|
+
*/
|
|
41
|
+
readonly paths: readonly string[];
|
|
42
|
+
/** Ports exercised, where the run was port-aware. */
|
|
43
|
+
readonly ports?: readonly number[];
|
|
44
|
+
/** Engines that took part. */
|
|
45
|
+
readonly engines: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One execution against a target.
|
|
49
|
+
*/
|
|
50
|
+
export interface Run {
|
|
51
|
+
/** Unique id. */
|
|
52
|
+
readonly id: string;
|
|
53
|
+
/** Organisation this run belongs to. */
|
|
54
|
+
readonly orgId: string;
|
|
55
|
+
/** Target it ran against. */
|
|
56
|
+
readonly targetId: string;
|
|
57
|
+
/** What kind of run it was. */
|
|
58
|
+
readonly kind: RunKind;
|
|
59
|
+
/** What set it going. */
|
|
60
|
+
readonly trigger: RunTrigger;
|
|
61
|
+
/** What it exercised. */
|
|
62
|
+
readonly coverage: Coverage;
|
|
63
|
+
/**
|
|
64
|
+
* The verification this run relied on, for runs that originate traffic.
|
|
65
|
+
*
|
|
66
|
+
* Recorded per run rather than per organisation, because verification
|
|
67
|
+
* belongs to a target: verifying production must never authorise scanning
|
|
68
|
+
* staging, even on the same wildcard domain (invariant 11).
|
|
69
|
+
*/
|
|
70
|
+
readonly verificationId?: string;
|
|
71
|
+
/** When it started. */
|
|
72
|
+
readonly startedAt: Date;
|
|
73
|
+
/** When it finished. */
|
|
74
|
+
readonly finishedAt?: Date;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A count of issues per {@link Severity}.
|
|
78
|
+
*
|
|
79
|
+
* Every severity is present, including zeroes, so a report renders a complete
|
|
80
|
+
* table without deciding what a missing key means.
|
|
81
|
+
*/
|
|
82
|
+
export type SeverityCounts = Readonly<Record<Severity, number>>;
|
|
83
|
+
/**
|
|
84
|
+
* What a run did, in the terms a reader cares about.
|
|
85
|
+
*
|
|
86
|
+
* The change summary every report opens with, and the row analytics aggregate.
|
|
87
|
+
* Computed once by reconciliation and stored, so no report has to recount
|
|
88
|
+
* findings at render time.
|
|
89
|
+
*/
|
|
90
|
+
export interface RunSummary {
|
|
91
|
+
/** The run this summarises. */
|
|
92
|
+
readonly runId: string;
|
|
93
|
+
/** Organisation it belongs to. */
|
|
94
|
+
readonly orgId: string;
|
|
95
|
+
/** Target it ran against. */
|
|
96
|
+
readonly targetId: string;
|
|
97
|
+
/** What kind of run it was. */
|
|
98
|
+
readonly kind: RunKind;
|
|
99
|
+
/** What set it going. */
|
|
100
|
+
readonly trigger: RunTrigger;
|
|
101
|
+
/** Engines that took part. */
|
|
102
|
+
readonly engines: readonly string[];
|
|
103
|
+
/** What the run exercised. */
|
|
104
|
+
readonly coverage: Coverage;
|
|
105
|
+
/** How long it took. */
|
|
106
|
+
readonly durationMs: number;
|
|
107
|
+
/** Issues opened for the first time by this run. */
|
|
108
|
+
readonly new: SeverityCounts;
|
|
109
|
+
/** Issues already open that this run saw again. */
|
|
110
|
+
readonly stillOpen: SeverityCounts;
|
|
111
|
+
/** Issues this run resolved. */
|
|
112
|
+
readonly resolved: SeverityCounts;
|
|
113
|
+
/** Issues that were resolved and that this run found again. */
|
|
114
|
+
readonly regressed: SeverityCounts;
|
|
115
|
+
/**
|
|
116
|
+
* Issues currently suppressed.
|
|
117
|
+
*
|
|
118
|
+
* Reported separately and **never folded into the other counts**: an ignored
|
|
119
|
+
* issue is excluded from metrics but never from the suppressed appendix
|
|
120
|
+
* (invariant 7).
|
|
121
|
+
*/
|
|
122
|
+
readonly ignored: SeverityCounts;
|
|
123
|
+
/**
|
|
124
|
+
* Exposure at the end of this run.
|
|
125
|
+
*
|
|
126
|
+
* `Σ over open issues of severityWeight × min(daysOpen, 90)`. Simple,
|
|
127
|
+
* explainable and monotone — it can only fall by fixing things or by time not
|
|
128
|
+
* passing.
|
|
129
|
+
*/
|
|
130
|
+
readonly exposureScore: number;
|
|
131
|
+
/** When the summary was computed. */
|
|
132
|
+
readonly createdAt: Date;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAC;AAE/E;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAElC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEnC,8BAA8B;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,iBAAiB;IACjB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAEvB,yBAAyB;IACzB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAE7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAE5B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC,uBAAuB;IACvB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IAEzB,wBAAwB;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAEvB,yBAAyB;IACzB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAE7B,8BAA8B;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAEpC,8BAA8B;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAE5B,wBAAwB;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,oDAAoD;IACpD,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAE7B,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IAEnC,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAElC,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IAEnC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IAEjC;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,qCAAqC;IACrC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B"}
|
package/dist/run.js
ADDED
package/dist/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How urgent an issue is.
|
|
3
|
+
*
|
|
4
|
+
* Five levels, ordered most to least urgent. `advisory` is the bottom of the
|
|
5
|
+
* scale rather than a separate "informational" category: it carries a weight of
|
|
6
|
+
* zero in the exposure score and no SLA, but it is still an issue, still
|
|
7
|
+
* tracked across runs, and still appears in reports.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link SEVERITY_ORDER} for comparing two severities.
|
|
10
|
+
* @see {@link SEVERITY_WEIGHTS} for the exposure-score weights.
|
|
11
|
+
*/
|
|
12
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'advisory';
|
|
13
|
+
/**
|
|
14
|
+
* Every {@link Severity}, most urgent first.
|
|
15
|
+
*
|
|
16
|
+
* Iteration order is part of the contract: report sections, severity
|
|
17
|
+
* breakdowns and count tables all render in this order, so a reader sees the
|
|
18
|
+
* same shape everywhere.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SEVERITY_ORDER: readonly ["critical", "high", "medium", "low", "advisory"];
|
|
21
|
+
/**
|
|
22
|
+
* Rank of a {@link Severity}, where a **higher number is more urgent**.
|
|
23
|
+
*
|
|
24
|
+
* Use this to compare severities rather than comparing the strings, which sort
|
|
25
|
+
* alphabetically and would put `advisory` above `critical`.
|
|
26
|
+
*
|
|
27
|
+
* @param severity - The severity to rank.
|
|
28
|
+
* @returns `4` for `critical` down to `0` for `advisory`.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* severityRank(finding.detectedSeverity) > severityRank(issue.severityAtIgnore);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function severityRank(severity: Severity): number;
|
|
36
|
+
/**
|
|
37
|
+
* Exposure-score weight for each {@link Severity}.
|
|
38
|
+
*
|
|
39
|
+
* `exposure_score = Σ over open issues of weight × min(daysOpen, 90)`.
|
|
40
|
+
*
|
|
41
|
+
* `advisory` weighs nothing, so advisories never inflate the number — they are
|
|
42
|
+
* tracked and reported, but they do not represent exposure.
|
|
43
|
+
*
|
|
44
|
+
* These weights are **placeholders** until ten design partners have looked at
|
|
45
|
+
* the number, and are stated as such in `00-DOMAIN.md` §8. Treat a change to
|
|
46
|
+
* them as a change to a published contract.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SEVERITY_WEIGHTS: Readonly<Record<Severity, number>>;
|
|
49
|
+
/**
|
|
50
|
+
* Why a finding was given the severity it has.
|
|
51
|
+
*
|
|
52
|
+
* Recorded on every finding because auditors ask, and because a severity with
|
|
53
|
+
* no provenance is not evidence. Listed here in precedence order — an earlier
|
|
54
|
+
* source wins over a later one:
|
|
55
|
+
*
|
|
56
|
+
* 1. `explicit` — stated by the scanner or the operator for this exact finding.
|
|
57
|
+
* 2. `cvss` — derived from a CVSS base score via {@link severityFromCvss}.
|
|
58
|
+
* 3. `engine_default` — the engine's own rating for the rule that fired.
|
|
59
|
+
* 4. `advisory` — taken from a published advisory for the associated CVE.
|
|
60
|
+
*
|
|
61
|
+
* Note the unfortunate collision: `advisory` is both the lowest
|
|
62
|
+
* {@link Severity} and the least-preferred severity *source*. They are
|
|
63
|
+
* unrelated — a finding can be `critical` from an `advisory` source. The names
|
|
64
|
+
* come from `00-DOMAIN.md` §3 and are kept so code and document agree.
|
|
65
|
+
*/
|
|
66
|
+
export type SeveritySource = 'explicit' | 'cvss' | 'engine_default' | 'advisory';
|
|
67
|
+
/**
|
|
68
|
+
* Every {@link SeveritySource}, most authoritative first.
|
|
69
|
+
*
|
|
70
|
+
* The order is the precedence rule: when two sources offer a severity for the
|
|
71
|
+
* same finding, the one appearing earlier here wins.
|
|
72
|
+
*/
|
|
73
|
+
export declare const SEVERITY_SOURCE_PRECEDENCE: readonly ["explicit", "cvss", "engine_default", "advisory"];
|
|
74
|
+
/**
|
|
75
|
+
* Maps a CVSS base score to a {@link Severity}.
|
|
76
|
+
*
|
|
77
|
+
* Uses the CVSS v3.1 qualitative severity rating scale unchanged, so a score
|
|
78
|
+
* rated "High" by any other tool is rated `high` here. The one adaptation is
|
|
79
|
+
* at the bottom: CVSS calls `0.0` "None", and this model has no "none", so it
|
|
80
|
+
* becomes `advisory`.
|
|
81
|
+
*
|
|
82
|
+
* | CVSS score | CVSS rating | {@link Severity} |
|
|
83
|
+
* | ----------- | ----------- | ---------------- |
|
|
84
|
+
* | 9.0 – 10.0 | Critical | `critical` |
|
|
85
|
+
* | 7.0 – 8.9 | High | `high` |
|
|
86
|
+
* | 4.0 – 6.9 | Medium | `medium` |
|
|
87
|
+
* | 0.1 – 3.9 | Low | `low` |
|
|
88
|
+
* | 0.0 | None | `advisory` |
|
|
89
|
+
*
|
|
90
|
+
* @param score - A CVSS base score between 0 and 10.
|
|
91
|
+
* @returns The corresponding severity.
|
|
92
|
+
* @throws RangeError If `score` is outside 0–10 or is not a number. A score
|
|
93
|
+
* that cannot be mapped is a data problem worth surfacing, not something to
|
|
94
|
+
* silently round into `advisory`.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* severityFromCvss(9.8); // 'critical'
|
|
99
|
+
* severityFromCvss(0); // 'advisory'
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare function severityFromCvss(score: number): Severity;
|
|
103
|
+
/**
|
|
104
|
+
* How long each severity may stay open before it breaches, and how long before
|
|
105
|
+
* breaching an issue starts warning.
|
|
106
|
+
*
|
|
107
|
+
* A policy rather than a constant because remediation windows are a customer
|
|
108
|
+
* agreement, not a property of the domain: a payments company and a hobby
|
|
109
|
+
* project do not owe the same turnaround on a `high`.
|
|
110
|
+
*
|
|
111
|
+
* @see {@link DEFAULT_SLA_POLICY} for the shipped default.
|
|
112
|
+
*/
|
|
113
|
+
export interface SlaPolicy {
|
|
114
|
+
/**
|
|
115
|
+
* Days allowed to remediate an issue of each severity, from `firstSeen`.
|
|
116
|
+
*
|
|
117
|
+
* `null` means no deadline applies — the issue is tracked and reported, but
|
|
118
|
+
* it can never be "due" or "breached". `advisory` is `null` by default.
|
|
119
|
+
*/
|
|
120
|
+
readonly durationDays: Readonly<Record<Severity, number | null>>;
|
|
121
|
+
/**
|
|
122
|
+
* How many days before the deadline an issue starts reporting `due_soon`.
|
|
123
|
+
*
|
|
124
|
+
* Exists so a report can distinguish "you have time" from "this is about to
|
|
125
|
+
* breach" without the reader doing arithmetic.
|
|
126
|
+
*/
|
|
127
|
+
readonly dueSoonDays: number;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The default remediation windows.
|
|
131
|
+
*
|
|
132
|
+
* Chosen to be defensible rather than derived: they line up with the windows
|
|
133
|
+
* most vulnerability-management programmes already run, so a customer can
|
|
134
|
+
* adopt them without an argument and override them without a migration.
|
|
135
|
+
*
|
|
136
|
+
* `advisory` has no deadline at all. Giving a zero-weight severity a due date
|
|
137
|
+
* would manufacture breaches out of things nobody agreed to fix.
|
|
138
|
+
*
|
|
139
|
+
* | Severity | Window |
|
|
140
|
+
* | ---------- | -------- |
|
|
141
|
+
* | `critical` | 7 days |
|
|
142
|
+
* | `high` | 30 days |
|
|
143
|
+
* | `medium` | 90 days |
|
|
144
|
+
* | `low` | 180 days |
|
|
145
|
+
* | `advisory` | none |
|
|
146
|
+
*
|
|
147
|
+
* **These are defaults, not the domain.** `00-DOMAIN.md` says only that
|
|
148
|
+
* `sla_due_at` is "derived from effective severity"; the numbers are this
|
|
149
|
+
* package's opinion and are meant to be replaced by an org's own policy.
|
|
150
|
+
*/
|
|
151
|
+
export declare const DEFAULT_SLA_POLICY: SlaPolicy;
|
|
152
|
+
/**
|
|
153
|
+
* Where an issue stands against its remediation deadline.
|
|
154
|
+
*
|
|
155
|
+
* - `within` — inside the window, and not close enough to warn about.
|
|
156
|
+
* - `due_soon` — inside the window but within {@link SlaPolicy.dueSoonDays}.
|
|
157
|
+
* - `breached` — past the deadline.
|
|
158
|
+
*
|
|
159
|
+
* An issue with no deadline (see {@link DEFAULT_SLA_POLICY}) is always
|
|
160
|
+
* `within`: it cannot breach something it was never given.
|
|
161
|
+
*/
|
|
162
|
+
export type SlaStatus = 'within' | 'due_soon' | 'breached';
|
|
163
|
+
/**
|
|
164
|
+
* When an issue of this severity, first seen at this moment, is due.
|
|
165
|
+
*
|
|
166
|
+
* Pure: the deadline is computed from the arguments alone, so the same inputs
|
|
167
|
+
* always give the same answer and a test needs no control over the clock.
|
|
168
|
+
*
|
|
169
|
+
* @param severity - The issue's **effective** severity, not its detected one —
|
|
170
|
+
* an override is a deliberate statement about how urgent something is, and the
|
|
171
|
+
* deadline should follow it.
|
|
172
|
+
* @param firstSeen - When the issue was first seen. Never the current run:
|
|
173
|
+
* `first_seen` is never reset, so neither is the deadline (invariant 4).
|
|
174
|
+
* @param policy - The remediation windows to apply.
|
|
175
|
+
* @returns The deadline, or `null` where the severity has no window.
|
|
176
|
+
*/
|
|
177
|
+
export declare function slaDueAt(severity: Severity, firstSeen: Date, policy?: SlaPolicy): Date | null;
|
|
178
|
+
/**
|
|
179
|
+
* Where an issue stands against a deadline, as of a given moment.
|
|
180
|
+
*
|
|
181
|
+
* Pure, and takes `now` explicitly rather than reading the clock, so a report
|
|
182
|
+
* rendered for a past run reports the status *as of that run* rather than as of
|
|
183
|
+
* today.
|
|
184
|
+
*
|
|
185
|
+
* @param dueAt - The deadline from {@link slaDueAt}, or `null` for no deadline.
|
|
186
|
+
* @param now - The moment to evaluate against.
|
|
187
|
+
* @param policy - Supplies the `due_soon` window.
|
|
188
|
+
* @returns The status. Always `within` when `dueAt` is `null`.
|
|
189
|
+
*/
|
|
190
|
+
export declare function slaStatus(dueAt: Date | null, now: Date, policy?: SlaPolicy): SlaStatus;
|
|
191
|
+
//# sourceMappingURL=severity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"severity.d.ts","sourceRoot":"","sources":["../src/severity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,UAAU,CAAC;AAE3E;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,4DAA6D,CAAC;AAEzF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAM9D,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,gBAAgB,GAAG,UAAU,CAAC;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,6DAK7B,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CASxD;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IAEjE;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAS/B,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,IAAI,EACf,MAAM,GAAE,SAA8B,GACrC,IAAI,GAAG,IAAI,CAIb;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,IAAI,GAAG,IAAI,EAClB,GAAG,EAAE,IAAI,EACT,MAAM,GAAE,SAA8B,GACrC,SAAS,CAKX"}
|