@secureport/core 0.2.1 → 0.3.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 +67 -31
- 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/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/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 +31 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -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/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 +70 -0
- package/dist/snapshot-builder.d.ts.map +1 -0
- package/dist/snapshot-builder.js +148 -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 +3 -2
- package/src/coverage.ts +65 -0
- package/src/finding.ts +112 -0
- package/src/fingerprint.ts +315 -0
- package/src/import/nuclei.ts +173 -0
- package/src/import/zap.ts +161 -0
- package/src/index.ts +56 -2
- package/src/issue.ts +244 -11
- package/src/reconcile.ts +421 -17
- package/src/run.ts +163 -0
- package/src/severity.ts +250 -0
- package/src/snapshot-builder.ts +199 -0
- package/src/snapshot.ts +146 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Which fingerprint algorithm this build of the package implements.
|
|
4
|
+
*
|
|
5
|
+
* **A public contract, and the most consequential string in the package.** It
|
|
6
|
+
* is stored on every finding and every issue, everywhere, so that a fingerprint
|
|
7
|
+
* recorded a year ago can still be interpreted. Changing the algorithm means
|
|
8
|
+
* bumping this *and* running a per-organisation re-fingerprint migration that
|
|
9
|
+
* preserves history — never a silent recomputation, which would orphan every
|
|
10
|
+
* issue whose evidence no longer hashes to the same value.
|
|
11
|
+
*
|
|
12
|
+
* If you are tempted to "just tweak" the normaliser, that is this constant's
|
|
13
|
+
* job to prevent.
|
|
14
|
+
*/
|
|
15
|
+
export const FINGERPRINT_VERSION = 'fp_v1';
|
|
16
|
+
/** A path segment that is entirely digits, e.g. the `123` in `/orders/123`. */
|
|
17
|
+
const NUMERIC_SEGMENT = /^\d+$/u;
|
|
18
|
+
/** A path segment that is a UUID in the canonical 8-4-4-4-12 form. */
|
|
19
|
+
const UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
20
|
+
/**
|
|
21
|
+
* The placeholder that a variable path segment collapses to.
|
|
22
|
+
*
|
|
23
|
+
* Exported because it appears in normalised locations, which appear in reports
|
|
24
|
+
* and in support conversations: someone reading `/orders/{id}` should be able
|
|
25
|
+
* to find out what produced it.
|
|
26
|
+
*/
|
|
27
|
+
export const PATH_PLACEHOLDER = '{id}';
|
|
28
|
+
/**
|
|
29
|
+
* Decodes percent-encoding exactly once, and never throws.
|
|
30
|
+
*
|
|
31
|
+
* Once, not repeatedly: decoding until stable would make `%2541` and `%41`
|
|
32
|
+
* collapse to the same thing, so a target that double-encodes could be made to
|
|
33
|
+
* collide with one that does not.
|
|
34
|
+
*
|
|
35
|
+
* Malformed encoding is left alone rather than rejected. A fingerprint that
|
|
36
|
+
* throws on strange input is a fingerprint that loses a finding.
|
|
37
|
+
*/
|
|
38
|
+
function decodeOnce(value) {
|
|
39
|
+
try {
|
|
40
|
+
return decodeURIComponent(value);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Collapses one path segment to {@link PATH_PLACEHOLDER} if it identifies a
|
|
48
|
+
* record rather than a route.
|
|
49
|
+
*/
|
|
50
|
+
function collapseSegment(segment) {
|
|
51
|
+
if (NUMERIC_SEGMENT.test(segment) || UUID_SEGMENT.test(segment))
|
|
52
|
+
return PATH_PLACEHOLDER;
|
|
53
|
+
return segment;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Normalises a location so that the same weakness in the same place produces
|
|
57
|
+
* the same string, however the engine happened to write it down.
|
|
58
|
+
*
|
|
59
|
+
* This is the part of the fingerprint that decides whether `/orders/1` and
|
|
60
|
+
* `/orders/2` are one issue or two thousand. Applies, in order:
|
|
61
|
+
*
|
|
62
|
+
* - **Lowercases the host**, and converts an internationalised domain to
|
|
63
|
+
* punycode, so `HTTPS://例え.テスト/x` and `https://xn--r8jz45g.xn--zckzah/x`
|
|
64
|
+
* are one location.
|
|
65
|
+
* - **Drops a default port** (`:443` on https, `:80` on http) and keeps any
|
|
66
|
+
* other, because `:8443` is a different service and `:443` is not.
|
|
67
|
+
* - **Decodes percent-encoding once.**
|
|
68
|
+
* - **Collapses numeric and UUID path segments** to `{id}`, so a per-record URL
|
|
69
|
+
* does not open a per-record issue.
|
|
70
|
+
* - **Drops a trailing slash**, except on the root path where it is the path.
|
|
71
|
+
* - **Strips query values but keeps parameter names, sorted.** `?b=2&a=secret`
|
|
72
|
+
* becomes `?a&b`. The names are structure and belong in identity; the values
|
|
73
|
+
* are usually the payload that proved the weakness, which is evidence and
|
|
74
|
+
* must never reach a fingerprint. Sorting means parameter order cannot split
|
|
75
|
+
* one issue into two.
|
|
76
|
+
* - **Drops the fragment**, which the server never sees.
|
|
77
|
+
*
|
|
78
|
+
* Anything that is not a parseable absolute URL — a bare host, a file path, a
|
|
79
|
+
* `host:port` pair from a network scan — is normalised as a path alone. That is
|
|
80
|
+
* deliberate: refusing to fingerprint a non-HTTP finding would exclude whole
|
|
81
|
+
* classes of scanner from the model.
|
|
82
|
+
*
|
|
83
|
+
* @param location - Where the weakness was found.
|
|
84
|
+
* @returns The normalised location.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* normaliseLocation('HTTPS://API.Example.com:443/Orders/123/items/?b=2&a=secret#f');
|
|
89
|
+
* // 'https://api.example.com/Orders/{id}/items?a&b'
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function normaliseLocation(location) {
|
|
93
|
+
const trimmed = location.trim();
|
|
94
|
+
let url;
|
|
95
|
+
try {
|
|
96
|
+
url = new URL(trimmed);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
url = undefined;
|
|
100
|
+
}
|
|
101
|
+
// Not an absolute URL: normalise it as a bare path and stop. `new URL` would
|
|
102
|
+
// otherwise turn `example.com/x` into the `example.com:` protocol.
|
|
103
|
+
if (!url || url.protocol === '' || !url.host) {
|
|
104
|
+
return normalisePathOnly(trimmed);
|
|
105
|
+
}
|
|
106
|
+
// Not `decodeOnce` here: normalisePathOnly decodes, and decoding on the way
|
|
107
|
+
// in as well would decode twice — which would fold `%252F` into `%2F` into
|
|
108
|
+
// `/` and let a double-encoding target collide with a single-encoding one.
|
|
109
|
+
const path = normalisePathOnly(url.pathname);
|
|
110
|
+
// `url.host` already carries punycode and lower case, and omits a default
|
|
111
|
+
// port for the scheme.
|
|
112
|
+
const names = [...new Set([...url.searchParams.keys()].map(decodeOnce))].sort();
|
|
113
|
+
const query = names.length > 0 ? `?${names.join('&')}` : '';
|
|
114
|
+
return `${url.protocol}//${url.host}${path}${query}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Normalises a path with no scheme or host.
|
|
118
|
+
*/
|
|
119
|
+
function normalisePathOnly(path) {
|
|
120
|
+
const decoded = decodeOnce(path.trim());
|
|
121
|
+
if (decoded === '' || decoded === '/')
|
|
122
|
+
return decoded === '' ? '' : '/';
|
|
123
|
+
const collapsed = decoded
|
|
124
|
+
.split('/')
|
|
125
|
+
.map((segment) => collapseSegment(segment))
|
|
126
|
+
.join('/');
|
|
127
|
+
return collapsed.length > 1 && collapsed.endsWith('/') ? collapsed.slice(0, -1) : collapsed;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Maps `engine:ruleId` to a shared, engine-independent weakness key.
|
|
131
|
+
*
|
|
132
|
+
* **This table is the entire mechanism of cross-engine deduplication.** Without
|
|
133
|
+
* it a ZAP detection and a Nuclei detection of the same weakness fall back to
|
|
134
|
+
* CWE, and where either engine omits the CWE they never collide at all — you
|
|
135
|
+
* get per-engine tracking wearing the clothes of an issue tracker.
|
|
136
|
+
*
|
|
137
|
+
* **It is deliberately small, and that is not laziness.** A mapping asserts
|
|
138
|
+
* that a specific rule id means a specific weakness, and a wrong assertion
|
|
139
|
+
* silently merges two unrelated issues — worse than not mapping at all, because
|
|
140
|
+
* the merge is invisible. Rule ids cannot be known honestly until real scanner
|
|
141
|
+
* output has been parsed, which is what 4.4a and 4.4b do with committed
|
|
142
|
+
* fixtures. The table grows there, from evidence.
|
|
143
|
+
*
|
|
144
|
+
* **Entries must be added in pairs, per weakness, across engines — a half-filled
|
|
145
|
+
* table is worse than an empty one.** The mapping beats the CWE fallback, so
|
|
146
|
+
* mapping ZAP's HSTS rule while leaving Nuclei's unmapped gives them *different*
|
|
147
|
+
* keys, when falling back to `CWE-319` on both sides would have collided them
|
|
148
|
+
* correctly. Adding one engine's rule silently un-deduplicates the weakness.
|
|
149
|
+
* Found by importing a fixture from each engine and watching them stop
|
|
150
|
+
* agreeing.
|
|
151
|
+
*
|
|
152
|
+
* `00-DOMAIN.md` §10 leaves the eventual size open, leaning towards the top
|
|
153
|
+
* ~200 Nuclei templates plus ZAP's plugin list, with CWE fallback beyond.
|
|
154
|
+
*
|
|
155
|
+
* Keys are `${sourceEngine}:${sourceRuleId}`, both lowercased.
|
|
156
|
+
*/
|
|
157
|
+
export const VULN_KEY_MAP = Object.freeze({
|
|
158
|
+
// ZAP plugin ids are stable and documented, which is why the seed is ZAP's.
|
|
159
|
+
'zap:40012': 'xss-reflected',
|
|
160
|
+
'zap:40014': 'xss-persistent',
|
|
161
|
+
'zap:40018': 'sql-injection',
|
|
162
|
+
'zap:10038': 'csp-missing',
|
|
163
|
+
'zap:10035': 'hsts-missing',
|
|
164
|
+
'zap:10021': 'x-content-type-options-missing',
|
|
165
|
+
'zap:10020': 'x-frame-options-missing',
|
|
166
|
+
// Nuclei's side of the pairs above. Anything mapped for one engine and not
|
|
167
|
+
// the other stops the two agreeing, so these travel together.
|
|
168
|
+
'nuclei:xss-reflected': 'xss-reflected',
|
|
169
|
+
'nuclei:missing-hsts': 'hsts-missing',
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* The engine-independent key for a weakness class.
|
|
173
|
+
*
|
|
174
|
+
* Resolution order, per `00-DOMAIN.md` §4:
|
|
175
|
+
*
|
|
176
|
+
* 1. {@link VULN_KEY_MAP}, looked up by `engine:ruleId`.
|
|
177
|
+
* 2. `cwe` alone.
|
|
178
|
+
* 3. `category` alone.
|
|
179
|
+
* 4. `engine:ruleId` itself.
|
|
180
|
+
*
|
|
181
|
+
* **The spec says `cwe + '/' + category`, and that was tried and abandoned.**
|
|
182
|
+
* Engines do not share a category vocabulary: ZAP supplies no category at all,
|
|
183
|
+
* so reflected XSS there keys as `CWE-79`, while Nuclei tags the same finding
|
|
184
|
+
* `xss` and keys as `CWE-79/xss`. The two never collide — so the fallback
|
|
185
|
+
* actively prevented the cross-engine deduplication it exists to provide, which
|
|
186
|
+
* a fixture from each engine demonstrated immediately.
|
|
187
|
+
*
|
|
188
|
+
* CWE alone is coarser, and the coarseness is bounded: the fingerprint also
|
|
189
|
+
* carries the normalised location and the parameter, so two findings only merge
|
|
190
|
+
* when they share a weakness class *and* a place. Two genuinely different
|
|
191
|
+
* weaknesses under one CWE, at the same URL and parameter, is the case this
|
|
192
|
+
* gets wrong — and `POST /issues/{a}/merge/{b}` exists because something will.
|
|
193
|
+
*
|
|
194
|
+
* Steps 3 and 4 keep a finding trackable when there is no CWE at all. **Step 4
|
|
195
|
+
* never deduplicates across engines**, which is the honest outcome for a rule
|
|
196
|
+
* nobody has mapped: it tracks correctly and merges nothing it should not.
|
|
197
|
+
*
|
|
198
|
+
* @param input - What the engine reported.
|
|
199
|
+
* @returns The weakness key.
|
|
200
|
+
* @throws TypeError If nothing identifying is present at all.
|
|
201
|
+
*/
|
|
202
|
+
export function vulnKey(input) {
|
|
203
|
+
const engine = input.sourceEngine.trim().toLowerCase();
|
|
204
|
+
const ruleId = input.sourceRuleId?.trim().toLowerCase();
|
|
205
|
+
if (engine !== '' && ruleId !== undefined && ruleId !== '') {
|
|
206
|
+
const mapped = VULN_KEY_MAP[`${engine}:${ruleId}`];
|
|
207
|
+
if (mapped !== undefined)
|
|
208
|
+
return mapped;
|
|
209
|
+
}
|
|
210
|
+
const cwe = input.cwe?.trim().toUpperCase();
|
|
211
|
+
const category = input.category?.trim().toLowerCase();
|
|
212
|
+
if (cwe !== undefined && cwe !== '')
|
|
213
|
+
return cwe;
|
|
214
|
+
if (category !== undefined && category !== '')
|
|
215
|
+
return category;
|
|
216
|
+
if (engine !== '' && ruleId !== undefined && ruleId !== '')
|
|
217
|
+
return `${engine}:${ruleId}`;
|
|
218
|
+
throw new TypeError('cannot derive a vuln_key: a finding needs a mapped rule, a CWE, a category, or an engine rule id');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* The content address of a weakness: `sha256(targetId | vulnKey | normalised
|
|
222
|
+
* location | parameter ?? port ?? '')`.
|
|
223
|
+
*
|
|
224
|
+
* Deterministic and pure — the same input always gives the same digest, on any
|
|
225
|
+
* machine, in any order, at any time. That is what lets findings from different
|
|
226
|
+
* runs and different engines reconcile into one issue.
|
|
227
|
+
*
|
|
228
|
+
* Uses Node's `node:crypto`, which is a builtin rather than a dependency, so
|
|
229
|
+
* the package still installs nothing. It does mean fingerprinting requires
|
|
230
|
+
* Node; report rendering does not, and rendering is the only part of this
|
|
231
|
+
* package a browser was ever going to run.
|
|
232
|
+
*
|
|
233
|
+
* @param input - The identifying facts.
|
|
234
|
+
* @returns A lowercase hex SHA-256 digest.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* const key = vulnKey({ sourceEngine: 'zap', sourceRuleId: '40012' });
|
|
239
|
+
* fingerprint({ targetId: 'tgt_1', vulnKey: key, location: '/search', parameter: 'q' });
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
export function fingerprint(input) {
|
|
243
|
+
const tail = input.parameter ?? (input.port !== undefined ? String(input.port) : '');
|
|
244
|
+
const material = [input.targetId, input.vulnKey, normaliseLocation(input.location), tail].join('|');
|
|
245
|
+
return createHash('sha256').update(material, 'utf8').digest('hex');
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Finding } from '../finding.js';
|
|
2
|
+
/**
|
|
3
|
+
* What an importer needs that a scanner's output cannot tell it.
|
|
4
|
+
*
|
|
5
|
+
* A scanner knows what it found; it does not know which organisation, run or
|
|
6
|
+
* target the result belongs to, and it must not invent ids. All of it is
|
|
7
|
+
* supplied, so importing stays pure and repeatable.
|
|
8
|
+
*/
|
|
9
|
+
export interface ImportOptions {
|
|
10
|
+
/** Organisation the findings belong to. */
|
|
11
|
+
readonly orgId: string;
|
|
12
|
+
/** The run that produced them. */
|
|
13
|
+
readonly runId: string;
|
|
14
|
+
/** The target they are against. Part of every fingerprint. */
|
|
15
|
+
readonly targetId: string;
|
|
16
|
+
/** When the import happened. No importer reads the clock. */
|
|
17
|
+
readonly now: Date;
|
|
18
|
+
/** Supplies finding ids. Injected so importing is deterministic. */
|
|
19
|
+
readonly newId: () => string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Turns Nuclei's JSONL output into {@link Finding}s.
|
|
23
|
+
*
|
|
24
|
+
* Nuclei writes **one JSON object per line** (`-jsonl`), so the file is not
|
|
25
|
+
* itself valid JSON. Blank lines are skipped, and a line that will not parse is
|
|
26
|
+
* skipped rather than thrown on: a single malformed record should not cost a
|
|
27
|
+
* user every other result in the file. Records with no `template-id` or no
|
|
28
|
+
* location are skipped for the same reason — there is nothing to fingerprint.
|
|
29
|
+
*
|
|
30
|
+
* Severity comes from the CVSS score where Nuclei supplies one, and from its own
|
|
31
|
+
* rating otherwise; `severitySource` records which, because a severity with no
|
|
32
|
+
* provenance is not evidence.
|
|
33
|
+
*
|
|
34
|
+
* @param jsonl - The contents of a Nuclei JSONL file.
|
|
35
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
36
|
+
* @returns One finding per usable record, in file order.
|
|
37
|
+
*/
|
|
38
|
+
export declare function importNuclei(jsonl: string, options: ImportOptions): Finding[];
|
|
39
|
+
//# sourceMappingURL=nuclei.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nuclei.d.ts","sourceRoot":"","sources":["../../src/import/nuclei.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAK7C;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IAEnB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;CAC9B;AAoDD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CA4E7E"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { severityFromCvss } from '../severity.js';
|
|
2
|
+
import { fingerprint, vulnKey, FINGERPRINT_VERSION } from '../fingerprint.js';
|
|
3
|
+
/**
|
|
4
|
+
* Nuclei's severity vocabulary, mapped onto {@link Severity}.
|
|
5
|
+
*
|
|
6
|
+
* `info` and `unknown` both become `advisory`: this model has no
|
|
7
|
+
* "informational" category separate from the bottom of the scale, and an
|
|
8
|
+
* unrated finding is not evidence of low risk — it is evidence of nothing, which
|
|
9
|
+
* is what `advisory` means here.
|
|
10
|
+
*/
|
|
11
|
+
const NUCLEI_SEVERITY = Object.freeze({
|
|
12
|
+
critical: 'critical',
|
|
13
|
+
high: 'high',
|
|
14
|
+
medium: 'medium',
|
|
15
|
+
low: 'low',
|
|
16
|
+
info: 'advisory',
|
|
17
|
+
unknown: 'advisory',
|
|
18
|
+
});
|
|
19
|
+
const str = (v) => typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined;
|
|
20
|
+
const firstOf = (v) => (Array.isArray(v) ? str(v[0]) : str(v));
|
|
21
|
+
const allOf = (v) => {
|
|
22
|
+
if (!Array.isArray(v))
|
|
23
|
+
return str(v) === undefined ? undefined : [str(v)];
|
|
24
|
+
const items = v.map(str).filter((x) => x !== undefined);
|
|
25
|
+
return items.length > 0 ? items : undefined;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Turns Nuclei's JSONL output into {@link Finding}s.
|
|
29
|
+
*
|
|
30
|
+
* Nuclei writes **one JSON object per line** (`-jsonl`), so the file is not
|
|
31
|
+
* itself valid JSON. Blank lines are skipped, and a line that will not parse is
|
|
32
|
+
* skipped rather than thrown on: a single malformed record should not cost a
|
|
33
|
+
* user every other result in the file. Records with no `template-id` or no
|
|
34
|
+
* location are skipped for the same reason — there is nothing to fingerprint.
|
|
35
|
+
*
|
|
36
|
+
* Severity comes from the CVSS score where Nuclei supplies one, and from its own
|
|
37
|
+
* rating otherwise; `severitySource` records which, because a severity with no
|
|
38
|
+
* provenance is not evidence.
|
|
39
|
+
*
|
|
40
|
+
* @param jsonl - The contents of a Nuclei JSONL file.
|
|
41
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
42
|
+
* @returns One finding per usable record, in file order.
|
|
43
|
+
*/
|
|
44
|
+
export function importNuclei(jsonl, options) {
|
|
45
|
+
const findings = [];
|
|
46
|
+
for (const line of jsonl.split('\n')) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (trimmed === '')
|
|
49
|
+
continue;
|
|
50
|
+
let record;
|
|
51
|
+
try {
|
|
52
|
+
record = JSON.parse(trimmed);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// One unparseable line must not cost the user the rest of the file.
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const templateId = str(record['template-id']);
|
|
59
|
+
const location = str(record['matched-at']) ?? str(record.host);
|
|
60
|
+
if (templateId === undefined || location === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
const info = record.info ?? {};
|
|
63
|
+
const classification = info.classification ?? {};
|
|
64
|
+
const cvss = typeof classification['cvss-score'] === 'number' ? classification['cvss-score'] : undefined;
|
|
65
|
+
const rated = NUCLEI_SEVERITY[str(info.severity)?.toLowerCase() ?? ''];
|
|
66
|
+
let detectedSeverity;
|
|
67
|
+
let severitySource;
|
|
68
|
+
if (cvss !== undefined && cvss >= 0 && cvss <= 10) {
|
|
69
|
+
detectedSeverity = severityFromCvss(cvss);
|
|
70
|
+
severitySource = 'cvss';
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
detectedSeverity = rated ?? 'advisory';
|
|
74
|
+
severitySource = 'engine_default';
|
|
75
|
+
}
|
|
76
|
+
// Nuclei writes CWE as `cwe-502`; the rest of the model uses `CWE-502`.
|
|
77
|
+
const cwe = firstOf(classification['cwe-id'])?.toUpperCase();
|
|
78
|
+
const category = firstOf(info.tags);
|
|
79
|
+
const key = vulnKey({
|
|
80
|
+
sourceEngine: 'nuclei',
|
|
81
|
+
sourceRuleId: templateId,
|
|
82
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
83
|
+
...(category === undefined ? {} : { category }),
|
|
84
|
+
});
|
|
85
|
+
findings.push({
|
|
86
|
+
id: options.newId(),
|
|
87
|
+
orgId: options.orgId,
|
|
88
|
+
runId: options.runId,
|
|
89
|
+
fingerprint: fingerprint({ targetId: options.targetId, vulnKey: key, location }),
|
|
90
|
+
fingerprintVersion: FINGERPRINT_VERSION,
|
|
91
|
+
title: str(info.name) ?? templateId,
|
|
92
|
+
...(str(info.description) === undefined ? {} : { description: str(info.description) }),
|
|
93
|
+
detectedSeverity,
|
|
94
|
+
severitySource,
|
|
95
|
+
...(cvss === undefined ? {} : { cvssScore: cvss }),
|
|
96
|
+
...(str(classification['cvss-metrics']) === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: { cvssVector: str(classification['cvss-metrics']) }),
|
|
99
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
100
|
+
...(firstOf(classification['cve-id']) === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: { cve: firstOf(classification['cve-id']).toUpperCase() }),
|
|
103
|
+
vulnKey: key,
|
|
104
|
+
...(category === undefined ? {} : { category }),
|
|
105
|
+
location,
|
|
106
|
+
...(str(info.remediation) === undefined ? {} : { recommendation: str(info.remediation) }),
|
|
107
|
+
...(allOf(info.reference) === undefined ? {} : { references: allOf(info.reference) }),
|
|
108
|
+
sourceEngine: 'nuclei',
|
|
109
|
+
sourceRuleId: templateId,
|
|
110
|
+
createdAt: options.now,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return findings;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=nuclei.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nuclei.js","sourceRoot":"","sources":["../../src/import/nuclei.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Finding } from '../finding.js';
|
|
2
|
+
import type { ImportOptions } from './nuclei.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turns a ZAP JSON report into {@link Finding}s.
|
|
5
|
+
*
|
|
6
|
+
* **One finding per instance, not per alert.** ZAP groups every occurrence of a
|
|
7
|
+
* weakness under a single alert with an `instances` array, so an alert is a
|
|
8
|
+
* class and an instance is a detection. Flattening is what lets two URLs
|
|
9
|
+
* affected by the same rule become two issues, and lets one of them be fixed
|
|
10
|
+
* without closing the other.
|
|
11
|
+
*
|
|
12
|
+
* An alert with no instances still produces one finding, against the site — a
|
|
13
|
+
* detection with no location is still a detection.
|
|
14
|
+
*
|
|
15
|
+
* Severity comes from ZAP's `riskcode`; see {@link ZAP_RISK} for why nothing
|
|
16
|
+
* here is ever `critical`.
|
|
17
|
+
*
|
|
18
|
+
* @param json - The contents of a ZAP JSON report.
|
|
19
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
20
|
+
* @returns One finding per instance, in report order.
|
|
21
|
+
* @throws SyntaxError If the report is not valid JSON. Unlike Nuclei's JSONL,
|
|
22
|
+
* where one bad line costs one record, a ZAP report is a single document: if it
|
|
23
|
+
* will not parse there is nothing to salvage and silence would be a lie.
|
|
24
|
+
*/
|
|
25
|
+
export declare function importZap(json: string, options: ImportOptions): Finding[];
|
|
26
|
+
//# sourceMappingURL=zap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zap.d.ts","sourceRoot":"","sources":["../../src/import/zap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CAmEzE"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { fingerprint, vulnKey, FINGERPRINT_VERSION } from '../fingerprint.js';
|
|
2
|
+
/**
|
|
3
|
+
* ZAP's `riskcode` mapped onto {@link Severity}.
|
|
4
|
+
*
|
|
5
|
+
* **ZAP has no "critical".** Its scale tops out at High (`3`), so nothing this
|
|
6
|
+
* importer produces is ever `critical` — a `critical` issue in a report that
|
|
7
|
+
* ZAP also detected got there from another engine, a CVSS score or a human.
|
|
8
|
+
* Worth knowing before wondering why a ZAP-only target has none.
|
|
9
|
+
*/
|
|
10
|
+
const ZAP_RISK = Object.freeze({
|
|
11
|
+
'3': 'high',
|
|
12
|
+
'2': 'medium',
|
|
13
|
+
'1': 'low',
|
|
14
|
+
'0': 'advisory',
|
|
15
|
+
});
|
|
16
|
+
const str = (v) => {
|
|
17
|
+
if (typeof v === 'number')
|
|
18
|
+
return String(v);
|
|
19
|
+
return typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined;
|
|
20
|
+
};
|
|
21
|
+
/** ZAP embeds HTML in its description and solution fields. */
|
|
22
|
+
const stripHtml = (v) => v
|
|
23
|
+
.replace(/<[^>]*>/gu, ' ')
|
|
24
|
+
.replace(/\s+/gu, ' ')
|
|
25
|
+
.trim();
|
|
26
|
+
/** ZAP's reference field is one string of newline-separated URLs. */
|
|
27
|
+
const splitReferences = (v) => {
|
|
28
|
+
const text = str(v);
|
|
29
|
+
if (text === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const urls = stripHtml(text)
|
|
32
|
+
.split(/\s+/u)
|
|
33
|
+
.filter((token) => token.startsWith('http'));
|
|
34
|
+
return urls.length > 0 ? urls : undefined;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Turns a ZAP JSON report into {@link Finding}s.
|
|
38
|
+
*
|
|
39
|
+
* **One finding per instance, not per alert.** ZAP groups every occurrence of a
|
|
40
|
+
* weakness under a single alert with an `instances` array, so an alert is a
|
|
41
|
+
* class and an instance is a detection. Flattening is what lets two URLs
|
|
42
|
+
* affected by the same rule become two issues, and lets one of them be fixed
|
|
43
|
+
* without closing the other.
|
|
44
|
+
*
|
|
45
|
+
* An alert with no instances still produces one finding, against the site — a
|
|
46
|
+
* detection with no location is still a detection.
|
|
47
|
+
*
|
|
48
|
+
* Severity comes from ZAP's `riskcode`; see {@link ZAP_RISK} for why nothing
|
|
49
|
+
* here is ever `critical`.
|
|
50
|
+
*
|
|
51
|
+
* @param json - The contents of a ZAP JSON report.
|
|
52
|
+
* @param options - Ownership, and the injected clock and id source.
|
|
53
|
+
* @returns One finding per instance, in report order.
|
|
54
|
+
* @throws SyntaxError If the report is not valid JSON. Unlike Nuclei's JSONL,
|
|
55
|
+
* where one bad line costs one record, a ZAP report is a single document: if it
|
|
56
|
+
* will not parse there is nothing to salvage and silence would be a lie.
|
|
57
|
+
*/
|
|
58
|
+
export function importZap(json, options) {
|
|
59
|
+
const report = JSON.parse(json);
|
|
60
|
+
const sites = Array.isArray(report.site) ? report.site : [];
|
|
61
|
+
const findings = [];
|
|
62
|
+
for (const site of sites) {
|
|
63
|
+
const siteName = str(site['@name']);
|
|
64
|
+
const alerts = Array.isArray(site.alerts) ? site.alerts : [];
|
|
65
|
+
for (const alert of alerts) {
|
|
66
|
+
const pluginId = str(alert.pluginid);
|
|
67
|
+
const title = str(alert.alert) ?? str(alert.name);
|
|
68
|
+
if (pluginId === undefined || title === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
// ZAP writes `0` for "no CWE", which is not a CWE.
|
|
71
|
+
const rawCwe = str(alert.cweid);
|
|
72
|
+
const cwe = rawCwe !== undefined && rawCwe !== '0' && rawCwe !== '-1' ? `CWE-${rawCwe}` : undefined;
|
|
73
|
+
const key = vulnKey({
|
|
74
|
+
sourceEngine: 'zap',
|
|
75
|
+
sourceRuleId: pluginId,
|
|
76
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
77
|
+
});
|
|
78
|
+
const instances = Array.isArray(alert.instances) ? alert.instances : [];
|
|
79
|
+
const targets = instances.length > 0 ? instances : [{ uri: siteName }];
|
|
80
|
+
for (const instance of targets) {
|
|
81
|
+
const location = str(instance.uri) ?? siteName;
|
|
82
|
+
if (location === undefined)
|
|
83
|
+
continue;
|
|
84
|
+
const parameter = str(instance.param);
|
|
85
|
+
findings.push({
|
|
86
|
+
id: options.newId(),
|
|
87
|
+
orgId: options.orgId,
|
|
88
|
+
runId: options.runId,
|
|
89
|
+
fingerprint: fingerprint({
|
|
90
|
+
targetId: options.targetId,
|
|
91
|
+
vulnKey: key,
|
|
92
|
+
location,
|
|
93
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
94
|
+
}),
|
|
95
|
+
fingerprintVersion: FINGERPRINT_VERSION,
|
|
96
|
+
title,
|
|
97
|
+
...(str(alert.desc) === undefined ? {} : { description: stripHtml(str(alert.desc)) }),
|
|
98
|
+
detectedSeverity: ZAP_RISK[str(alert.riskcode) ?? ''] ?? 'advisory',
|
|
99
|
+
severitySource: 'engine_default',
|
|
100
|
+
...(cwe === undefined ? {} : { cwe }),
|
|
101
|
+
vulnKey: key,
|
|
102
|
+
location,
|
|
103
|
+
...(parameter === undefined ? {} : { parameter }),
|
|
104
|
+
...(str(alert.solution) === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: { recommendation: stripHtml(str(alert.solution)) }),
|
|
107
|
+
...(splitReferences(alert.reference) === undefined
|
|
108
|
+
? {}
|
|
109
|
+
: { references: splitReferences(alert.reference) }),
|
|
110
|
+
sourceEngine: 'zap',
|
|
111
|
+
sourceRuleId: pluginId,
|
|
112
|
+
createdAt: options.now,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=zap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zap.js","sourceRoot":"","sources":["../../src/import/zap.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The Secureport domain model.
|
|
3
|
+
*
|
|
4
|
+
* The tracked entity is the **issue**, not the finding. A {@link Run} produces
|
|
5
|
+
* {@link Finding}s, which are immutable evidence. Findings are fingerprinted
|
|
6
|
+
* and reconciled into {@link Issue}s, which persist across runs and carry
|
|
7
|
+
* status, severity, age and history. A {@link Snapshot} is issue state as of a
|
|
8
|
+
* run, and is the only thing a report ever reads.
|
|
9
|
+
*
|
|
10
|
+
* Zero runtime dependencies, by design: this package is imported by the hosted
|
|
11
|
+
* API and by third parties on equal terms, and a domain model should not drag
|
|
12
|
+
* anything in with it.
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
export type { Finding } from './finding.js';
|
|
17
|
+
export type { FingerprintInput, VulnKeyInput } from './fingerprint.js';
|
|
18
|
+
export { FINGERPRINT_VERSION, PATH_PLACEHOLDER, VULN_KEY_MAP, fingerprint, normaliseLocation, vulnKey, } from './fingerprint.js';
|
|
19
|
+
export type { IgnoreReason, IgnoreScope, Issue, IssueEvent, IssueEventType, IssueOrigin, IssueStatus, } from './issue.js';
|
|
20
|
+
export { coversLocation } from './coverage.js';
|
|
21
|
+
export type { ImportOptions } from './import/nuclei.js';
|
|
22
|
+
export { importNuclei } from './import/nuclei.js';
|
|
23
|
+
export { importZap } from './import/zap.js';
|
|
24
|
+
export type { BuildSnapshotInput } from './snapshot-builder.js';
|
|
25
|
+
export { buildSnapshot, parseSnapshot } from './snapshot-builder.js';
|
|
26
|
+
export type { ReconcileInput, ReconcileResult } from './reconcile.js';
|
|
27
|
+
export { reconcile } from './reconcile.js';
|
|
28
|
+
export type { Coverage, Run, RunKind, RunSummary, RunTrigger, SeverityCounts } from './run.js';
|
|
29
|
+
export type { IssueChange, Snapshot, SnapshotIssue, SuppressedIssue, Target } from './snapshot.js';
|
|
30
|
+
export type { Severity, SeveritySource, SlaPolicy, SlaStatus } from './severity.js';
|
|
31
|
+
export { DEFAULT_SLA_POLICY, SEVERITY_ORDER, SEVERITY_SOURCE_PRECEDENCE, SEVERITY_WEIGHTS, severityFromCvss, severityRank, slaDueAt, slaStatus, } from './severity.js';
|
|
3
32
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,OAAO,GACR,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,KAAK,EACL,UAAU,EACV,cAAc,EACd,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACrE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/F,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACnG,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,0BAA0B,EAC1B,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,QAAQ,EACR,SAAS,GACV,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The Secureport domain model.
|
|
3
|
+
*
|
|
4
|
+
* The tracked entity is the **issue**, not the finding. A {@link Run} produces
|
|
5
|
+
* {@link Finding}s, which are immutable evidence. Findings are fingerprinted
|
|
6
|
+
* and reconciled into {@link Issue}s, which persist across runs and carry
|
|
7
|
+
* status, severity, age and history. A {@link Snapshot} is issue state as of a
|
|
8
|
+
* run, and is the only thing a report ever reads.
|
|
9
|
+
*
|
|
10
|
+
* Zero runtime dependencies, by design: this package is imported by the hosted
|
|
11
|
+
* API and by third parties on equal terms, and a domain model should not drag
|
|
12
|
+
* anything in with it.
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
export { FINGERPRINT_VERSION, PATH_PLACEHOLDER, VULN_KEY_MAP, fingerprint, normaliseLocation, vulnKey, } from './fingerprint.js';
|
|
17
|
+
export { coversLocation } from './coverage.js';
|
|
18
|
+
export { importNuclei } from './import/nuclei.js';
|
|
19
|
+
export { importZap } from './import/zap.js';
|
|
20
|
+
export { buildSnapshot, parseSnapshot } from './snapshot-builder.js';
|
|
21
|
+
export { reconcile } from './reconcile.js';
|
|
22
|
+
export { DEFAULT_SLA_POLICY, SEVERITY_ORDER, SEVERITY_SOURCE_PRECEDENCE, SEVERITY_WEIGHTS, severityFromCvss, severityRank, slaDueAt, slaStatus, } from './severity.js';
|
|
3
23
|
//# sourceMappingURL=index.js.map
|