@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.
Files changed (100) hide show
  1. package/README.md +146 -33
  2. package/dist/coverage.d.ts +21 -0
  3. package/dist/coverage.d.ts.map +1 -0
  4. package/dist/coverage.js +65 -0
  5. package/dist/coverage.js.map +1 -0
  6. package/dist/finding.d.ts +89 -0
  7. package/dist/finding.d.ts.map +1 -0
  8. package/dist/finding.js +2 -0
  9. package/dist/finding.js.map +1 -0
  10. package/dist/fingerprint.d.ts +185 -0
  11. package/dist/fingerprint.d.ts.map +1 -0
  12. package/dist/fingerprint.js +247 -0
  13. package/dist/fingerprint.js.map +1 -0
  14. package/dist/import/burp.d.ts +19 -0
  15. package/dist/import/burp.d.ts.map +1 -0
  16. package/dist/import/burp.js +114 -0
  17. package/dist/import/burp.js.map +1 -0
  18. package/dist/import/generic.d.ts +90 -0
  19. package/dist/import/generic.d.ts.map +1 -0
  20. package/dist/import/generic.js +159 -0
  21. package/dist/import/generic.js.map +1 -0
  22. package/dist/import/nessus.d.ts +32 -0
  23. package/dist/import/nessus.d.ts.map +1 -0
  24. package/dist/import/nessus.js +125 -0
  25. package/dist/import/nessus.js.map +1 -0
  26. package/dist/import/nuclei.d.ts +39 -0
  27. package/dist/import/nuclei.d.ts.map +1 -0
  28. package/dist/import/nuclei.js +115 -0
  29. package/dist/import/nuclei.js.map +1 -0
  30. package/dist/import/xml.d.ts +47 -0
  31. package/dist/import/xml.d.ts.map +1 -0
  32. package/dist/import/xml.js +157 -0
  33. package/dist/import/xml.js.map +1 -0
  34. package/dist/import/zap.d.ts +26 -0
  35. package/dist/import/zap.d.ts.map +1 -0
  36. package/dist/import/zap.js +119 -0
  37. package/dist/import/zap.js.map +1 -0
  38. package/dist/index.d.ts +41 -2
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +29 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/issue.d.ts +190 -11
  43. package/dist/issue.d.ts.map +1 -1
  44. package/dist/reconcile.d.ts +115 -10
  45. package/dist/reconcile.d.ts.map +1 -1
  46. package/dist/reconcile.js +306 -12
  47. package/dist/reconcile.js.map +1 -1
  48. package/dist/report/html.d.ts +21 -0
  49. package/dist/report/html.d.ts.map +1 -0
  50. package/dist/report/html.js +324 -0
  51. package/dist/report/html.js.map +1 -0
  52. package/dist/report/json.d.ts +81 -0
  53. package/dist/report/json.d.ts.map +1 -0
  54. package/dist/report/json.js +47 -0
  55. package/dist/report/json.js.map +1 -0
  56. package/dist/report/markdown.d.ts +24 -0
  57. package/dist/report/markdown.d.ts.map +1 -0
  58. package/dist/report/markdown.js +304 -0
  59. package/dist/report/markdown.js.map +1 -0
  60. package/dist/report/model.d.ts +215 -0
  61. package/dist/report/model.d.ts.map +1 -0
  62. package/dist/report/model.js +197 -0
  63. package/dist/report/model.js.map +1 -0
  64. package/dist/run.d.ts +134 -0
  65. package/dist/run.d.ts.map +1 -0
  66. package/dist/run.js +2 -0
  67. package/dist/run.js.map +1 -0
  68. package/dist/severity.d.ts +191 -0
  69. package/dist/severity.d.ts.map +1 -0
  70. package/dist/severity.js +171 -0
  71. package/dist/severity.js.map +1 -0
  72. package/dist/snapshot-builder.d.ts +78 -0
  73. package/dist/snapshot-builder.d.ts.map +1 -0
  74. package/dist/snapshot-builder.js +172 -0
  75. package/dist/snapshot-builder.js.map +1 -0
  76. package/dist/snapshot.d.ts +125 -0
  77. package/dist/snapshot.d.ts.map +1 -0
  78. package/dist/snapshot.js +2 -0
  79. package/dist/snapshot.js.map +1 -0
  80. package/package.json +5 -4
  81. package/src/coverage.ts +65 -0
  82. package/src/finding.ts +112 -0
  83. package/src/fingerprint.ts +315 -0
  84. package/src/import/burp.ts +126 -0
  85. package/src/import/generic.ts +258 -0
  86. package/src/import/nessus.ts +136 -0
  87. package/src/import/nuclei.ts +173 -0
  88. package/src/import/xml.ts +187 -0
  89. package/src/import/zap.ts +161 -0
  90. package/src/index.ts +75 -2
  91. package/src/issue.ts +244 -11
  92. package/src/reconcile.ts +421 -17
  93. package/src/report/html.ts +449 -0
  94. package/src/report/json.ts +134 -0
  95. package/src/report/markdown.ts +435 -0
  96. package/src/report/model.ts +462 -0
  97. package/src/run.ts +163 -0
  98. package/src/severity.ts +250 -0
  99. package/src/snapshot-builder.ts +225 -0
  100. package/src/snapshot.ts +146 -0
@@ -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 `ZAP_RISK` below 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,42 @@
1
- export * from './issue.js';
2
- export * from './reconcile.js';
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 { importBurp } from './import/burp.js';
25
+ export { importNessus } from './import/nessus.js';
26
+ export type { GenericDocument, GenericFinding } from './import/generic.js';
27
+ export { importGeneric } from './import/generic.js';
28
+ export type { BuildSnapshotInput } from './snapshot-builder.js';
29
+ export { buildSnapshot, parseSnapshot } from './snapshot-builder.js';
30
+ export type { ReportKind, ReportModel, ReportOptions, ReportSection, RetestEntry, RetestOutcome, RetestVerdict, TestingBasis, } from './report/model.js';
31
+ export { buildReportModel } from './report/model.js';
32
+ export { renderMarkdown } from './report/markdown.js';
33
+ export { renderHtml } from './report/html.js';
34
+ export type { JsonReport } from './report/json.js';
35
+ export { renderJson } from './report/json.js';
36
+ export type { ReconcileInput, ReconcileResult } from './reconcile.js';
37
+ export { reconcile } from './reconcile.js';
38
+ export type { Coverage, Run, RunKind, RunSummary, RunTrigger, SeverityCounts } from './run.js';
39
+ export type { IssueChange, Snapshot, SnapshotIssue, SuppressedIssue, Target } from './snapshot.js';
40
+ export type { Severity, SeveritySource, SlaPolicy, SlaStatus } from './severity.js';
41
+ export { DEFAULT_SLA_POLICY, SEVERITY_ORDER, SEVERITY_SOURCE_PRECEDENCE, SEVERITY_WEIGHTS, severityFromCvss, severityRank, slaDueAt, slaStatus, } from './severity.js';
3
42
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,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,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACrE,YAAY,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,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,30 @@
1
- export * from './issue.js';
2
- export * from './reconcile.js';
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 { importBurp } from './import/burp.js';
21
+ export { importNessus } from './import/nessus.js';
22
+ export { importGeneric } from './import/generic.js';
23
+ export { buildSnapshot, parseSnapshot } from './snapshot-builder.js';
24
+ export { buildReportModel } from './report/model.js';
25
+ export { renderMarkdown } from './report/markdown.js';
26
+ export { renderHtml } from './report/html.js';
27
+ export { renderJson } from './report/json.js';
28
+ export { reconcile } from './reconcile.js';
29
+ export { DEFAULT_SLA_POLICY, SEVERITY_ORDER, SEVERITY_SOURCE_PRECEDENCE, SEVERITY_WEIGHTS, severityFromCvss, severityRank, slaDueAt, slaStatus, } from './severity.js';
3
30
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,OAAO,GACR,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAWrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAI3C,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,0BAA0B,EAC1B,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,QAAQ,EACR,SAAS,GACV,MAAM,eAAe,CAAC"}
package/dist/issue.d.ts CHANGED
@@ -1,16 +1,195 @@
1
- /** Severity of an {@link Issue}, ordered from most to least urgent by convention. */
2
- export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
3
- /** Lifecycle state of an {@link Issue}. */
4
- export type IssueStatus = 'open' | 'triaging' | 'accepted' | 'resolved';
1
+ import type { Severity } from './severity.js';
2
+ import type { RunKind } from './run.js';
5
3
  /**
6
- * A tracked issue — the entity Secureport reports on. One or more findings
7
- * from one or more scanners can be merged into a single issue; the issue,
8
- * not the finding, is what carries status and severity through review.
4
+ * Where an issue is in its lifecycle.
5
+ *
6
+ * - `open` — currently detected, or detected recently enough not to be resolved.
7
+ * - `resolved` — no longer detected by runs that covered it.
8
+ * - `regressed` — was resolved, and has come back. Distinct from `open` because
9
+ * a returning issue is a different and more interesting event than a new one.
10
+ * - `ignored` — deliberately suppressed, with a reason and an author.
11
+ *
12
+ * There is no `triaging` state. Triage is a person's activity, not an issue's
13
+ * condition, and a status nobody can define precisely is a status nobody
14
+ * filters on correctly.
15
+ */
16
+ export type IssueStatus = 'open' | 'resolved' | 'regressed' | 'ignored';
17
+ /**
18
+ * How an issue came into existence.
19
+ *
20
+ * Matters because **manual-origin issues never auto-resolve** (invariant 6): a
21
+ * human closes what a human opened.
22
+ */
23
+ export type IssueOrigin = RunKind;
24
+ /**
25
+ * Why an issue was suppressed.
26
+ *
27
+ * A fixed set rather than free text because this list is what a
28
+ * suppressed-findings appendix is grouped by, and an auditor reading it needs
29
+ * categories that mean the same thing every time.
30
+ */
31
+ export type IgnoreReason = 'false_positive' | 'accepted_risk' | 'out_of_scope' | 'duplicate';
32
+ /**
33
+ * How widely an ignore applies.
34
+ *
35
+ * - `target` — this issue on this target only. The default.
36
+ * - `org` — the same weakness anywhere in the organisation, matched on
37
+ * `vulnKey` and normalised location, ignoring the parameter.
38
+ */
39
+ export type IgnoreScope = 'target' | 'org';
40
+ /**
41
+ * The tracked record that findings reconcile into, and the entity the product
42
+ * is actually about.
43
+ *
44
+ * An issue persists across runs. It carries status, severity, age, ownership
45
+ * and history; findings are the evidence beneath it. The distinction is the
46
+ * whole point of the model: two scanners reporting the same weakness produce
47
+ * one issue with two sources, not two rows, and an issue that comes back after
48
+ * being fixed is the same issue regressing rather than a new discovery.
49
+ *
50
+ * Unique on `(orgId, targetId, fingerprint)`.
51
+ *
52
+ * **Issue state changes only through reconciliation or an issue-service
53
+ * method, and every change emits an {@link IssueEvent}** (invariant 2).
9
54
  */
10
55
  export interface Issue {
11
- id: string;
12
- title: string;
13
- severity: Severity;
14
- status: IssueStatus;
56
+ /** Unique id. */
57
+ readonly id: string;
58
+ /** Organisation this issue belongs to. */
59
+ readonly orgId: string;
60
+ /**
61
+ * Target this issue is on.
62
+ *
63
+ * Part of the identity, and deliberately so: because the fingerprint includes
64
+ * the target, **the same weakness in staging and in production is two
65
+ * issues**. They are two systems, fixed separately, and verifying one must
66
+ * never authorise the other.
67
+ */
68
+ readonly targetId: string;
69
+ /** Content address of the weakness. Stable across runs and engines. */
70
+ readonly fingerprint: string;
71
+ /** Which algorithm produced {@link Issue.fingerprint}. */
72
+ readonly fingerprintVersion: string;
73
+ /** Human-readable name. Seeded from the first finding; editable afterwards. */
74
+ readonly title: string;
75
+ /** Engine-independent key for the weakness class. */
76
+ readonly vulnKey: string;
77
+ /** CWE identifier, where one applies. */
78
+ readonly cwe?: string;
79
+ /** Where the weakness is. */
80
+ readonly location: string;
81
+ /** The specific parameter implicated, where there is one. */
82
+ readonly parameter?: string;
83
+ /** Lifecycle state. */
84
+ readonly status: IssueStatus;
85
+ /**
86
+ * Severity as most recently detected.
87
+ *
88
+ * Follows the evidence. Changing it emits `severity_detected_changed`.
89
+ */
90
+ readonly detectedSeverity: Severity;
91
+ /**
92
+ * Severity that reports show, and that the SLA deadline derives from.
93
+ *
94
+ * Follows {@link Issue.detectedSeverity} unless a human has overridden it. A
95
+ * null override means the two move together.
96
+ */
97
+ readonly effectiveSeverity: Severity;
98
+ /** Why the severity was overridden. Present only when it was. */
99
+ readonly severityOverrideReason?: string;
100
+ /** Who overrode it. */
101
+ readonly severityOverriddenBy?: string;
102
+ /** When it was overridden. */
103
+ readonly severityOverriddenAt?: Date;
104
+ /**
105
+ * When this issue was first seen.
106
+ *
107
+ * **Never reset** (invariant 4) — not by a regression, not by a severity
108
+ * change, not by anything. It is what "how long has this been open" means,
109
+ * and resetting it would quietly erase the age of the oldest problems.
110
+ */
111
+ readonly firstSeen: Date;
112
+ /** When it was most recently detected. Keeps updating even while ignored. */
113
+ readonly lastSeen: Date;
114
+ /** When it was resolved, if it has been. */
115
+ readonly resolvedAt?: Date;
116
+ /** When it most recently regressed, if it has. */
117
+ readonly reopenedAt?: Date;
118
+ /**
119
+ * Consecutive covering runs that did not detect this issue.
120
+ *
121
+ * The auto-resolve counter, and the flapping guard: a single missed
122
+ * detection from a timeout or a rate limit should not close an issue. Reset
123
+ * to zero the moment the issue is seen again.
124
+ */
125
+ readonly consecutiveMisses: number;
126
+ /** How the issue came into existence. */
127
+ readonly origin: IssueOrigin;
128
+ /** Who it is assigned to, if anyone. */
129
+ readonly assignee?: string;
130
+ /** Key in an external tracker, set by the export. */
131
+ readonly externalRef?: string;
132
+ /** Why it is suppressed. Present only while `status` is `ignored`. */
133
+ readonly ignoreReason?: IgnoreReason;
134
+ /** Free-text justification for the suppression. Required when ignoring. */
135
+ readonly ignoreComment?: string;
136
+ /** How widely the suppression applies. */
137
+ readonly ignoreScope?: IgnoreScope;
138
+ /** When the suppression lapses, after which the issue re-surfaces. */
139
+ readonly ignoreExpiresAt?: Date;
140
+ /** Who suppressed it. */
141
+ readonly ignoredBy?: string;
142
+ /**
143
+ * Detected severity at the moment it was ignored.
144
+ *
145
+ * Recorded so that a *detected increase* re-surfaces the issue
146
+ * automatically: accepting the risk of a medium is not accepting the risk of
147
+ * the critical it later turns out to be.
148
+ */
149
+ readonly severityAtIgnore?: Severity;
150
+ /**
151
+ * When remediation is due, derived from {@link Issue.effectiveSeverity}.
152
+ *
153
+ * Recomputed whenever the effective severity changes. `null` where the
154
+ * severity carries no deadline.
155
+ */
156
+ readonly slaDueAt?: Date | null;
157
+ }
158
+ /**
159
+ * Something that happened to an issue.
160
+ *
161
+ * The audit trail and the source of every analytic. Nothing scans findings at
162
+ * request time: "issues resolved this quarter" and "regressions caught" are
163
+ * both counts over this log.
164
+ */
165
+ export type IssueEventType = 'created' | 'seen' | 'severity_detected_changed' | 'severity_overridden' | 'resolved' | 'reopened' | 'ignored' | 'unignored' | 'commented' | 'assigned' | 'merged' | 'exported';
166
+ /**
167
+ * An append-only record of a single change to an issue.
168
+ *
169
+ * Append-only is load-bearing rather than stylistic: it is what makes the
170
+ * history trustworthy as evidence, and what lets analytics be a query rather
171
+ * than a recomputation.
172
+ */
173
+ export interface IssueEvent {
174
+ /** Unique id. */
175
+ readonly id: string;
176
+ /** Organisation this event belongs to. */
177
+ readonly orgId: string;
178
+ /** The issue it happened to. */
179
+ readonly issueId: string;
180
+ /** The run that caused it, where a run did. Absent for human actions. */
181
+ readonly runId?: string;
182
+ /** What happened. */
183
+ readonly type: IssueEventType;
184
+ /**
185
+ * Who or what did it — a user id, an API key id, or the reconciler.
186
+ *
187
+ * Never optional: an audit trail that cannot say who is not one.
188
+ */
189
+ readonly actor: string;
190
+ /** Type-specific detail, e.g. the old and new severity. */
191
+ readonly payload?: Readonly<Record<string, unknown>>;
192
+ /** When it happened. */
193
+ readonly createdAt: Date;
15
194
  }
16
195
  //# sourceMappingURL=issue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvE,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAExE;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;CACrB"}
1
+ {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,120 @@
1
- import type { Issue } from './issue.js';
2
- /** A raw scanner result, not yet folded into the tracked issue set. */
3
- export interface Finding {
4
- title: string;
5
- severity: Issue['severity'];
1
+ import type { Finding } from './finding.js';
2
+ import type { Issue, IssueEvent } from './issue.js';
3
+ import type { Run, RunSummary } from './run.js';
4
+ import { type SlaPolicy } from './severity.js';
5
+ /**
6
+ * Everything reconciliation needs, and nothing it could get for itself.
7
+ *
8
+ * The clock and the id generator are arguments because the function is pure:
9
+ * no `Date.now()`, no `randomUUID()`, no I/O. The same inputs give the same
10
+ * issues and the same events, on any machine, which is what makes golden-file
11
+ * testing possible and what lets a run be replayed.
12
+ */
13
+ export interface ReconcileInput {
14
+ /**
15
+ * Every known issue for this run's target.
16
+ *
17
+ * Issues for other targets must not be passed in: the fingerprint already
18
+ * includes the target, so they could never match, and including them would
19
+ * return them as untouched output and invite a caller to write them back.
20
+ */
21
+ readonly issues: readonly Issue[];
22
+ /** The findings this run produced. */
23
+ readonly findings: readonly Finding[];
24
+ /** The run itself, which supplies the target, the kind and the coverage. */
25
+ readonly run: Run;
26
+ /** The moment the run is being reconciled at. */
27
+ readonly now: Date;
28
+ /**
29
+ * Supplies ids for issues and events.
30
+ *
31
+ * Injected rather than generated so the function stays deterministic. A
32
+ * caller in production passes `randomUUID`; a test passes a counter.
33
+ */
34
+ readonly newId: () => string;
35
+ /**
36
+ * Who to record as having made these changes.
37
+ *
38
+ * Defaults to `reconciler`. An audit trail that cannot say who is not one.
39
+ */
40
+ readonly actor?: string;
41
+ /** Remediation windows, for recomputing `slaDueAt`. */
42
+ readonly slaPolicy?: SlaPolicy;
43
+ /**
44
+ * How many consecutive covering runs may miss an issue before it resolves.
45
+ *
46
+ * **The flapping guard.** Defaults to **1 for uploads** — the person
47
+ * uploading is asserting a complete result — and **2 for scans**, where a
48
+ * timeout or a rate limit causes a miss that means nothing. Between the first
49
+ * miss and resolution the issue stays open, and reports can say "not seen in
50
+ * last run".
51
+ *
52
+ * Per organisation in the hosted product; an argument here, because this
53
+ * function has nowhere to read configuration from.
54
+ */
55
+ readonly autoResolveThreshold?: number;
56
+ /** How long the run took, for the summary. */
57
+ readonly durationMs?: number;
58
+ }
59
+ /**
60
+ * What reconciliation decided.
61
+ *
62
+ * Returns **every** issue for the target, changed or not, so a caller can write
63
+ * the set back without working out which ones moved. Events are returned rather
64
+ * than applied: persisting them is the caller's transaction, and reconciliation
65
+ * has no I/O.
66
+ */
67
+ export interface ReconcileResult {
68
+ /** Every issue for the target, after reconciliation. */
69
+ readonly issues: readonly Issue[];
70
+ /** What changed, in the order it was decided. */
71
+ readonly events: readonly IssueEvent[];
72
+ /** What the run did, in the terms a report opens with. */
73
+ readonly summary: RunSummary;
6
74
  }
7
75
  /**
8
- * Merges incoming findings into the tracked issue set. A finding whose title
9
- * matches an open issue is folded into it; anything new opens an issue.
76
+ * Turns a run's findings into issue state.
77
+ *
78
+ * **The only writer of issue state, and a pure function.** Given the same
79
+ * issues, findings, run and clock it produces the same result every time. It
80
+ * performs no I/O, reads no clock and generates no randomness; the caller
81
+ * supplies `now` and `newId` and persists what comes back.
82
+ *
83
+ * This half handles everything a **present** finding causes:
84
+ *
85
+ * - a fingerprint that matches nothing opens an issue (`created`);
86
+ * - a fingerprint that matches attaches evidence and refreshes `lastSeen`,
87
+ * clearing the miss counter (`seen`);
88
+ * - a `resolved` issue seen again becomes `regressed` (`reopened`) — a returning
89
+ * problem is a different and more interesting event than a new one;
90
+ * - a changed detected severity updates the issue, and updates the *effective*
91
+ * severity and the SLA deadline only where nobody has overridden it
92
+ * (`severity_detected_changed`);
93
+ * - an `ignored` issue whose detected severity has risen above what was
94
+ * accepted comes back (`unignored`) — accepting the risk of a medium is not
95
+ * accepting the risk of the critical it turned out to be.
96
+ *
97
+ * And everything an **absent** finding causes:
98
+ *
99
+ * - an issue the run covered but did not see takes a miss, and resolves once it
100
+ * has missed enough consecutive covering runs (`resolved`);
101
+ * - an issue **outside** the run's coverage is untouched — not resolved, and not
102
+ * even counted as a miss, so a sequence of narrow scans cannot accumulate
103
+ * misses against a path none of them looked at (invariant 5);
104
+ * - a **manual-origin** issue never auto-resolves; a human closes what a human
105
+ * opened (invariant 6);
106
+ * - a suppression whose expiry has passed lapses (`unignored`), regardless of
107
+ * coverage — an ignore expiring is a decision timing out, not an observation.
108
+ *
109
+ * Finally it computes the {@link RunSummary}: counts by severity for new, still
110
+ * open, resolved, regressed and ignored, plus the exposure score. Ignored issues
111
+ * are excluded from every count but their own, and from exposure, and never from
112
+ * the suppressed appendix (invariant 7).
113
+ *
114
+ * `firstSeen` is never reset, by anything here (invariant 4).
10
115
  *
11
- * Pure: no I/O, no clock, no randomness. Callers supply the id for any issue
12
- * this opens via `nextId`.
116
+ * @param input - Issues, findings, the run, the clock and an id source.
117
+ * @returns Every issue for the target, plus the events that explain the changes.
13
118
  */
14
- export declare function reconcile(findings: Finding[], openIssues: Issue[], nextId: () => string): Issue[];
119
+ export declare function reconcile(input: ReconcileInput): ReconcileResult;
15
120
  //# sourceMappingURL=reconcile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,uEAAuE;AACvE,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,MAAM,GAAG,KAAK,EAAE,CAgBjG"}
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,GAAG,EAAW,UAAU,EAAkB,MAAM,UAAU,CAAC;AACzE,OAAO,EAML,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AAGvB;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAElC,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IAEtC,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAElB,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IAEnB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;IAE7B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,uDAAuD;IACvD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAEvC,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAElC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAEvC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;CAC9B;AAgED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CA0OhE"}