@secureport/core 0.3.0 → 1.0.0-rc.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.
Files changed (59) hide show
  1. package/README.md +116 -11
  2. package/dist/import/burp.d.ts +19 -0
  3. package/dist/import/burp.d.ts.map +1 -0
  4. package/dist/import/burp.js +114 -0
  5. package/dist/import/burp.js.map +1 -0
  6. package/dist/import/generic.d.ts +90 -0
  7. package/dist/import/generic.d.ts.map +1 -0
  8. package/dist/import/generic.js +159 -0
  9. package/dist/import/generic.js.map +1 -0
  10. package/dist/import/nessus.d.ts +32 -0
  11. package/dist/import/nessus.d.ts.map +1 -0
  12. package/dist/import/nessus.js +125 -0
  13. package/dist/import/nessus.js.map +1 -0
  14. package/dist/import/xml.d.ts +47 -0
  15. package/dist/import/xml.d.ts.map +1 -0
  16. package/dist/import/xml.js +157 -0
  17. package/dist/import/xml.js.map +1 -0
  18. package/dist/import/zap.d.ts +1 -1
  19. package/dist/import/zap.js +1 -1
  20. package/dist/index.d.ts +10 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +7 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/reconcile.d.ts.map +1 -1
  25. package/dist/reconcile.js +35 -0
  26. package/dist/reconcile.js.map +1 -1
  27. package/dist/report/html.d.ts +21 -0
  28. package/dist/report/html.d.ts.map +1 -0
  29. package/dist/report/html.js +324 -0
  30. package/dist/report/html.js.map +1 -0
  31. package/dist/report/json.d.ts +81 -0
  32. package/dist/report/json.d.ts.map +1 -0
  33. package/dist/report/json.js +47 -0
  34. package/dist/report/json.js.map +1 -0
  35. package/dist/report/markdown.d.ts +24 -0
  36. package/dist/report/markdown.d.ts.map +1 -0
  37. package/dist/report/markdown.js +304 -0
  38. package/dist/report/markdown.js.map +1 -0
  39. package/dist/report/model.d.ts +215 -0
  40. package/dist/report/model.d.ts.map +1 -0
  41. package/dist/report/model.js +197 -0
  42. package/dist/report/model.js.map +1 -0
  43. package/dist/snapshot-builder.d.ts +8 -0
  44. package/dist/snapshot-builder.d.ts.map +1 -1
  45. package/dist/snapshot-builder.js +25 -1
  46. package/dist/snapshot-builder.js.map +1 -1
  47. package/package.json +3 -3
  48. package/src/import/burp.ts +126 -0
  49. package/src/import/generic.ts +258 -0
  50. package/src/import/nessus.ts +136 -0
  51. package/src/import/xml.ts +187 -0
  52. package/src/import/zap.ts +1 -1
  53. package/src/index.ts +19 -0
  54. package/src/reconcile.ts +37 -0
  55. package/src/report/html.ts +449 -0
  56. package/src/report/json.ts +134 -0
  57. package/src/report/markdown.ts +435 -0
  58. package/src/report/model.ts +462 -0
  59. package/src/snapshot-builder.ts +28 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @secureport/core
2
2
 
3
- The shared domain model for [Secureport](https://github.com/agbjordan/secureport).
3
+ The shared domain model for [Secureport](https://secureport.io/).
4
4
  The hosted API imports this package the same way a third party would — one
5
5
  model, one implementation.
6
6
 
@@ -24,12 +24,54 @@ comes back after being fixed is a regression, not a discovery.
24
24
  npm install @secureport/core
25
25
  ```
26
26
 
27
+ ## What it does
28
+
29
+ Everything below is pure: no I/O, no clock, no randomness. Time and ids are
30
+ arguments, which is what makes a report reproducible and a test a golden file.
31
+
32
+ ```ts
33
+ import { buildSnapshot, importNuclei, renderMarkdown, parseSnapshot } from '@secureport/core';
34
+
35
+ const findings = importNuclei(text, { orgId, runId, targetId, now, newId });
36
+
37
+ const snapshot = buildSnapshot({
38
+ target,
39
+ run,
40
+ findings,
41
+ previous: parseSnapshot(lastTime), // optional — this is what makes a retest
42
+ now,
43
+ newId,
44
+ });
45
+
46
+ renderMarkdown(snapshot, { kind: 'retest', now });
47
+ ```
48
+
49
+ | | |
50
+ | ------------------ | -------------------------------------------------------------------------- |
51
+ | **Importers** | `importNuclei`, `importZap`, `importBurp`, `importNessus`, `importGeneric` |
52
+ | **Fingerprint** | `fingerprint`, `vulnKey`, `normaliseLocation`, `FINGERPRINT_VERSION` |
53
+ | **Reconciliation** | `reconcile` — the only thing that changes issue state |
54
+ | **Coverage** | `coversLocation` — what makes auto-resolution safe |
55
+ | **Snapshots** | `buildSnapshot`, `parseSnapshot` |
56
+ | **Reports** | `renderMarkdown`, `renderHtml`, `renderJson`, `buildReportModel` |
57
+
58
+ Rendered examples of all five report kinds are at
59
+ [secureport.io](https://secureport.io/) — the source repository is private, so
60
+ the site is where they live.
61
+
62
+ For a command line rather than an API, see
63
+ [`@secureport/cli`](https://www.npmjs.com/package/@secureport/cli):
64
+
65
+ ```sh
66
+ npx @secureport/cli report pen scan.jsonl --url https://app.example.com
67
+ ```
68
+
27
69
  ## Status
28
70
 
29
- `0.3.x` ships the domain types, the severity model and the SLA policy.
30
- Still to come, in this order: the fingerprint, `reconcile()`, `SnapshotBuilder`
31
- and the scanner importers. Until `reconcile()` lands, this package describes the
32
- model rather than computing it.
71
+ Pre-1.0. Every export is a semver contract already, and the shapes are settled
72
+ by five report templates and five importers built on them — but below `1.0.0` a
73
+ minor may still move something. See the deprecation policy below for what
74
+ changes at `1.0.0`.
33
75
 
34
76
  `0.2.x` had a placeholder `reconcile()` that matched issues by title. It is gone
35
77
  rather than deprecated — matching by title is not a simplified version of
@@ -85,11 +127,74 @@ account.
85
127
 
86
128
  The snapshot is the artefact; a PDF is a view of it.
87
129
 
88
- ## Contracts
130
+ ## Public contracts
131
+
132
+ Every export is a semver contract. Two of them are worth stating separately,
133
+ because they outlive any one version of this package.
89
134
 
90
- Every export is a semver contract. `fingerprintVersion` travels with every
91
- fingerprint everywhere, so a stored fingerprint can always be interpreted, and
92
- changing the algorithm means a migration that preserves history rather than a
93
- silent recomputation.
135
+ ### `fingerprint_version`
136
+
137
+ An issue's identity is the pair `(fingerprintVersion, fingerprint)`, not the
138
+ fingerprint alone. `FINGERPRINT_VERSION` is `fp_v1` and it travels with every
139
+ fingerprint everywhere it is stored, so a fingerprint written today can always
140
+ be interpreted — including by code that has moved on.
141
+
142
+ ```
143
+ fingerprint = sha256(targetId + '|' + vulnKey + '|' + normalisedLocation + '|' + (parameter ?? port ?? ''))
144
+ ```
94
145
 
95
- A deprecation policy lands before `1.0.0`.
146
+ `vulnKey` is the engine-independent name for a weakness: a mapping-table entry
147
+ where one exists, then the CWE, then the category, then `engine:ruleId`. That
148
+ chain is what makes two scanners reporting the same weakness one issue rather
149
+ than two.
150
+
151
+ **Changing any of this changes the version**, and a version change means a
152
+ migration that recomputes fingerprints while preserving history — `firstSeen`
153
+ is never reset — not a silent recomputation that orphans every issue. That is
154
+ the whole reason the version exists, and it is why it is a contract rather than
155
+ an implementation detail.
156
+
157
+ ### The JSON report
158
+
159
+ `renderJson` emits a `JsonReport`, versioned by `reportVersion` (currently
160
+ `1`), for consumers rendering their own view. It carries the derived answers
161
+ beside the snapshot rather than only the snapshot, and two of its fields are
162
+ there for a specific reason:
163
+
164
+ - **`basis`** — how the findings were produced, as data. A consumer must not be
165
+ able to present automated results as manual testing simply by not reading the
166
+ prose.
167
+ - **`limitations`** — what the report does not establish, as data, for the same
168
+ reason.
169
+
170
+ `reportVersion` bumps when the shape changes incompatibly. Adding a field does
171
+ not bump it, so read defensively.
172
+
173
+ ## Deprecation
174
+
175
+ Nothing that works stops working inside a major version, and nothing is removed
176
+ without having been deprecated in a release you could have seen: marked
177
+ `@deprecated` with its replacement named, left working for at least one further
178
+ minor and 90 days, then removed in a major.
179
+
180
+ | | |
181
+ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
182
+ | **Patch** | Behaviour unchanged, or a fix to behaviour that was already wrong. Never a signature change |
183
+ | **Minor** | Additions. New exports, new optional fields, new accepted input. **Adding a field to a returned object is a minor, so read defensively** |
184
+ | **Major** | Anything that could break a build or change an answer — a removed or renamed export, a narrowed input, a changed default |
185
+
186
+ Making an optional field required, or a required field optional, is a breaking
187
+ change. Anything not reachable from this package's entry point is internal,
188
+ whatever it looks like; reaching into `dist/` is not covered.
189
+
190
+ **`fingerprint_version` never changes without a migration.** That is what the
191
+ version is for: a change obliges a per-org re-fingerprint that preserves
192
+ history — `firstSeen` is never reset — rather than a silent recomputation that
193
+ orphans every issue.
194
+
195
+ **A published version is immutable.** A mistake is corrected by publishing
196
+ another one, not by changing or unpublishing that one.
197
+
198
+ **If the only correct fix for a vulnerability is a breaking change, we make it
199
+ and say so.** A policy that would rather leave a hole open than break a
200
+ signature is not one worth having.
@@ -0,0 +1,19 @@
1
+ import type { Finding } from '../finding.js';
2
+ import type { ImportOptions } from './nuclei.js';
3
+ /**
4
+ * Turns a Burp Suite XML export into {@link Finding}s.
5
+ *
6
+ * Burp exports a flat `<issues>` document, one `<issue>` per detection, with the
7
+ * host and path in separate elements — so the location is assembled rather than
8
+ * read. Prose fields arrive as CDATA containing HTML, which is stripped.
9
+ *
10
+ * `<type>` is Burp's numeric issue type and is used as the rule id, because it
11
+ * is stable across versions where the human-readable `<name>` is not.
12
+ *
13
+ * @param xml - The contents of a Burp XML export.
14
+ * @param options - Ownership, and the injected clock and id source.
15
+ * @returns One finding per issue, in document order.
16
+ * @throws SyntaxError If the document is not well-formed XML.
17
+ */
18
+ export declare function importBurp(xml: string, options: ImportOptions): Finding[];
19
+ //# sourceMappingURL=burp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"burp.d.ts","sourceRoot":"","sources":["../../src/import/burp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwCjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CA0DzE"}
@@ -0,0 +1,114 @@
1
+ import { fingerprint, vulnKey, FINGERPRINT_VERSION } from '../fingerprint.js';
2
+ import { childText, findAll, parseXml } from './xml.js';
3
+ /**
4
+ * Burp's severity vocabulary, mapped onto {@link Severity}.
5
+ *
6
+ * **Burp has no "critical" either.** Like ZAP it tops out at High, and
7
+ * `Information` is the bottom of the scale rather than a separate category.
8
+ */
9
+ const BURP_SEVERITY = Object.freeze({
10
+ high: 'high',
11
+ medium: 'medium',
12
+ low: 'low',
13
+ information: 'advisory',
14
+ info: 'advisory',
15
+ });
16
+ /**
17
+ * Burp reports confidence in words. Mapped to a number so the model does not
18
+ * have to carry a second vocabulary for it.
19
+ */
20
+ const BURP_CONFIDENCE = Object.freeze({
21
+ certain: 1,
22
+ firm: 0.7,
23
+ tentative: 0.4,
24
+ });
25
+ /**
26
+ * Burp writes the affected parameter into the location, not a field of its own.
27
+ *
28
+ * `/search [q parameter]` means the `q` parameter of `/search`. Pulling it out
29
+ * matters: the parameter is part of the fingerprint, so leaving it embedded in
30
+ * the path would make Burp's finding a different issue from ZAP's finding of the
31
+ * same weakness in the same place.
32
+ */
33
+ function splitLocation(location) {
34
+ const match = /^(.*?)\s*\[\s*(.+?)\s+parameter\s*\]\s*$/iu.exec(location);
35
+ if (match)
36
+ return { path: match[1].trim(), parameter: match[2].trim() };
37
+ return { path: location.trim() };
38
+ }
39
+ /**
40
+ * Turns a Burp Suite XML export into {@link Finding}s.
41
+ *
42
+ * Burp exports a flat `<issues>` document, one `<issue>` per detection, with the
43
+ * host and path in separate elements — so the location is assembled rather than
44
+ * read. Prose fields arrive as CDATA containing HTML, which is stripped.
45
+ *
46
+ * `<type>` is Burp's numeric issue type and is used as the rule id, because it
47
+ * is stable across versions where the human-readable `<name>` is not.
48
+ *
49
+ * @param xml - The contents of a Burp XML export.
50
+ * @param options - Ownership, and the injected clock and id source.
51
+ * @returns One finding per issue, in document order.
52
+ * @throws SyntaxError If the document is not well-formed XML.
53
+ */
54
+ export function importBurp(xml, options) {
55
+ const root = parseXml(xml);
56
+ const findings = [];
57
+ for (const issue of findAll(root, 'issue')) {
58
+ const name = childText(issue, 'name');
59
+ const type = childText(issue, 'type');
60
+ if (name === undefined || type === undefined)
61
+ continue;
62
+ const host = childText(issue, 'host') ?? '';
63
+ const raw = childText(issue, 'location') ?? childText(issue, 'path') ?? '';
64
+ const { path, parameter } = splitLocation(raw);
65
+ const location = path.startsWith('http') ? path : `${host}${path}`;
66
+ if (location === '')
67
+ continue;
68
+ const cwe = childText(issue, 'vulnerabilityClassifications')
69
+ ?.match(/CWE-\d+/iu)?.[0]
70
+ ?.toUpperCase();
71
+ const key = vulnKey({
72
+ sourceEngine: 'burp',
73
+ sourceRuleId: type,
74
+ ...(cwe === undefined ? {} : { cwe }),
75
+ });
76
+ const confidence = BURP_CONFIDENCE[childText(issue, 'confidence')?.toLowerCase() ?? ''];
77
+ const background = childText(issue, 'issueBackground');
78
+ const remediation = childText(issue, 'remediationBackground');
79
+ findings.push({
80
+ id: options.newId(),
81
+ orgId: options.orgId,
82
+ runId: options.runId,
83
+ fingerprint: fingerprint({
84
+ targetId: options.targetId,
85
+ vulnKey: key,
86
+ location,
87
+ ...(parameter === undefined ? {} : { parameter }),
88
+ }),
89
+ fingerprintVersion: FINGERPRINT_VERSION,
90
+ title: name,
91
+ ...(background === undefined ? {} : { description: stripHtml(background) }),
92
+ detectedSeverity: BURP_SEVERITY[childText(issue, 'severity')?.toLowerCase() ?? ''] ?? 'advisory',
93
+ severitySource: 'engine_default',
94
+ ...(cwe === undefined ? {} : { cwe }),
95
+ vulnKey: key,
96
+ location,
97
+ ...(parameter === undefined ? {} : { parameter }),
98
+ ...(remediation === undefined ? {} : { recommendation: stripHtml(remediation) }),
99
+ sourceEngine: 'burp',
100
+ sourceRuleId: type,
101
+ ...(confidence === undefined ? {} : { confidence }),
102
+ createdAt: options.now,
103
+ });
104
+ }
105
+ return findings;
106
+ }
107
+ /** Burp's prose fields are HTML inside CDATA. */
108
+ function stripHtml(value) {
109
+ return value
110
+ .replace(/<[^>]*>/gu, ' ')
111
+ .replace(/\s+/gu, ' ')
112
+ .trim();
113
+ }
114
+ //# sourceMappingURL=burp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"burp.js","sourceRoot":"","sources":["../../src/import/burp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import type { Finding } from '../finding.js';
2
+ import type { Severity } from '../severity.js';
3
+ import type { ImportOptions } from './nuclei.js';
4
+ /**
5
+ * One finding in the generic Secureport format.
6
+ *
7
+ * **Written last, on purpose.** This is the shape the four scanner importers
8
+ * turned out to have in common, rather than a format designed in advance and
9
+ * then argued with. Everything an engine reliably supplies is here; everything
10
+ * only one of them had is not.
11
+ *
12
+ * Only `title` and `location` are required. A tool that knows nothing but "this
13
+ * is wrong, and it is here" can still produce a trackable finding — which is the
14
+ * point of having a generic format at all.
15
+ */
16
+ export interface GenericFinding {
17
+ /** What is wrong. */
18
+ readonly title: string;
19
+ /** Where it is — a URL, a host, a `host:port`, or a file path. */
20
+ readonly location: string;
21
+ /** Fuller explanation. */
22
+ readonly description?: string;
23
+ /**
24
+ * Severity, if the tool rates it.
25
+ *
26
+ * Ignored when `cvssScore` is present: a score is more precise than a word,
27
+ * and `severitySource` records which was used.
28
+ */
29
+ readonly severity?: Severity;
30
+ /** CVSS base score, 0–10. Preferred over `severity` when both are given. */
31
+ readonly cvssScore?: number;
32
+ /** CVSS vector string. */
33
+ readonly cvssVector?: string;
34
+ /** CWE identifier, with or without the `CWE-` prefix. */
35
+ readonly cwe?: string;
36
+ /** CVE identifier. */
37
+ readonly cve?: string;
38
+ /** Broad grouping, e.g. `injection`. */
39
+ readonly category?: string;
40
+ /** The parameter implicated, where the weakness has one. */
41
+ readonly parameter?: string;
42
+ /** The port, for findings about a service rather than a path. */
43
+ readonly port?: number;
44
+ /** What produced it. Defaults to `generic`. */
45
+ readonly engine?: string;
46
+ /** The producing tool's own identifier for the rule. */
47
+ readonly ruleId?: string;
48
+ /** What to do about it. */
49
+ readonly recommendation?: string;
50
+ /** Further reading. */
51
+ readonly references?: readonly string[];
52
+ /** Pointers to stored evidence. */
53
+ readonly evidenceUri?: readonly string[];
54
+ /** The tool's confidence, 0–1. */
55
+ readonly confidence?: number;
56
+ }
57
+ /** A generic findings document. */
58
+ export interface GenericDocument {
59
+ /**
60
+ * Format version.
61
+ *
62
+ * Present so this file can change shape later without guessing. An unknown
63
+ * version is refused rather than parsed optimistically.
64
+ */
65
+ readonly version: 1;
66
+ /** The findings. */
67
+ readonly findings: readonly GenericFinding[];
68
+ }
69
+ /**
70
+ * Turns the generic Secureport format into {@link Finding}s.
71
+ *
72
+ * The escape hatch for everything with no importer of its own: a manual test, an
73
+ * internal tool, a scanner nobody has written a parser for. Fingerprinting,
74
+ * reconciliation and reports then work identically — a finding that arrives this
75
+ * way is not a second-class finding.
76
+ *
77
+ * Refuses the document rather than salvaging part of it. Unlike Nuclei's JSONL,
78
+ * where one bad line costs one record, this is a single document a human or a
79
+ * script wrote deliberately: a field in the wrong shape is a mistake worth
80
+ * hearing about, not one to route around silently.
81
+ *
82
+ * @param json - The contents of a generic findings document.
83
+ * @param options - Ownership, and the injected clock and id source.
84
+ * @returns One finding per entry, in document order.
85
+ * @throws SyntaxError If the text is not valid JSON.
86
+ * @throws TypeError If the document is not the expected shape, naming the entry
87
+ * and field at fault.
88
+ */
89
+ export declare function importGeneric(json: string, options: ImportOptions): Finding[];
90
+ //# sourceMappingURL=generic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generic.d.ts","sourceRoot":"","sources":["../../src/import/generic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B,qBAAqB;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAE7B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,0BAA0B;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEtB,sBAAsB;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEtB,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEvB,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,2BAA2B;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC,uBAAuB;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAExC,mCAAmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzC,kCAAkC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpB,oBAAoB;IACpB,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAuCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CA8G7E"}
@@ -0,0 +1,159 @@
1
+ import { SEVERITY_ORDER, severityFromCvss } from '../severity.js';
2
+ import { fingerprint, vulnKey, FINGERPRINT_VERSION } from '../fingerprint.js';
3
+ const SEVERITIES = new Set(SEVERITY_ORDER);
4
+ /** A non-empty trimmed string, or nothing. */
5
+ const str = (v) => typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined;
6
+ /** A finite number, or nothing. */
7
+ const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
8
+ /**
9
+ * A title reduced to something usable as a weakness key.
10
+ *
11
+ * The last resort, and only for the generic format. A tool that reports nothing
12
+ * but "this is wrong, and it is here" has no CWE, no category and no rule id —
13
+ * and `vulnKey` would rightly refuse, because there is nothing to key on.
14
+ * Refusing would mean the minimal case cannot be imported at all, which defeats
15
+ * the point of having a generic format.
16
+ *
17
+ * **Titles are a poor key and this is not pretending otherwise:** reword the
18
+ * title and the issue becomes a different issue, and two tools describing the
19
+ * same weakness differently never collide. Supplying a `ruleId`, a `cwe` or a
20
+ * `category` is strictly better and all three are preferred over this.
21
+ */
22
+ const titleKey = (title) => title
23
+ .toLowerCase()
24
+ .replace(/[^a-z0-9]+/gu, '-')
25
+ .replace(/^-|-$/gu, '');
26
+ /** An array of non-empty strings, or nothing. */
27
+ const strArray = (v) => {
28
+ if (!Array.isArray(v))
29
+ return undefined;
30
+ const items = v.map(str).filter((x) => x !== undefined);
31
+ return items.length > 0 ? items : undefined;
32
+ };
33
+ /**
34
+ * Turns the generic Secureport format into {@link Finding}s.
35
+ *
36
+ * The escape hatch for everything with no importer of its own: a manual test, an
37
+ * internal tool, a scanner nobody has written a parser for. Fingerprinting,
38
+ * reconciliation and reports then work identically — a finding that arrives this
39
+ * way is not a second-class finding.
40
+ *
41
+ * Refuses the document rather than salvaging part of it. Unlike Nuclei's JSONL,
42
+ * where one bad line costs one record, this is a single document a human or a
43
+ * script wrote deliberately: a field in the wrong shape is a mistake worth
44
+ * hearing about, not one to route around silently.
45
+ *
46
+ * @param json - The contents of a generic findings document.
47
+ * @param options - Ownership, and the injected clock and id source.
48
+ * @returns One finding per entry, in document order.
49
+ * @throws SyntaxError If the text is not valid JSON.
50
+ * @throws TypeError If the document is not the expected shape, naming the entry
51
+ * and field at fault.
52
+ */
53
+ export function importGeneric(json, options) {
54
+ const doc = JSON.parse(json);
55
+ if (typeof doc !== 'object' || doc === null) {
56
+ throw new TypeError('generic findings document must be an object');
57
+ }
58
+ const record = doc;
59
+ if (record['version'] !== 1) {
60
+ throw new TypeError(`unsupported generic findings version: ${String(record['version'])}`);
61
+ }
62
+ const raw = record['findings'];
63
+ if (!Array.isArray(raw)) {
64
+ throw new TypeError('generic findings document has no `findings` array');
65
+ }
66
+ return raw.map((item, index) => {
67
+ const where = `findings[${String(index)}]`;
68
+ if (typeof item !== 'object' || item === null) {
69
+ throw new TypeError(`${where} is not an object`);
70
+ }
71
+ const entry = item;
72
+ const title = str(entry['title']);
73
+ const location = str(entry['location']);
74
+ if (title === undefined)
75
+ throw new TypeError(`${where}.title is required`);
76
+ if (location === undefined)
77
+ throw new TypeError(`${where}.location is required`);
78
+ const stated = str(entry['severity']);
79
+ if (stated !== undefined && !SEVERITIES.has(stated)) {
80
+ throw new TypeError(`${where}.severity is not a severity: ${stated}`);
81
+ }
82
+ const score = num(entry['cvssScore']);
83
+ let detectedSeverity;
84
+ let severitySource;
85
+ if (score !== undefined && score >= 0 && score <= 10) {
86
+ detectedSeverity = severityFromCvss(score);
87
+ severitySource = 'cvss';
88
+ }
89
+ else if (stated !== undefined) {
90
+ detectedSeverity = stated;
91
+ // Somebody stated it for this finding specifically, which is the
92
+ // strongest provenance there is.
93
+ severitySource = 'explicit';
94
+ }
95
+ else {
96
+ detectedSeverity = 'advisory';
97
+ severitySource = 'engine_default';
98
+ }
99
+ const engine = str(entry['engine']) ?? 'generic';
100
+ const ruleId = str(entry['ruleId']);
101
+ const rawCwe = str(entry['cwe']);
102
+ const cwe = rawCwe === undefined ? undefined : `CWE-${rawCwe.replace(/^CWE-/iu, '')}`;
103
+ const category = str(entry['category']);
104
+ const parameter = str(entry['parameter']);
105
+ const port = num(entry['port']);
106
+ // Nothing classifies this finding, so key on the title rather than refuse
107
+ // it. `sourceRuleId` stays absent: the tool supplied none, and recording a
108
+ // slug as though it had would be a lie about provenance.
109
+ const key = cwe === undefined && category === undefined && ruleId === undefined
110
+ ? `${engine}:${titleKey(title)}`
111
+ : vulnKey({
112
+ sourceEngine: engine,
113
+ ...(ruleId === undefined ? {} : { sourceRuleId: ruleId }),
114
+ ...(cwe === undefined ? {} : { cwe }),
115
+ ...(category === undefined ? {} : { category }),
116
+ });
117
+ return {
118
+ id: options.newId(),
119
+ orgId: options.orgId,
120
+ runId: options.runId,
121
+ fingerprint: fingerprint({
122
+ targetId: options.targetId,
123
+ vulnKey: key,
124
+ location,
125
+ ...(parameter === undefined ? {} : { parameter }),
126
+ ...(port === undefined ? {} : { port }),
127
+ }),
128
+ fingerprintVersion: FINGERPRINT_VERSION,
129
+ title,
130
+ ...(str(entry['description']) === undefined
131
+ ? {}
132
+ : { description: str(entry['description']) }),
133
+ detectedSeverity,
134
+ severitySource,
135
+ ...(score === undefined ? {} : { cvssScore: score }),
136
+ ...(str(entry['cvssVector']) === undefined ? {} : { cvssVector: str(entry['cvssVector']) }),
137
+ ...(cwe === undefined ? {} : { cwe }),
138
+ ...(str(entry['cve']) === undefined ? {} : { cve: str(entry['cve']).toUpperCase() }),
139
+ vulnKey: key,
140
+ ...(category === undefined ? {} : { category: category.toLowerCase() }),
141
+ location,
142
+ ...(parameter === undefined ? {} : { parameter }),
143
+ ...(strArray(entry['evidenceUri']) === undefined
144
+ ? {}
145
+ : { evidenceUri: strArray(entry['evidenceUri']) }),
146
+ ...(str(entry['recommendation']) === undefined
147
+ ? {}
148
+ : { recommendation: str(entry['recommendation']) }),
149
+ ...(strArray(entry['references']) === undefined
150
+ ? {}
151
+ : { references: strArray(entry['references']) }),
152
+ sourceEngine: engine,
153
+ ...(ruleId === undefined ? {} : { sourceRuleId: ruleId }),
154
+ ...(num(entry['confidence']) === undefined ? {} : { confidence: num(entry['confidence']) }),
155
+ createdAt: options.now,
156
+ };
157
+ });
158
+ }
159
+ //# sourceMappingURL=generic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generic.js","sourceRoot":"","sources":["../../src/import/generic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { Finding } from '../finding.js';
2
+ import type { ImportOptions } from './nuclei.js';
3
+ /**
4
+ * Turns a `.nessus` export into {@link Finding}s.
5
+ *
6
+ * Nessus is host-oriented: every `ReportItem` hangs off a `ReportHost`, and the
7
+ * port and protocol are attributes rather than part of a URL. The location is
8
+ * assembled as `host:port`, which is **the only place the port survives** —
9
+ * `00-DOMAIN.md` §3 defines no `port` on a finding, so anything not in the
10
+ * location is lost. A network finding with no path still fingerprints
11
+ * distinctly per service because the port is inside its location.
12
+ *
13
+ * A consequence worth knowing: the fingerprint's last component is
14
+ * `parameter ?? port ?? ''`, so a Nessus port is counted twice — once inside
15
+ * the location and once as that component. Harmless, but it means a port
16
+ * cannot be recovered from a finding except by parsing its location. Recorded
17
+ * as backlog **B42**.
18
+ *
19
+ * Severity comes from the CVSS v3 base score where Nessus supplies one, and from
20
+ * its own numeric rating otherwise.
21
+ *
22
+ * A `ReportItem` with severity `0` and no port — Nessus's host-inventory
23
+ * plugins — still imports: an advisory is still a finding, and dropping it here
24
+ * would make coverage look narrower than it was.
25
+ *
26
+ * @param xml - The contents of a `.nessus` file.
27
+ * @param options - Ownership, and the injected clock and id source.
28
+ * @returns One finding per report item, in document order.
29
+ * @throws SyntaxError If the document is not well-formed XML.
30
+ */
31
+ export declare function importNessus(xml: string, options: ImportOptions): Finding[];
32
+ //# sourceMappingURL=nessus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nessus.d.ts","sourceRoot":"","sources":["../../src/import/nessus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAK7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgBjD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,EAAE,CAsF3E"}