@mitre/hdf-utilities 3.4.4 → 3.5.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/dist/index.d.ts CHANGED
@@ -28,6 +28,15 @@ import { formatTimestamp, formatTimestampSeconds, normalizeHdfTimestamps, parseT
28
28
  declare function severityToImpact(severity: null): null;
29
29
  declare function severityToImpact(severity: string): number;
30
30
  declare function severityToImpact(severity: string | null): number | null;
31
+ /**
32
+ * Round an HDF impact value to 2 decimal places — its canonical precision —
33
+ * eliminating the representation noise that binary float division leaves behind
34
+ * (e.g. 9.8 / 10 = 0.9800000000000001). Impact is defined on 0.0–1.0 with a
35
+ * natural 0.01 grid (a 1-decimal CVSS score / 10), so this is lossless in
36
+ * intent. Use it wherever impact is COMPUTED (divided or otherwise
37
+ * arithmetically derived), not when assigned from a literal band.
38
+ */
39
+ declare function roundImpact(x: number): number;
31
40
  /**
32
41
  * Map an impact score to a severity string.
33
42
  *
@@ -56,6 +65,80 @@ declare function impactToSeverity(impact: number | null): string | null;
56
65
  */
57
66
  declare function cvssScoreToSeverity(score: number): 'none' | 'low' | 'medium' | 'high' | 'critical';
58
67
  //#endregion
68
+ //#region src/status/index.d.ts
69
+ /**
70
+ * Canonical HDF result-status ordering and effective-status computation.
71
+ *
72
+ * This is the single source of truth for worst-wins roll-up and
73
+ * effective-status selection across hdf-libs (mirrored in Go as
74
+ * hdf-utilities/go status.go). The precedence rules are published in
75
+ * site/docs/architecture/status-determination.md; do not re-implement them.
76
+ */
77
+ /** Canonical worst-wins ordering, worst first. */
78
+ declare const STATUS_SEVERITY_ORDER: readonly string[];
79
+ /** Severity rank of a status: higher = worse. Unknown statuses rank -1. */
80
+ declare function statusRank(status: string): number;
81
+ /**
82
+ * Rolls a list of result statuses up to the worst one. An empty list (or a
83
+ * list of only unknown statuses) rolls up to "notReviewed".
84
+ */
85
+ declare function worstStatus(statuses: readonly string[]): string;
86
+ /**
87
+ * Neutral shape of a status override for effective-status computation.
88
+ * Callers map their concrete types (schema objects or raw JSON) onto it.
89
+ */
90
+ interface StatusOverrideInput {
91
+ /** Empty/undefined means the override carries no status and never governs. */
92
+ status?: string;
93
+ /** RFC3339 timestamp; undefined sorts earliest. */
94
+ appliedAt?: string;
95
+ /** RFC3339 timestamp; undefined means the override never expires. */
96
+ expiresAt?: string;
97
+ }
98
+ /**
99
+ * Selects the override that governs a requirement: the most recently applied
100
+ * (by appliedAt) non-expired override that carries a status — matching the
101
+ * schema's definition of disposition ("the most recent non-expired
102
+ * override"). Returns undefined when no override governs.
103
+ */
104
+ declare function governingStatusOverride(overrides: readonly StatusOverrideInput[], referenceTimestamp?: string): StatusOverrideInput | undefined;
105
+ /**
106
+ * Returns the index of the most recently applied (by appliedAt) non-expired
107
+ * override among those for which `eligible` returns true, or -1 when none
108
+ * qualifies. Generalizes governing-override selection to per-field
109
+ * eligibility: the schema defines effectiveStatus, effectiveImpact, and
110
+ * disposition each as "the most recent non-expired override" carrying the
111
+ * relevant field, so callers pass the field-presence check as the predicate.
112
+ */
113
+ declare function governingOverrideIndex(overrides: readonly StatusOverrideInput[], eligible: (index: number) => boolean, referenceTimestamp?: string): number;
114
+ /**
115
+ * Index variant of {@link governingStatusOverride}: returns -1 when no
116
+ * override governs. Callers holding richer concrete override types use the
117
+ * index to recover their own object.
118
+ */
119
+ declare function governingStatusOverrideIndex(overrides: readonly StatusOverrideInput[], referenceTimestamp?: string): number;
120
+ /** Neutral shape of a requirement for effective-status computation. */
121
+ interface EffectiveStatusInput {
122
+ impact: number;
123
+ /** The stored effectiveStatus field; undefined means unset. */
124
+ effectiveStatus?: string;
125
+ resultStatuses?: readonly string[];
126
+ overrides?: readonly StatusOverrideInput[];
127
+ }
128
+ /**
129
+ * Determines a requirement's effective status — the single canonical
130
+ * implementation of the precedence in status-determination.md:
131
+ *
132
+ * 1. impact === 0 → "notApplicable", regardless of results or overrides
133
+ * 2. the governing (most recent non-expired) status override's status
134
+ * 3. the stored effectiveStatus, honored only when NO overrides are present —
135
+ * effectiveStatus is state derived from overrides, so when every override
136
+ * has expired it is stale and the result roll-up wins
137
+ * 4. worst-wins roll-up of the result statuses
138
+ * 5. no results → "notReviewed"
139
+ */
140
+ declare function computeEffectiveStatus(input: EffectiveStatusInput, referenceTimestamp?: string): string;
141
+ //#endregion
59
142
  //#region src/cvss/index.d.ts
60
143
  /**
61
144
  * CVSS vector parsing and validation utilities.
@@ -108,6 +191,42 @@ declare function parseCvssVector(vector: string): ParsedCvssVector;
108
191
  * @returns `{ valid, errors[] }` — never throws
109
192
  */
110
193
  declare function validateCvssVector(vector: string, version?: string): CvssValidationResult;
194
+ /** A computed CVSS score. */
195
+ interface CvssScore {
196
+ /** CVSS major.minor version the score was computed under (currently always "3.1"). */
197
+ version: string;
198
+ /** Base score (0.0–10.0). */
199
+ baseScore: number;
200
+ /**
201
+ * Temporal (Threat) score (0.0–10.0). Equals baseScore when no temporal
202
+ * metrics (E/RL/RC) are present, per the CVSS 3.1 spec.
203
+ */
204
+ temporalScore: number;
205
+ }
206
+ /**
207
+ * Compute the CVSS 3.1 Base and Temporal (Threat) scores for a vector string.
208
+ *
209
+ * Only CVSS 3.1 is supported here — CVSS 4.0's MacroVector engine lives
210
+ * separately — so any other version throws rather than returning a wrong number.
211
+ * The peer of the Go `ComputeCvssScore` (kept byte-identical via shared fixtures).
212
+ *
213
+ * @throws if the version is not 3.1, a required base metric is missing, or any
214
+ * metric value is out of the 3.1 enum.
215
+ */
216
+ declare function computeCvssScore(vector: string): CvssScore;
217
+ /** Parse a CVSS 4.0 vector and return its MacroVector string. */
218
+ declare function cvss40MacroVector(vector: string): string;
219
+ /**
220
+ * Compute the CVSS 4.0 score for a vector string, split into a Base score
221
+ * (`baseScore`, with Exploit Maturity stripped to its worst-case default E:A) and
222
+ * a Threat score (`temporalScore`, with the E value present in the vector). For a
223
+ * base-only vector the two are equal. The peer of the Go `ComputeCvss40Score`,
224
+ * kept byte-identical via the shared data file and parity fixture.
225
+ *
226
+ * @throws if the version is not 4.0, a required base metric is missing, or any
227
+ * metric value is out of the 4.0 enum.
228
+ */
229
+ declare function computeCvss40Score(vector: string): CvssScore;
111
230
  //#endregion
112
231
  //#region src/cpe/index.d.ts
113
232
  /**
@@ -237,5 +356,5 @@ interface ParsedPurl {
237
356
  */
238
357
  declare function parsePurl(purlStr: string): ParsedPurl | null;
239
358
  //#endregion
240
- export { type CpePart, type CsvBuildOptions, type CsvParseOptions, type CvssValidationResult, type HashAlgorithm, type HashEncoding, type HashOptions, type ParsedCpe, type ParsedCvssVector, type ParsedPurl, type StringifyOptions, buildCsv, buildCsvArray, buildXml, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatTimestamp, formatTimestampSeconds, generateHash, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, verifyHash };
359
+ export { type CpePart, type CsvBuildOptions, type CsvParseOptions, type CvssScore, type CvssValidationResult, type EffectiveStatusInput, type HashAlgorithm, type HashEncoding, type HashOptions, type ParsedCpe, type ParsedCvssVector, type ParsedPurl, STATUS_SEVERITY_ORDER, type StatusOverrideInput, type StringifyOptions, buildCsv, buildCsvArray, buildXml, computeCvss40Score, computeCvssScore, computeEffectiveStatus, cvss40MacroVector, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatTimestamp, formatTimestampSeconds, generateHash, governingOverrideIndex, governingStatusOverride, governingStatusOverrideIndex, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, roundImpact, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, statusRank, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, verifyHash, worstStatus };
241
360
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/severity/index.ts","../src/cvss/index.ts","../src/cpe/index.ts","../src/purl/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCgB,iBAAiB;iBACjB,iBAAiB;iBACjB,iBAAiB;;;;;;;iBAajB,iBAAiB;iBACjB,iBAAiB;iBACjB,iBAAiB;;;;;;;;;;;;;;;;;;iBA2BjB,oBACd;;;;;;;;;;;;;;;UChEe;;EAEf;;EAEA,SAAS;;UAGM;EACf;EACA;;;;;;;;;;;;iBA2Ic,gBAAgB,iBAAiB;;;;;;;;;;;;;;;;;;iBA4DjC,mBACd,gBACA,mBACC;;;;;;;;;;;;;;;;;;KChNS;;;;;;;;UASK;;EAEf;;EAEA,MAAM;EACN;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuEc,SAAS,iBAAiB;;;;;;;;;;;;;;;;;;;UC/FzB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,YAAY;;EAEZ;;EAEA;;;;;;;;;;;;;;;;;;;;;iBAwBc,UAAU,kBAAkB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/severity/index.ts","../src/status/index.ts","../src/cvss/index.ts","../src/cpe/index.ts","../src/purl/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCgB,iBAAiB;iBACjB,iBAAiB;iBACjB,iBAAiB;;;;;;;;;iBAejB,YAAY;;;;;;;iBAUZ,iBAAiB;iBACjB,iBAAiB;iBACjB,iBAAiB;;;;;;;;;;;;;;;;;;iBA2BjB,oBACd;;;;;;;;;;;;cC7EW;;iBASG,WAAW;;;;;iBASX,YAAY;;;;;UAiBX;;EAEf;;EAEA;;EAEA;;;;;;;;iBAiBc,wBACd,oBAAoB,uBACpB,8BACC;;;;;;;;;iBAaa,uBACd,oBAAoB,uBACpB,WAAW,2BACX;;;;;;iBAmBc,6BACd,oBAAoB,uBACpB;;UAgBe;EACf;;EAEA;EACA;EACA,qBAAqB;;;;;;;;;;;;;;iBAeP,uBACd,OAAO,sBACP;;;;;;;;;;;;;;;UCrIe;;EAEf;;EAEA,SAAS;;UAGM;EACf;EACA;;;;;;;;;;;;iBA2Ic,gBAAgB,iBAAiB;;;;;;;;;;;;;;;;;;iBA4DjC,mBACd,gBACA,mBACC;;UAsCc;;EAEf;;EAEA;;;;;EAKA;;;;;;;;;;;;iBAsDc,iBAAiB,iBAAiB;;iBA6KlC,kBAAkB;;;;;;;;;;;iBAiMlB,mBAAmB,iBAAiB;;;;;;;;;;;;;;;;;;KCrqBxC;;;;;;;;UASK;;EAEf;;EAEA,MAAM;EACN;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuEc,SAAS,iBAAiB;;;;;;;;;;;;;;;;;;;UC/FzB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,YAAY;;EAEZ;;EAEA;;;;;;;;;;;;;;;;;;;;;iBAwBc,UAAU,kBAAkB"}
package/dist/index.js CHANGED
@@ -34,6 +34,17 @@ function severityToImpact(severity) {
34
34
  const normalized = severity.toLowerCase();
35
35
  return severityMap[normalized] ?? .5;
36
36
  }
37
+ /**
38
+ * Round an HDF impact value to 2 decimal places — its canonical precision —
39
+ * eliminating the representation noise that binary float division leaves behind
40
+ * (e.g. 9.8 / 10 = 0.9800000000000001). Impact is defined on 0.0–1.0 with a
41
+ * natural 0.01 grid (a 1-decimal CVSS score / 10), so this is lossless in
42
+ * intent. Use it wherever impact is COMPUTED (divided or otherwise
43
+ * arithmetically derived), not when assigned from a literal band.
44
+ */
45
+ function roundImpact(x) {
46
+ return Math.round(x * 100) / 100;
47
+ }
37
48
  function impactToSeverity(impact) {
38
49
  if (impact === null) return null;
39
50
  if (impact >= .9) return "critical";
@@ -67,8 +78,481 @@ function cvssScoreToSeverity(score) {
67
78
  return "critical";
68
79
  }
69
80
  //#endregion
81
+ //#region src/status/index.ts
82
+ /**
83
+ * Canonical HDF result-status ordering and effective-status computation.
84
+ *
85
+ * This is the single source of truth for worst-wins roll-up and
86
+ * effective-status selection across hdf-libs (mirrored in Go as
87
+ * hdf-utilities/go status.go). The precedence rules are published in
88
+ * site/docs/architecture/status-determination.md; do not re-implement them.
89
+ */
90
+ /** Canonical worst-wins ordering, worst first. */
91
+ const STATUS_SEVERITY_ORDER = [
92
+ "error",
93
+ "failed",
94
+ "passed",
95
+ "notApplicable",
96
+ "notReviewed"
97
+ ];
98
+ /** Severity rank of a status: higher = worse. Unknown statuses rank -1. */
99
+ function statusRank(status) {
100
+ const idx = STATUS_SEVERITY_ORDER.indexOf(status);
101
+ return idx === -1 ? -1 : STATUS_SEVERITY_ORDER.length - 1 - idx;
102
+ }
103
+ /**
104
+ * Rolls a list of result statuses up to the worst one. An empty list (or a
105
+ * list of only unknown statuses) rolls up to "notReviewed".
106
+ */
107
+ function worstStatus(statuses) {
108
+ let worst = "notReviewed";
109
+ let worstRank = -1;
110
+ for (const s of statuses) {
111
+ const r = statusRank(s);
112
+ if (r > worstRank) {
113
+ worstRank = r;
114
+ worst = s;
115
+ }
116
+ }
117
+ return worst;
118
+ }
119
+ function isExpired(override, ref) {
120
+ if (!override.expiresAt) return false;
121
+ const expires = parseTimestamp(override.expiresAt);
122
+ return expires !== null && expires <= ref;
123
+ }
124
+ /**
125
+ * Selects the override that governs a requirement: the most recently applied
126
+ * (by appliedAt) non-expired override that carries a status — matching the
127
+ * schema's definition of disposition ("the most recent non-expired
128
+ * override"). Returns undefined when no override governs.
129
+ */
130
+ function governingStatusOverride(overrides, referenceTimestamp) {
131
+ const i = governingStatusOverrideIndex(overrides, referenceTimestamp);
132
+ return i >= 0 ? overrides[i] : void 0;
133
+ }
134
+ /**
135
+ * Returns the index of the most recently applied (by appliedAt) non-expired
136
+ * override among those for which `eligible` returns true, or -1 when none
137
+ * qualifies. Generalizes governing-override selection to per-field
138
+ * eligibility: the schema defines effectiveStatus, effectiveImpact, and
139
+ * disposition each as "the most recent non-expired override" carrying the
140
+ * relevant field, so callers pass the field-presence check as the predicate.
141
+ */
142
+ function governingOverrideIndex(overrides, eligible, referenceTimestamp) {
143
+ const ref = refTime(referenceTimestamp);
144
+ let governing = -1;
145
+ for (let i = 0; i < overrides.length; i++) {
146
+ const override = overrides[i];
147
+ if (!override || !eligible(i) || isExpired(override, ref)) continue;
148
+ if (governing === -1 || appliedTime(override) > appliedTime(overrides[governing])) governing = i;
149
+ }
150
+ return governing;
151
+ }
152
+ /**
153
+ * Index variant of {@link governingStatusOverride}: returns -1 when no
154
+ * override governs. Callers holding richer concrete override types use the
155
+ * index to recover their own object.
156
+ */
157
+ function governingStatusOverrideIndex(overrides, referenceTimestamp) {
158
+ return governingOverrideIndex(overrides, (i) => !!overrides[i]?.status, referenceTimestamp);
159
+ }
160
+ function appliedTime(override) {
161
+ if (!override.appliedAt) return Number.NEGATIVE_INFINITY;
162
+ return parseTimestamp(override.appliedAt)?.getTime() ?? Number.NEGATIVE_INFINITY;
163
+ }
164
+ function refTime(referenceTimestamp) {
165
+ return (referenceTimestamp ? parseTimestamp(referenceTimestamp) : null) ?? /* @__PURE__ */ new Date();
166
+ }
167
+ /**
168
+ * Determines a requirement's effective status — the single canonical
169
+ * implementation of the precedence in status-determination.md:
170
+ *
171
+ * 1. impact === 0 → "notApplicable", regardless of results or overrides
172
+ * 2. the governing (most recent non-expired) status override's status
173
+ * 3. the stored effectiveStatus, honored only when NO overrides are present —
174
+ * effectiveStatus is state derived from overrides, so when every override
175
+ * has expired it is stale and the result roll-up wins
176
+ * 4. worst-wins roll-up of the result statuses
177
+ * 5. no results → "notReviewed"
178
+ */
179
+ function computeEffectiveStatus(input, referenceTimestamp) {
180
+ if (input.impact === 0) return "notApplicable";
181
+ const overrides = input.overrides ?? [];
182
+ if (overrides.length > 0) {
183
+ const governing = governingStatusOverride(overrides, referenceTimestamp);
184
+ if (governing?.status) return governing.status;
185
+ return worstStatus(input.resultStatuses ?? []);
186
+ }
187
+ if (input.effectiveStatus) return input.effectiveStatus;
188
+ return worstStatus(input.resultStatuses ?? []);
189
+ }
190
+ //#endregion
191
+ //#region src/cvss/cvss40-data.json
192
+ var cvss40_data_default = {
193
+ _comment: "CVSS 4.0 scoring data tables extracted VERBATIM from the FIRST reference calculator (https://github.com/FIRSTdotorg/cvss-v4-calculator; cvss_lookup.js / max_composed.js / max_severity.js). Regenerate via scratchpad/extract-data.mjs. Consumed byte-identical by Go (go/cvss40-data.json, //go:embed) and TS (src/cvss/cvss40-data.json).",
194
+ lookup: {
195
+ "100000": 9.8,
196
+ "100001": 9.5,
197
+ "100010": 9.4,
198
+ "100011": 8.7,
199
+ "100020": 9.1,
200
+ "100021": 8.1,
201
+ "100100": 9.4,
202
+ "100101": 8.9,
203
+ "100110": 8.6,
204
+ "100111": 7.4,
205
+ "100120": 7.7,
206
+ "100121": 6.4,
207
+ "100200": 8.7,
208
+ "100201": 7.5,
209
+ "100210": 7.4,
210
+ "100211": 6.3,
211
+ "100220": 6.3,
212
+ "100221": 4.9,
213
+ "101000": 9.4,
214
+ "101001": 8.9,
215
+ "101010": 8.8,
216
+ "101011": 7.7,
217
+ "101020": 7.6,
218
+ "101021": 6.7,
219
+ "101100": 8.6,
220
+ "101101": 7.6,
221
+ "101110": 7.4,
222
+ "101111": 5.8,
223
+ "101120": 5.9,
224
+ "101121": 5,
225
+ "101200": 7.2,
226
+ "101201": 5.7,
227
+ "101210": 5.7,
228
+ "101211": 5.2,
229
+ "101220": 5.2,
230
+ "101221": 2.5,
231
+ "102001": 8.3,
232
+ "102011": 7,
233
+ "102021": 5.4,
234
+ "102101": 6.5,
235
+ "102111": 5.8,
236
+ "102121": 2.6,
237
+ "102201": 5.3,
238
+ "102211": 2.1,
239
+ "102221": 1.3,
240
+ "110000": 9.5,
241
+ "110001": 9,
242
+ "110010": 8.8,
243
+ "110011": 7.6,
244
+ "110020": 7.6,
245
+ "110021": 7,
246
+ "110100": 9,
247
+ "110101": 7.7,
248
+ "110110": 7.5,
249
+ "110111": 6.2,
250
+ "110120": 6.1,
251
+ "110121": 5.3,
252
+ "110200": 7.7,
253
+ "110201": 6.6,
254
+ "110210": 6.8,
255
+ "110211": 5.9,
256
+ "110220": 5.2,
257
+ "110221": 3,
258
+ "111000": 8.9,
259
+ "111001": 7.8,
260
+ "111010": 7.6,
261
+ "111011": 6.7,
262
+ "111020": 6.2,
263
+ "111021": 5.8,
264
+ "111100": 7.4,
265
+ "111101": 5.9,
266
+ "111110": 5.7,
267
+ "111111": 5.7,
268
+ "111120": 4.7,
269
+ "111121": 2.3,
270
+ "111200": 6.1,
271
+ "111201": 5.2,
272
+ "111210": 5.7,
273
+ "111211": 2.9,
274
+ "111220": 2.4,
275
+ "111221": 1.6,
276
+ "112001": 7.1,
277
+ "112011": 5.9,
278
+ "112021": 3,
279
+ "112101": 5.8,
280
+ "112111": 2.6,
281
+ "112121": 1.5,
282
+ "112201": 2.3,
283
+ "112211": 1.3,
284
+ "112221": .6,
285
+ "200000": 9.3,
286
+ "200001": 8.7,
287
+ "200010": 8.6,
288
+ "200011": 7.2,
289
+ "200020": 7.5,
290
+ "200021": 5.8,
291
+ "200100": 8.6,
292
+ "200101": 7.4,
293
+ "200110": 7.4,
294
+ "200111": 6.1,
295
+ "200120": 5.6,
296
+ "200121": 3.4,
297
+ "200200": 7,
298
+ "200201": 5.4,
299
+ "200210": 5.2,
300
+ "200211": 4,
301
+ "200220": 4,
302
+ "200221": 2.2,
303
+ "201000": 8.5,
304
+ "201001": 7.5,
305
+ "201010": 7.4,
306
+ "201011": 5.5,
307
+ "201020": 6.2,
308
+ "201021": 5.1,
309
+ "201100": 7.2,
310
+ "201101": 5.7,
311
+ "201110": 5.5,
312
+ "201111": 4.1,
313
+ "201120": 4.6,
314
+ "201121": 1.9,
315
+ "201200": 5.3,
316
+ "201201": 3.6,
317
+ "201210": 3.4,
318
+ "201211": 1.9,
319
+ "201220": 1.9,
320
+ "201221": .8,
321
+ "202001": 6.4,
322
+ "202011": 5.1,
323
+ "202021": 2,
324
+ "202101": 4.7,
325
+ "202111": 2.1,
326
+ "202121": 1.1,
327
+ "202201": 2.4,
328
+ "202211": .9,
329
+ "202221": .4,
330
+ "210000": 8.8,
331
+ "210001": 7.5,
332
+ "210010": 7.3,
333
+ "210011": 5.3,
334
+ "210020": 6,
335
+ "210021": 5,
336
+ "210100": 7.3,
337
+ "210101": 5.5,
338
+ "210110": 5.9,
339
+ "210111": 4,
340
+ "210120": 4.1,
341
+ "210121": 2,
342
+ "210200": 5.4,
343
+ "210201": 4.3,
344
+ "210210": 4.5,
345
+ "210211": 2.2,
346
+ "210220": 2,
347
+ "210221": 1.1,
348
+ "211000": 7.5,
349
+ "211001": 5.5,
350
+ "211010": 5.8,
351
+ "211011": 4.5,
352
+ "211020": 4,
353
+ "211021": 2.1,
354
+ "211100": 6.1,
355
+ "211101": 5.1,
356
+ "211110": 4.8,
357
+ "211111": 1.8,
358
+ "211120": 2,
359
+ "211121": .9,
360
+ "211200": 4.6,
361
+ "211201": 1.8,
362
+ "211210": 1.7,
363
+ "211211": .7,
364
+ "211220": .8,
365
+ "211221": .2,
366
+ "212001": 5.3,
367
+ "212011": 2.4,
368
+ "212021": 1.4,
369
+ "212101": 2.4,
370
+ "212111": 1.2,
371
+ "212121": .5,
372
+ "212201": 1,
373
+ "212211": .3,
374
+ "212221": .1,
375
+ "000000": 10,
376
+ "000001": 9.9,
377
+ "000010": 9.8,
378
+ "000011": 9.5,
379
+ "000020": 9.5,
380
+ "000021": 9.2,
381
+ "000100": 10,
382
+ "000101": 9.6,
383
+ "000110": 9.3,
384
+ "000111": 8.7,
385
+ "000120": 9.1,
386
+ "000121": 8.1,
387
+ "000200": 9.3,
388
+ "000201": 9,
389
+ "000210": 8.9,
390
+ "000211": 8,
391
+ "000220": 8.1,
392
+ "000221": 6.8,
393
+ "001000": 9.8,
394
+ "001001": 9.5,
395
+ "001010": 9.5,
396
+ "001011": 9.2,
397
+ "001020": 9,
398
+ "001021": 8.4,
399
+ "001100": 9.3,
400
+ "001101": 9.2,
401
+ "001110": 8.9,
402
+ "001111": 8.1,
403
+ "001120": 8.1,
404
+ "001121": 6.5,
405
+ "001200": 8.8,
406
+ "001201": 8,
407
+ "001210": 7.8,
408
+ "001211": 7,
409
+ "001220": 6.9,
410
+ "001221": 4.8,
411
+ "002001": 9.2,
412
+ "002011": 8.2,
413
+ "002021": 7.2,
414
+ "002101": 7.9,
415
+ "002111": 6.9,
416
+ "002121": 5,
417
+ "002201": 6.9,
418
+ "002211": 5.5,
419
+ "002221": 2.7,
420
+ "010000": 9.9,
421
+ "010001": 9.7,
422
+ "010010": 9.5,
423
+ "010011": 9.2,
424
+ "010020": 9.2,
425
+ "010021": 8.5,
426
+ "010100": 9.5,
427
+ "010101": 9.1,
428
+ "010110": 9,
429
+ "010111": 8.3,
430
+ "010120": 8.4,
431
+ "010121": 7.1,
432
+ "010200": 9.2,
433
+ "010201": 8.1,
434
+ "010210": 8.2,
435
+ "010211": 7.1,
436
+ "010220": 7.2,
437
+ "010221": 5.3,
438
+ "011000": 9.5,
439
+ "011001": 9.3,
440
+ "011010": 9.2,
441
+ "011011": 8.5,
442
+ "011020": 8.5,
443
+ "011021": 7.3,
444
+ "011100": 9.2,
445
+ "011101": 8.2,
446
+ "011110": 8,
447
+ "011111": 7.2,
448
+ "011120": 7,
449
+ "011121": 5.9,
450
+ "011200": 8.4,
451
+ "011201": 7,
452
+ "011210": 7.1,
453
+ "011211": 5.2,
454
+ "011220": 5,
455
+ "011221": 3,
456
+ "012001": 8.6,
457
+ "012011": 7.5,
458
+ "012021": 5.2,
459
+ "012101": 7.1,
460
+ "012111": 5.2,
461
+ "012121": 2.9,
462
+ "012201": 6.3,
463
+ "012211": 2.9,
464
+ "012221": 1.7
465
+ },
466
+ maxComposed: {
467
+ "eq1": {
468
+ "0": ["AV:N/PR:N/UI:N/"],
469
+ "1": [
470
+ "AV:A/PR:N/UI:N/",
471
+ "AV:N/PR:L/UI:N/",
472
+ "AV:N/PR:N/UI:P/"
473
+ ],
474
+ "2": ["AV:P/PR:N/UI:N/", "AV:A/PR:L/UI:P/"]
475
+ },
476
+ "eq2": {
477
+ "0": ["AC:L/AT:N/"],
478
+ "1": ["AC:H/AT:N/", "AC:L/AT:P/"]
479
+ },
480
+ "eq3": {
481
+ "0": {
482
+ "0": ["VC:H/VI:H/VA:H/CR:H/IR:H/AR:H/"],
483
+ "1": ["VC:H/VI:H/VA:L/CR:M/IR:M/AR:H/", "VC:H/VI:H/VA:H/CR:M/IR:M/AR:M/"]
484
+ },
485
+ "1": {
486
+ "0": ["VC:L/VI:H/VA:H/CR:H/IR:H/AR:H/", "VC:H/VI:L/VA:H/CR:H/IR:H/AR:H/"],
487
+ "1": [
488
+ "VC:L/VI:H/VA:L/CR:H/IR:M/AR:H/",
489
+ "VC:L/VI:H/VA:H/CR:H/IR:M/AR:M/",
490
+ "VC:H/VI:L/VA:H/CR:M/IR:H/AR:M/",
491
+ "VC:H/VI:L/VA:L/CR:M/IR:H/AR:H/",
492
+ "VC:L/VI:L/VA:H/CR:H/IR:H/AR:M/"
493
+ ]
494
+ },
495
+ "2": { "1": ["VC:L/VI:L/VA:L/CR:H/IR:H/AR:H/"] }
496
+ },
497
+ "eq4": {
498
+ "0": ["SC:H/SI:S/SA:S/"],
499
+ "1": ["SC:H/SI:H/SA:H/"],
500
+ "2": ["SC:L/SI:L/SA:L/"]
501
+ },
502
+ "eq5": {
503
+ "0": ["E:A/"],
504
+ "1": ["E:P/"],
505
+ "2": ["E:U/"]
506
+ }
507
+ },
508
+ maxSeverity: {
509
+ "eq1": {
510
+ "0": 1,
511
+ "1": 4,
512
+ "2": 5
513
+ },
514
+ "eq2": {
515
+ "0": 1,
516
+ "1": 2
517
+ },
518
+ "eq3eq6": {
519
+ "0": {
520
+ "0": 7,
521
+ "1": 6
522
+ },
523
+ "1": {
524
+ "0": 8,
525
+ "1": 8
526
+ },
527
+ "2": { "1": 10 }
528
+ },
529
+ "eq4": {
530
+ "0": 6,
531
+ "1": 5,
532
+ "2": 4
533
+ },
534
+ "eq5": {
535
+ "0": 1,
536
+ "1": 1,
537
+ "2": 1
538
+ }
539
+ }
540
+ };
541
+ //#endregion
70
542
  //#region src/cvss/index.ts
71
543
  /**
544
+ * CVSS vector parsing and validation utilities.
545
+ *
546
+ * Supports CVSS 2.0 (legacy, no prefix), 3.0, 3.1, and 4.0 vector strings.
547
+ * Parsing is permissive: it splits any well-shaped key:value pair separated
548
+ * by `/`. Validation is strict per the FIRST grammar for each version.
549
+ *
550
+ * References:
551
+ * - CVSS 2.0: https://www.first.org/cvss/v2/guide
552
+ * - CVSS 3.1: https://www.first.org/cvss/v3.1/specification-document
553
+ * - CVSS 4.0: https://www.first.org/cvss/v4.0/specification-document
554
+ */
555
+ /**
72
556
  * Allowed values for each metric, keyed by CVSS major version.
73
557
  *
74
558
  * Sourced from FIRST specifications (linked above). Only metrics present
@@ -704,6 +1188,362 @@ function validateCvssVector(vector, version) {
704
1188
  errors
705
1189
  };
706
1190
  }
1191
+ const CVSS31_AV = {
1192
+ N: .85,
1193
+ A: .62,
1194
+ L: .55,
1195
+ P: .2
1196
+ };
1197
+ const CVSS31_AC = {
1198
+ L: .77,
1199
+ H: .44
1200
+ };
1201
+ const CVSS31_UI = {
1202
+ N: .85,
1203
+ R: .62
1204
+ };
1205
+ const CVSS31_CIA = {
1206
+ N: 0,
1207
+ L: .22,
1208
+ H: .56
1209
+ };
1210
+ const CVSS31_PR_UNCHANGED = {
1211
+ N: .85,
1212
+ L: .62,
1213
+ H: .27
1214
+ };
1215
+ const CVSS31_PR_CHANGED = {
1216
+ N: .85,
1217
+ L: .68,
1218
+ H: .5
1219
+ };
1220
+ const CVSS31_E = {
1221
+ X: 1,
1222
+ H: 1,
1223
+ F: .97,
1224
+ P: .94,
1225
+ U: .91
1226
+ };
1227
+ const CVSS31_RL = {
1228
+ X: 1,
1229
+ U: 1,
1230
+ W: .97,
1231
+ T: .96,
1232
+ O: .95
1233
+ };
1234
+ const CVSS31_RC = {
1235
+ X: 1,
1236
+ C: 1,
1237
+ R: .96,
1238
+ U: .92
1239
+ };
1240
+ /**
1241
+ * CVSS 3.1 Roundup (spec §7.4): the smallest number, to one decimal place, that
1242
+ * is >= the input — computed via integer math to avoid the floating-point edge
1243
+ * cases 3.0 rounding suffered.
1244
+ */
1245
+ function roundUp31(x) {
1246
+ const intInput = Math.round(x * 1e5);
1247
+ if (intInput % 1e4 === 0) return intInput / 1e5;
1248
+ return (Math.floor(intInput / 1e4) + 1) / 10;
1249
+ }
1250
+ function cvss31TemporalWeight(table, metrics, key) {
1251
+ const v = metrics.get(key);
1252
+ if (v === void 0) return 1;
1253
+ const w = table[v];
1254
+ if (w === void 0) throw new Error(`cvss: invalid 3.1 ${key} value "${v}"`);
1255
+ return w;
1256
+ }
1257
+ /**
1258
+ * Compute the CVSS 3.1 Base and Temporal (Threat) scores for a vector string.
1259
+ *
1260
+ * Only CVSS 3.1 is supported here — CVSS 4.0's MacroVector engine lives
1261
+ * separately — so any other version throws rather than returning a wrong number.
1262
+ * The peer of the Go `ComputeCvssScore` (kept byte-identical via shared fixtures).
1263
+ *
1264
+ * @throws if the version is not 3.1, a required base metric is missing, or any
1265
+ * metric value is out of the 3.1 enum.
1266
+ */
1267
+ function computeCvssScore(vector) {
1268
+ const { version, metrics } = parseCvssVector(vector);
1269
+ if (version !== "3.1") throw new Error(`cvss: score compute supports only CVSS 3.1, got "${version}"`);
1270
+ for (const k of [
1271
+ "AV",
1272
+ "AC",
1273
+ "PR",
1274
+ "UI",
1275
+ "S",
1276
+ "C",
1277
+ "I",
1278
+ "A"
1279
+ ]) if (!metrics.has(k)) throw new Error(`cvss: missing required 3.1 base metric "${k}"`);
1280
+ const scope = metrics.get("S");
1281
+ if (scope !== "U" && scope !== "C") throw new Error(`cvss: invalid 3.1 Scope value "${scope ?? ""}"`);
1282
+ const scopeChanged = scope === "C";
1283
+ const prTable = scopeChanged ? CVSS31_PR_CHANGED : CVSS31_PR_UNCHANGED;
1284
+ const lookup = (table, metric) => {
1285
+ const key = metrics.get(metric);
1286
+ const w = key === void 0 ? void 0 : table[key];
1287
+ if (w === void 0) throw new Error(`cvss: invalid 3.1 metric value ${metric}:"${key ?? ""}"`);
1288
+ return w;
1289
+ };
1290
+ const av = lookup(CVSS31_AV, "AV");
1291
+ const ac = lookup(CVSS31_AC, "AC");
1292
+ const ui = lookup(CVSS31_UI, "UI");
1293
+ const pr = lookup(prTable, "PR");
1294
+ const c = lookup(CVSS31_CIA, "C");
1295
+ const i = lookup(CVSS31_CIA, "I");
1296
+ const a = lookup(CVSS31_CIA, "A");
1297
+ const iss = 1 - (1 - c) * (1 - i) * (1 - a);
1298
+ const impact = scopeChanged ? 7.52 * (iss - .029) - 3.25 * Math.pow(iss - .02, 15) : 6.42 * iss;
1299
+ const exploitability = 8.22 * av * ac * pr * ui;
1300
+ let baseScore;
1301
+ if (impact <= 0) baseScore = 0;
1302
+ else if (scopeChanged) baseScore = roundUp31(Math.min(1.08 * (impact + exploitability), 10));
1303
+ else baseScore = roundUp31(Math.min(impact + exploitability, 10));
1304
+ const e = cvss31TemporalWeight(CVSS31_E, metrics, "E");
1305
+ const rl = cvss31TemporalWeight(CVSS31_RL, metrics, "RL");
1306
+ const rc = cvss31TemporalWeight(CVSS31_RC, metrics, "RC");
1307
+ const temporalScore = roundUp31(baseScore * e * rl * rc);
1308
+ return {
1309
+ version: "3.1",
1310
+ baseScore,
1311
+ temporalScore
1312
+ };
1313
+ }
1314
+ const CVSS40 = cvss40_data_default;
1315
+ const CVSS40_AV = {
1316
+ N: 0,
1317
+ A: .1,
1318
+ L: .2,
1319
+ P: .3
1320
+ };
1321
+ const CVSS40_PR = {
1322
+ N: 0,
1323
+ L: .1,
1324
+ H: .2
1325
+ };
1326
+ const CVSS40_UI = {
1327
+ N: 0,
1328
+ P: .1,
1329
+ A: .2
1330
+ };
1331
+ const CVSS40_AC = {
1332
+ L: 0,
1333
+ H: .1
1334
+ };
1335
+ const CVSS40_AT = {
1336
+ N: 0,
1337
+ P: .1
1338
+ };
1339
+ const CVSS40_VC = {
1340
+ H: 0,
1341
+ L: .1,
1342
+ N: .2
1343
+ };
1344
+ const CVSS40_SC = {
1345
+ H: .1,
1346
+ L: .2,
1347
+ N: .3
1348
+ };
1349
+ const CVSS40_SI = {
1350
+ S: 0,
1351
+ H: .1,
1352
+ L: .2,
1353
+ N: .3
1354
+ };
1355
+ const CVSS40_CR = {
1356
+ H: 0,
1357
+ M: .1,
1358
+ L: .2
1359
+ };
1360
+ /** Throw on an unexpected missing table entry rather than fabricate a value. */
1361
+ function req(value, what) {
1362
+ if (value === void 0) throw new Error(`cvss: 4.0 data table missing ${what}`);
1363
+ return value;
1364
+ }
1365
+ /**
1366
+ * Resolve the effective value of a metric, mirroring the reference m(): E:X and
1367
+ * unset default to A (worst case); CR/IR/AR:X and unset default to H; a present,
1368
+ * non-X Modified (M-prefixed) metric overrides its base.
1369
+ */
1370
+ function cvss40Metric(sel, metric) {
1371
+ const selected = sel.get(metric) ?? "";
1372
+ if (metric === "E" && (selected === "" || selected === "X")) return "A";
1373
+ if ((metric === "CR" || metric === "IR" || metric === "AR") && (selected === "" || selected === "X")) return "H";
1374
+ const modified = sel.get("M" + metric);
1375
+ if (modified !== void 0 && modified !== "X") return modified;
1376
+ return selected;
1377
+ }
1378
+ function cvss40MacroVectorFromSel(sel) {
1379
+ const mv = (k) => cvss40Metric(sel, k);
1380
+ let eq1 = "2";
1381
+ if (mv("AV") === "N" && mv("PR") === "N" && mv("UI") === "N") eq1 = "0";
1382
+ else if ((mv("AV") === "N" || mv("PR") === "N" || mv("UI") === "N") && mv("AV") !== "P") eq1 = "1";
1383
+ let eq2 = "1";
1384
+ if (mv("AC") === "L" && mv("AT") === "N") eq2 = "0";
1385
+ let eq3 = "2";
1386
+ if (mv("VC") === "H" && mv("VI") === "H") eq3 = "0";
1387
+ else if (mv("VC") === "H" || mv("VI") === "H" || mv("VA") === "H") eq3 = "1";
1388
+ let eq4 = "2";
1389
+ if (mv("MSI") === "S" || mv("MSA") === "S") eq4 = "0";
1390
+ else if (mv("SC") === "H" || mv("SI") === "H" || mv("SA") === "H") eq4 = "1";
1391
+ let eq5 = "2";
1392
+ if (mv("E") === "A") eq5 = "0";
1393
+ else if (mv("E") === "P") eq5 = "1";
1394
+ let eq6 = "1";
1395
+ if (mv("CR") === "H" && mv("VC") === "H" || mv("IR") === "H" && mv("VI") === "H" || mv("AR") === "H" && mv("VA") === "H") eq6 = "0";
1396
+ return eq1 + eq2 + eq3 + eq4 + eq5 + eq6;
1397
+ }
1398
+ /** Parse a CVSS 4.0 vector and return its MacroVector string. */
1399
+ function cvss40MacroVector(vector) {
1400
+ return cvss40MacroVectorFromSel(parseCvssVector(vector).metrics);
1401
+ }
1402
+ /** Pull a metric's value out of a composed max-vector string (reference extractValueMetric). */
1403
+ function cvss40ExtractMetric(metric, s) {
1404
+ const idx = s.indexOf(metric);
1405
+ if (idx < 0) return "";
1406
+ const rest = s.slice(idx + metric.length + 1);
1407
+ const slash = rest.indexOf("/");
1408
+ return slash >= 0 ? rest.slice(0, slash) : rest;
1409
+ }
1410
+ /** value minus the lower macro's score, or NaN when that lower macro does not exist. */
1411
+ function cvss40AvailDist(value, lowerMacro) {
1412
+ const s = CVSS40.lookup[lowerMacro];
1413
+ return s === void 0 ? NaN : value - s;
1414
+ }
1415
+ function cvss40Score(sel) {
1416
+ const mv = (k) => cvss40Metric(sel, k);
1417
+ if (mv("VC") === "N" && mv("VI") === "N" && mv("VA") === "N" && mv("SC") === "N" && mv("SI") === "N" && mv("SA") === "N") return 0;
1418
+ const macro = cvss40MacroVectorFromSel(sel);
1419
+ let value = req(CVSS40.lookup[macro], `lookup[${macro}]`);
1420
+ const c0 = macro.charAt(0);
1421
+ const c1 = macro.charAt(1);
1422
+ const c2 = macro.charAt(2);
1423
+ const c3 = macro.charAt(3);
1424
+ const c4 = macro.charAt(4);
1425
+ const c5 = macro.charAt(5);
1426
+ const d = (i) => macro.charCodeAt(i) - 48;
1427
+ const eq3 = d(2);
1428
+ const eq6 = d(5);
1429
+ const rep = (i, digit) => macro.slice(0, i) + String(digit) + macro.slice(i + 1);
1430
+ const availDistEq1 = cvss40AvailDist(value, rep(0, d(0) + 1));
1431
+ const availDistEq2 = cvss40AvailDist(value, rep(1, d(1) + 1));
1432
+ const availDistEq4 = cvss40AvailDist(value, rep(3, d(3) + 1));
1433
+ const availDistEq5 = cvss40AvailDist(value, rep(4, d(4) + 1));
1434
+ let availDistEq3eq6 = NaN;
1435
+ if (eq3 === 1 && eq6 === 1 || eq3 === 0 && eq6 === 1) availDistEq3eq6 = cvss40AvailDist(value, rep(2, eq3 + 1));
1436
+ else if (eq3 === 1 && eq6 === 0) availDistEq3eq6 = cvss40AvailDist(value, rep(5, eq6 + 1));
1437
+ else if (eq3 === 0 && eq6 === 0) {
1438
+ const left = CVSS40.lookup[rep(5, eq6 + 1)];
1439
+ const right = CVSS40.lookup[rep(2, eq3 + 1)];
1440
+ if (left !== void 0 && right !== void 0) availDistEq3eq6 = value - Math.max(left, right);
1441
+ else if (right !== void 0) availDistEq3eq6 = value - right;
1442
+ } else availDistEq3eq6 = cvss40AvailDist(value, macro.slice(0, 2) + String(eq3 + 1) + macro.slice(3, 5) + String(eq6 + 1));
1443
+ const eq1Maxes = req(CVSS40.maxComposed.eq1[c0], `maxComposed.eq1[${c0}]`);
1444
+ const eq2Maxes = req(CVSS40.maxComposed.eq2[c1], `maxComposed.eq2[${c1}]`);
1445
+ const eq3eq6Maxes = req(req(CVSS40.maxComposed.eq3[c2], `maxComposed.eq3[${c2}]`)[c5], `maxComposed.eq3[${c2}][${c5}]`);
1446
+ const eq4Maxes = req(CVSS40.maxComposed.eq4[c3], `maxComposed.eq4[${c3}]`);
1447
+ const eq5Maxes = req(CVSS40.maxComposed.eq5[c4], `maxComposed.eq5[${c4}]`);
1448
+ const maxVectors = [];
1449
+ for (const a of eq1Maxes) for (const b of eq2Maxes) for (const c of eq3eq6Maxes) for (const e of eq4Maxes) for (const f of eq5Maxes) maxVectors.push(a + b + c + e + f);
1450
+ const dist = (table, metric, maxVector) => req(table[mv(metric)], `level ${metric}:${mv(metric)}`) - req(table[cvss40ExtractMetric(metric, maxVector)], `max level ${metric}`);
1451
+ let sdAV = 0, sdPR = 0, sdUI = 0, sdAC = 0, sdAT = 0, sdVC = 0, sdVI = 0, sdVA = 0;
1452
+ let sdSC = 0, sdSI = 0, sdSA = 0, sdCR = 0, sdIR = 0, sdAR = 0;
1453
+ for (const maxVector of maxVectors) {
1454
+ sdAV = dist(CVSS40_AV, "AV", maxVector);
1455
+ sdPR = dist(CVSS40_PR, "PR", maxVector);
1456
+ sdUI = dist(CVSS40_UI, "UI", maxVector);
1457
+ sdAC = dist(CVSS40_AC, "AC", maxVector);
1458
+ sdAT = dist(CVSS40_AT, "AT", maxVector);
1459
+ sdVC = dist(CVSS40_VC, "VC", maxVector);
1460
+ sdVI = dist(CVSS40_VC, "VI", maxVector);
1461
+ sdVA = dist(CVSS40_VC, "VA", maxVector);
1462
+ sdSC = dist(CVSS40_SC, "SC", maxVector);
1463
+ sdSI = dist(CVSS40_SI, "SI", maxVector);
1464
+ sdSA = dist(CVSS40_SI, "SA", maxVector);
1465
+ sdCR = dist(CVSS40_CR, "CR", maxVector);
1466
+ sdIR = dist(CVSS40_CR, "IR", maxVector);
1467
+ sdAR = dist(CVSS40_CR, "AR", maxVector);
1468
+ if ([
1469
+ sdAV,
1470
+ sdPR,
1471
+ sdUI,
1472
+ sdAC,
1473
+ sdAT,
1474
+ sdVC,
1475
+ sdVI,
1476
+ sdVA,
1477
+ sdSC,
1478
+ sdSI,
1479
+ sdSA,
1480
+ sdCR,
1481
+ sdIR,
1482
+ sdAR
1483
+ ].some((v) => v < 0)) continue;
1484
+ break;
1485
+ }
1486
+ const curEq1 = sdAV + sdPR + sdUI;
1487
+ const curEq2 = sdAC + sdAT;
1488
+ const curEq3eq6 = sdVC + sdVI + sdVA + sdCR + sdIR + sdAR;
1489
+ const curEq4 = sdSC + sdSI + sdSA;
1490
+ const step = .1;
1491
+ const maxSevEq1 = req(CVSS40.maxSeverity.eq1[c0], `maxSeverity.eq1[${c0}]`) * step;
1492
+ const maxSevEq2 = req(CVSS40.maxSeverity.eq2[c1], `maxSeverity.eq2[${c1}]`) * step;
1493
+ const maxSevEq3eq6 = req(req(CVSS40.maxSeverity.eq3eq6[c2], `maxSeverity.eq3eq6[${c2}]`)[c5], `maxSeverity.eq3eq6[${c2}][${c5}]`) * step;
1494
+ const maxSevEq4 = req(CVSS40.maxSeverity.eq4[c3], `maxSeverity.eq4[${c3}]`) * step;
1495
+ let nExisting = 0;
1496
+ let normEq1 = 0, normEq2 = 0, normEq3eq6 = 0, normEq4 = 0;
1497
+ const normEq5 = 0;
1498
+ if (!Number.isNaN(availDistEq1)) {
1499
+ nExisting++;
1500
+ normEq1 = availDistEq1 * (curEq1 / maxSevEq1);
1501
+ }
1502
+ if (!Number.isNaN(availDistEq2)) {
1503
+ nExisting++;
1504
+ normEq2 = availDistEq2 * (curEq2 / maxSevEq2);
1505
+ }
1506
+ if (!Number.isNaN(availDistEq3eq6)) {
1507
+ nExisting++;
1508
+ normEq3eq6 = availDistEq3eq6 * (curEq3eq6 / maxSevEq3eq6);
1509
+ }
1510
+ if (!Number.isNaN(availDistEq4)) {
1511
+ nExisting++;
1512
+ normEq4 = availDistEq4 * (curEq4 / maxSevEq4);
1513
+ }
1514
+ if (!Number.isNaN(availDistEq5)) nExisting++;
1515
+ const meanDistance = nExisting === 0 ? 0 : (normEq1 + normEq2 + normEq3eq6 + normEq4 + normEq5) / nExisting;
1516
+ value -= meanDistance;
1517
+ if (value < 0) value = 0;
1518
+ if (value > 10) value = 10;
1519
+ return Math.round(value * 10) / 10;
1520
+ }
1521
+ /** Remove the E: segment so the Base score reflects the worst-case default (E:X → E:A). */
1522
+ function cvss40StripExploitMaturity(vector) {
1523
+ return vector.split("/").filter((seg) => !seg.startsWith("E:")).join("/");
1524
+ }
1525
+ /**
1526
+ * Compute the CVSS 4.0 score for a vector string, split into a Base score
1527
+ * (`baseScore`, with Exploit Maturity stripped to its worst-case default E:A) and
1528
+ * a Threat score (`temporalScore`, with the E value present in the vector). For a
1529
+ * base-only vector the two are equal. The peer of the Go `ComputeCvss40Score`,
1530
+ * kept byte-identical via the shared data file and parity fixture.
1531
+ *
1532
+ * @throws if the version is not 4.0, a required base metric is missing, or any
1533
+ * metric value is out of the 4.0 enum.
1534
+ */
1535
+ function computeCvss40Score(vector) {
1536
+ const { version } = parseCvssVector(vector);
1537
+ if (version !== "4.0") throw new Error(`cvss: 4.0 score compute supports only CVSS 4.0, got "${version}"`);
1538
+ const { valid, errors } = validateCvssVector(vector, "4.0");
1539
+ if (!valid) throw new Error(`cvss: invalid 4.0 vector: ${errors.join("; ")}`);
1540
+ const temporalScore = cvss40Score(parseCvssVector(vector).metrics);
1541
+ return {
1542
+ version: "4.0",
1543
+ baseScore: cvss40Score(parseCvssVector(cvss40StripExploitMaturity(vector)).metrics),
1544
+ temporalScore
1545
+ };
1546
+ }
707
1547
  //#endregion
708
1548
  //#region src/cpe/index.ts
709
1549
  const CPE_PREFIX = "cpe:2.3:";
@@ -926,6 +1766,6 @@ function safeDecode(s) {
926
1766
  }
927
1767
  }
928
1768
  //#endregion
929
- export { buildCsv, buildCsvArray, buildXml, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatTimestamp, formatTimestampSeconds, generateHash, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, verifyHash };
1769
+ export { STATUS_SEVERITY_ORDER, buildCsv, buildCsvArray, buildXml, computeCvss40Score, computeCvssScore, computeEffectiveStatus, cvss40MacroVector, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatTimestamp, formatTimestampSeconds, generateHash, governingOverrideIndex, governingStatusOverride, governingStatusOverrideIndex, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, normalizeHdfTimestamps, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, roundImpact, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, statusRank, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, verifyHash, worstStatus };
930
1770
 
931
1771
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/severity/index.ts","../src/cvss/index.ts","../src/cpe/index.ts","../src/purl/index.ts"],"sourcesContent":["/**\n * Severity-to-impact mapping utilities.\n *\n * These define the canonical bidirectional mapping between severity labels\n * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),\n * aligned with CVSS 3.x severity bands normalized to 0–1.\n *\n * Threshold ranges (used by impactToSeverity):\n * >=0.9 = critical (CVSS 9.0–10.0)\n * [0.7, 0.9) = high (CVSS 7.0–8.9)\n * [0.4, 0.7) = medium (CVSS 4.0–6.9)\n * (0.0, 0.4) = low (CVSS 0.1–3.9)\n * 0.0 = informational (CVSS 0.0)\n */\n\nconst severityMap: Record<string, number> = {\n critical: 0.9,\n high: 0.7,\n medium: 0.5,\n low: 0.3,\n info: 0.0,\n none: 0.0,\n informational: 0.0,\n information: 0.0,\n};\n\n/**\n * Map a severity string to an impact score.\n *\n * @param severity - Severity level (case-insensitive), or null for no severity assessment\n * @returns Impact score between 0.0 and 1.0; null when input is null; defaults to 0.5 for unrecognized values\n */\nexport function severityToImpact(severity: null): null;\nexport function severityToImpact(severity: string): number;\nexport function severityToImpact(severity: string | null): number | null;\nexport function severityToImpact(severity: string | null): number | null {\n if (severity === null) return null;\n const normalized = severity.toLowerCase();\n return severityMap[normalized] ?? 0.5;\n}\n\n/**\n * Map an impact score to a severity string.\n *\n * @param impact - Impact score (0.0 to 1.0), or null for no impact assessment\n * @returns Severity level string, or null when input is null\n */\nexport function impactToSeverity(impact: null): null;\nexport function impactToSeverity(impact: number): string;\nexport function impactToSeverity(impact: number | null): string | null;\nexport function impactToSeverity(impact: number | null): string | null {\n if (impact === null) return null;\n if (impact >= 0.9) return 'critical';\n if (impact >= 0.7) return 'high';\n if (impact >= 0.4) return 'medium';\n if (impact > 0.0) return 'low';\n return 'informational';\n}\n\n/**\n * Map a raw CVSS base score (0.0–10.0) to a FIRST qualitative severity band.\n *\n * Bands per FIRST CVSS v3.x / v4.0 spec:\n * none = 0.0\n * low = 0.1 – 3.9\n * medium = 4.0 – 6.9\n * high = 7.0 – 8.9\n * critical = 9.0 – 10.0\n *\n * Out-of-range inputs are clamped: scores < 0.1 → \"none\" (band floor),\n * scores > 10.0 → \"critical\". This matches scanner behavior of treating\n * malformed scores as informational/critical extremes rather than throwing.\n *\n * @param score - CVSS base score\n * @returns FIRST severity band label\n */\nexport function cvssScoreToSeverity(\n score: number,\n): 'none' | 'low' | 'medium' | 'high' | 'critical' {\n if (!Number.isFinite(score) || score < 0.1) return 'none';\n if (score < 4.0) return 'low';\n if (score < 7.0) return 'medium';\n if (score < 9.0) return 'high';\n return 'critical';\n}\n","/**\n * CVSS vector parsing and validation utilities.\n *\n * Supports CVSS 2.0 (legacy, no prefix), 3.0, 3.1, and 4.0 vector strings.\n * Parsing is permissive: it splits any well-shaped key:value pair separated\n * by `/`. Validation is strict per the FIRST grammar for each version.\n *\n * References:\n * - CVSS 2.0: https://www.first.org/cvss/v2/guide\n * - CVSS 3.1: https://www.first.org/cvss/v3.1/specification-document\n * - CVSS 4.0: https://www.first.org/cvss/v4.0/specification-document\n */\n\nexport interface ParsedCvssVector {\n /** \"2.0\" | \"3.0\" | \"3.1\" | \"4.0\" | \"unknown\" */\n version: string;\n /** Metric name -> metric value (e.g. \"AV\" -> \"N\"). Order-preserving via Map. */\n metrics: Map<string, string>;\n}\n\nexport interface CvssValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Allowed values for each metric, keyed by CVSS major version.\n *\n * Sourced from FIRST specifications (linked above). Only metrics present\n * in a given version's grammar appear in that version's map; unknown metrics\n * encountered during validation are ignored (forward-compat).\n */\nconst METRIC_VALUES: Record<string, Record<string, string[]>> = {\n '2.0': {\n AV: ['L', 'A', 'N'],\n AC: ['H', 'M', 'L'],\n Au: ['M', 'S', 'N'],\n C: ['N', 'P', 'C'],\n I: ['N', 'P', 'C'],\n A: ['N', 'P', 'C'],\n // Temporal\n E: ['U', 'POC', 'F', 'H', 'ND'],\n RL: ['OF', 'TF', 'W', 'U', 'ND'],\n RC: ['UC', 'UR', 'C', 'ND'],\n // Environmental\n CDP: ['N', 'L', 'LM', 'MH', 'H', 'ND'],\n TD: ['N', 'L', 'M', 'H', 'ND'],\n CR: ['L', 'M', 'H', 'ND'],\n IR: ['L', 'M', 'H', 'ND'],\n AR: ['L', 'M', 'H', 'ND'],\n },\n '3.0': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '3.1': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '4.0': {\n // Base\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n AT: ['N', 'P'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'P', 'A'],\n VC: ['H', 'L', 'N'],\n VI: ['H', 'L', 'N'],\n VA: ['H', 'L', 'N'],\n SC: ['H', 'L', 'N'],\n SI: ['H', 'L', 'N'],\n SA: ['H', 'L', 'N'],\n // Threat\n E: ['X', 'A', 'P', 'U'],\n // Environmental (modified base) — same enums as their base counterparts plus X\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MAT: ['X', 'N', 'P'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'P', 'A'],\n MVC: ['X', 'H', 'L', 'N'],\n MVI: ['X', 'H', 'L', 'N'],\n MVA: ['X', 'H', 'L', 'N'],\n MSC: ['X', 'H', 'L', 'N'],\n MSI: ['X', 'S', 'H', 'L', 'N'],\n MSA: ['X', 'S', 'H', 'L', 'N'],\n // Requirements\n CR: ['X', 'H', 'M', 'L'],\n IR: ['X', 'H', 'M', 'L'],\n AR: ['X', 'H', 'M', 'L'],\n // Supplemental\n S: ['X', 'N', 'P'],\n AU: ['X', 'N', 'Y'],\n R: ['X', 'A', 'U', 'I'],\n V: ['X', 'D', 'C'],\n RE: ['X', 'L', 'M', 'H'],\n U: ['X', 'Clear', 'Green', 'Amber', 'Red'],\n },\n};\n\nconst REQUIRED_METRICS: Record<string, string[]> = {\n '2.0': ['AV', 'AC', 'Au', 'C', 'I', 'A'],\n '3.0': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '3.1': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '4.0': ['AV', 'AC', 'AT', 'PR', 'UI', 'VC', 'VI', 'VA', 'SC', 'SI', 'SA'],\n};\n\n/**\n * Parse a CVSS vector string into version + metric map.\n *\n * Version detection rule: a leading `CVSS:X.Y` segment yields version \"X.Y\".\n * Otherwise (legacy v2 vectors), version is \"2.0\". Malformed input yields\n * \"unknown\" with an empty metric map; no exceptions are thrown.\n *\n * @param vector - CVSS vector string (e.g. \"CVSS:3.1/AV:N/AC:L/...\")\n * @returns Parsed version + metric map\n */\nexport function parseCvssVector(vector: string): ParsedCvssVector {\n const metrics = new Map<string, string>();\n if (typeof vector !== 'string' || vector.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n const segments = vector.split('/').filter((s) => s.length > 0);\n if (segments.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n let version = '2.0';\n let start = 0;\n const first = segments[0];\n if (first === undefined) {\n return { version: 'unknown', metrics };\n }\n if (first.startsWith('CVSS:')) {\n version = first.slice(5);\n start = 1;\n } else if (!first.includes(':')) {\n // No prefix AND first segment isn't a key:value pair — bail.\n return { version: 'unknown', metrics };\n }\n\n for (let i = start; i < segments.length; i++) {\n const seg = segments[i];\n if (seg === undefined) {\n continue;\n }\n const colon = seg.indexOf(':');\n if (colon <= 0 || colon === seg.length - 1) {\n // Skip malformed segments (no colon, empty key, or empty value).\n continue;\n }\n const key = seg.slice(0, colon);\n const value = seg.slice(colon + 1);\n metrics.set(key, value);\n }\n\n return { version, metrics };\n}\n\n/**\n * Validate a CVSS vector string against the FIRST grammar for its version.\n *\n * Checks:\n * - Version is one of the supported set (2.0, 3.0, 3.1, 4.0).\n * - All required base metrics for the version are present.\n * - All known metric values are within the allowed enum.\n *\n * Unknown metric keys are tolerated (forward-compat with future minor versions\n * or scanner-specific extensions). To check a v2 vector without the CVSS:\n * prefix, callers may pass version=\"2.0\" explicitly; otherwise the parser\n * defaults to \"2.0\" on prefix absence.\n *\n * @param vector - CVSS vector string\n * @param version - Optional explicit version override (else inferred)\n * @returns `{ valid, errors[] }` — never throws\n */\nexport function validateCvssVector(\n vector: string,\n version?: string,\n): CvssValidationResult {\n const errors: string[] = [];\n\n if (typeof vector !== 'string' || vector.length === 0) {\n return { valid: false, errors: ['vector is empty'] };\n }\n\n const parsed = parseCvssVector(vector);\n const v = version ?? parsed.version;\n\n const grammar = METRIC_VALUES[v];\n const required = REQUIRED_METRICS[v];\n if (!grammar || !required) {\n errors.push(`unsupported CVSS version: ${v}`);\n return { valid: false, errors };\n }\n\n for (const req of required) {\n if (!parsed.metrics.has(req)) {\n errors.push(`missing required metric: ${req}`);\n }\n }\n\n for (const [key, value] of parsed.metrics.entries()) {\n const allowed = grammar[key];\n if (!allowed) {\n // Unknown metric: forward-compat — no error.\n continue;\n }\n if (!allowed.includes(value)) {\n errors.push(`invalid value for metric ${key}: ${value} (allowed: ${allowed.join(',')})`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n","/**\n * CPE 2.3 URI parser for HDF converters.\n *\n * Common Platform Enumeration (CPE) 2.3 URIs identify affected products.\n * Container scanners (grype, twistlock, snyk) emit them in vulnerability\n * findings. This module provides an accept-and-warn parser: any input that\n * carries the `cpe:2.3:` prefix is best-effort parsed; deviations from the\n * canonical 13-field form are reported via `warnings` rather than rejected.\n *\n * Canonical form:\n * `cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw:target_hw:other`\n *\n * Reference: NIST IR 7695 (https://csrc.nist.gov/publications/detail/nistir/7695/final)\n */\n\n/** Valid CPE 2.3 `part` values. */\nexport type CpePart = 'a' | 'o' | 'h' | '*';\n\n/**\n * Parsed CPE 2.3 URI.\n *\n * `warnings` collects any deviations from the canonical form (truncation,\n * extra fields, unknown `part`). An empty array means the input was a strict\n * 13-field, valid-part CPE.\n */\nexport interface ParsedCpe {\n /** The original input string, unmodified. */\n raw: string;\n /** Application kind. `'a'` (app), `'o'` (OS), `'h'` (hardware), `'*'` (any). */\n part: CpePart | string;\n vendor: string;\n product: string;\n version: string;\n update: string;\n edition: string;\n language: string;\n swEdition: string;\n targetSw: string;\n targetHw: string;\n other: string;\n /** Deviation messages; empty when the input is a canonical 13-field CPE. */\n warnings: string[];\n}\n\nconst CPE_PREFIX = 'cpe:2.3:';\nconst EXPECTED_TOTAL_FIELDS = 13; // \"cpe\", \"2.3\", and 11 product fields = 13 colon-separated tokens\nconst PRODUCT_FIELD_COUNT = 11; // part + 10 attribute fields\nconst VALID_PARTS = new Set(['a', 'o', 'h', '*']);\n\n/**\n * Split a CPE 2.3 body on unescaped `:` separators.\n *\n * Per CPE 2.3 spec section 6.1.2.4, `:` and `\\` inside a field must be\n * escaped as `\\:` and `\\\\`. We respect those escapes during the split and\n * unescape the result.\n */\nfunction splitOnUnescapedColons(body: string): string[] {\n const result: string[] = [];\n let current = '';\n for (let i = 0; i < body.length; i++) {\n const ch = body[i];\n if (ch === '\\\\' && i + 1 < body.length) {\n const next = body[i + 1];\n // Preserve known escapes (\\: and \\\\) as a literal next character.\n if (next === ':' || next === '\\\\') {\n current += next;\n i++;\n continue;\n }\n // Unknown escape — keep the backslash and continue.\n current += ch;\n continue;\n }\n if (ch === ':') {\n result.push(current);\n current = '';\n continue;\n }\n current += ch;\n }\n result.push(current);\n return result;\n}\n\n/**\n * Parse a CPE 2.3 URI.\n *\n * Accept-and-warn semantics:\n * - Input missing the `cpe:2.3:` prefix → returns `null`.\n * - Input with the prefix but fewer than 11 product fields → fields are\n * padded with `\"*\"` and a `truncated:` warning is added.\n * - Input with more than 11 product fields → extras are dropped and an\n * `\"extra fields ignored\"` warning is added.\n * - Unknown `part` value → kept as-is in the result, `\"unknown part: X\"`\n * warning is added.\n * - `\\:` and `\\\\` escapes inside fields are honored during splitting and\n * unescaped in the returned field values.\n *\n * @param cpeUri - CPE 2.3 URI string\n * @returns Parsed CPE with warnings, or `null` if input lacks the prefix\n *\n * @example\n * ```typescript\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k:*:*:*:*:*:*:*');\n * // { part: 'a', vendor: 'openssl', product: 'openssl', version: '1.1.1k', ... warnings: [] }\n *\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k');\n * // truncated input → padded with '*', warnings: ['truncated: expected 13 colon-separated fields, got 6']\n *\n * parseCpe('openssl:1.1.1k'); // null — no prefix\n * ```\n */\nexport function parseCpe(cpeUri: string): ParsedCpe | null {\n if (!cpeUri.startsWith(CPE_PREFIX)) {\n return null;\n }\n\n const warnings: string[] = [];\n const body = cpeUri.slice(CPE_PREFIX.length);\n\n // Special case: bare prefix `cpe:2.3:` — all fields empty, still warn.\n // This is the \"I have nothing useful here\" form; padding with \"*\" would\n // suggest the parser inferred wildcards, which it didn't.\n const isBarePrefix = body.length === 0;\n\n const bodyParts = isBarePrefix ? [] : splitOnUnescapedColons(body);\n\n // Total colon-separated fields including the prefix \"cpe\" and \"2.3\" markers.\n const totalFields = isBarePrefix ? 2 : 2 + bodyParts.length;\n\n if (bodyParts.length > PRODUCT_FIELD_COUNT) {\n warnings.push('extra fields ignored');\n } else if (bodyParts.length < PRODUCT_FIELD_COUNT) {\n warnings.push(\n `truncated: expected ${EXPECTED_TOTAL_FIELDS} colon-separated fields, got ${totalFields}`,\n );\n }\n\n // Pad missing fields. For a bare prefix, pad with \"\" (no inferred data).\n // For partial input, pad missing trailing fields with \"*\" (wildcard).\n const padValue = isBarePrefix ? '' : '*';\n const fields: string[] = bodyParts.slice(0, PRODUCT_FIELD_COUNT);\n while (fields.length < PRODUCT_FIELD_COUNT) {\n fields.push(padValue);\n }\n\n // fields is padded to PRODUCT_FIELD_COUNT above, so every slot is present;\n // the destructuring defaults only satisfy the compiler's indexed-access check.\n const [\n part = '',\n vendor = '',\n product = '',\n version = '',\n update = '',\n edition = '',\n language = '',\n swEdition = '',\n targetSw = '',\n targetHw = '',\n other = '',\n ] = fields;\n\n if (!VALID_PARTS.has(part)) {\n warnings.push(`unknown part: ${part}`);\n }\n\n return {\n raw: cpeUri,\n part,\n vendor,\n product,\n version,\n update,\n edition,\n language,\n swEdition,\n targetSw,\n targetHw,\n other,\n warnings,\n };\n}\n","/**\n * Package URL (PURL) parser for SBOM and CVE-scanner ingestion.\n *\n * PURL is the canonical package identifier used by CycloneDX, SPDX, OSV,\n * GitHub Advisory Database, and most modern scanners. Spec:\n * https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst\n *\n * Grammar: `pkg:type/namespace/name@version?qualifiers#subpath`\n *\n * This parser is intentionally accept-and-warn. Scanners occasionally emit\n * slightly malformed PURLs (qualifier with no `=`, missing name, etc.) and\n * we'd rather record a warning than fail ingestion.\n */\n\n/**\n * Result of parsing a PURL string.\n */\nexport interface ParsedPurl {\n /** Original input string, unmodified. */\n raw: string;\n /** Package type (e.g., \"npm\", \"rpm\", \"pypi\", \"maven\"). Lowercased. */\n type: string;\n /** Namespace segment (e.g., \"redhat\" for RPM, \"org.apache...\" for Maven). */\n namespace: string | null;\n /** Package name. Empty string (with a warning) if the input lacks one. */\n name: string;\n /** Version after the `@` separator. URL-decoded. */\n version: string | null;\n /** Qualifier key-value pairs from the `?` segment. */\n qualifiers: Map<string, string>;\n /** Subpath from the `#` fragment. */\n subpath: string | null;\n /** Deviations or oddities encountered during parsing. */\n warnings: string[];\n}\n\nconst PURL_PREFIX = 'pkg:';\n\n/**\n * Parse a Package URL string.\n *\n * Returns `null` only when the input is missing the mandatory `pkg:` prefix\n * or the type segment. Other deviations are surfaced via `warnings` on the\n * returned object.\n *\n * @param purlStr - PURL string, e.g. `pkg:npm/lodash@4.17.21`\n * @returns Parsed PURL or `null` if the input is unrecoverably malformed\n *\n * @example\n * ```typescript\n * const r = parsePurl('pkg:rpm/redhat/openssl@1.1.1k-7.el8_4?arch=x86_64');\n * r?.type; // \"rpm\"\n * r?.namespace; // \"redhat\"\n * r?.name; // \"openssl\"\n * r?.qualifiers.get('arch'); // \"x86_64\"\n * ```\n */\nexport function parsePurl(purlStr: string): ParsedPurl | null {\n if (!purlStr || !purlStr.startsWith(PURL_PREFIX)) {\n return null;\n }\n\n const raw = purlStr;\n const warnings: string[] = [];\n\n // Strip prefix. Per spec, scheme is case-insensitive but only `pkg:` is\n // valid; we already matched it.\n let remainder = purlStr.slice(PURL_PREFIX.length);\n\n // Per spec, leading slashes after the scheme are stripped. This also means\n // `pkg:/foo` and `pkg:foo` are equivalent, but `pkg:/` with no following\n // type segment is still invalid.\n while (remainder.startsWith('/')) {\n remainder = remainder.slice(1);\n }\n\n if (remainder.length === 0) {\n return null;\n }\n\n // Extract subpath (fragment). Per spec, the fragment is everything after\n // the first `#`. Splitting before qualifiers keeps `#` inside qualifier\n // values out of scope (which is fine — qualifier values do not legally\n // contain raw `#`; they would be percent-encoded).\n let subpath: string | null = null;\n const hashIdx = remainder.indexOf('#');\n if (hashIdx !== -1) {\n subpath = remainder.slice(hashIdx + 1);\n remainder = remainder.slice(0, hashIdx);\n if (subpath.length === 0) {\n subpath = null;\n }\n }\n\n // Extract qualifiers (query string). Everything after the first `?`.\n const qualifiers = new Map<string, string>();\n const qIdx = remainder.indexOf('?');\n if (qIdx !== -1) {\n const qStr = remainder.slice(qIdx + 1);\n remainder = remainder.slice(0, qIdx);\n parseQualifiers(qStr, qualifiers, warnings);\n }\n\n // Strip a trailing slash from the path portion. Spec allows it; we silently\n // normalize.\n while (remainder.endsWith('/')) {\n remainder = remainder.slice(0, -1);\n }\n\n // Type is everything up to the first `/`.\n const firstSlash = remainder.indexOf('/');\n let type: string;\n let pathPart: string;\n if (firstSlash === -1) {\n type = remainder;\n pathPart = '';\n } else {\n type = remainder.slice(0, firstSlash);\n pathPart = remainder.slice(firstSlash + 1);\n }\n\n if (type.length === 0) {\n return null;\n }\n type = type.toLowerCase();\n\n // Split version off the path part. Per spec, the version separator is the\n // LAST `@` in the path — names may contain `@` (e.g., npm scoped packages\n // when not percent-encoded).\n let version: string | null = null;\n const lastAt = pathPart.lastIndexOf('@');\n let nameAndNs = pathPart;\n if (lastAt !== -1) {\n version = pathPart.slice(lastAt + 1);\n nameAndNs = pathPart.slice(0, lastAt);\n version = safeDecode(version);\n }\n\n // Namespace is everything before the last `/` in the name+namespace\n // section; the final segment is the name.\n let namespace: string | null = null;\n let name: string;\n const lastSlash = nameAndNs.lastIndexOf('/');\n if (lastSlash === -1) {\n name = nameAndNs;\n } else {\n namespace = nameAndNs.slice(0, lastSlash);\n name = nameAndNs.slice(lastSlash + 1);\n }\n\n // Decode percent-encoded segments.\n if (namespace !== null) {\n // Decode each path segment independently to preserve embedded `/`.\n namespace = namespace.split('/').map(safeDecode).join('/');\n }\n name = safeDecode(name);\n\n if (name.length === 0) {\n warnings.push('PURL is missing the name segment');\n }\n\n // Normalize empty namespace to null.\n if (namespace !== null && namespace.length === 0) {\n namespace = null;\n }\n\n // Normalize empty version to null.\n if (version !== null && version.length === 0) {\n version = null;\n }\n\n return {\n raw,\n type,\n namespace,\n name,\n version,\n qualifiers,\n subpath,\n warnings,\n };\n}\n\n/**\n * Parse the `?key=value&key=value` qualifier string. Empty segments are\n * skipped silently; segments with no `=` are recorded with empty value and\n * a warning. Values are URL-decoded.\n */\nfunction parseQualifiers(\n qStr: string,\n out: Map<string, string>,\n warnings: string[],\n): void {\n const parts = qStr.split('&');\n for (const part of parts) {\n if (part.length === 0) {\n continue;\n }\n const eq = part.indexOf('=');\n if (eq === -1) {\n out.set(part, '');\n warnings.push(`qualifier \"${part}\" has no value`);\n continue;\n }\n const key = part.slice(0, eq);\n const value = part.slice(eq + 1);\n out.set(key, safeDecode(value));\n }\n}\n\n/**\n * decodeURIComponent that returns the original string on failure rather than\n * throwing. PURLs in the wild occasionally contain stray `%` characters that\n * aren't legitimate percent-encoding.\n */\nfunction safeDecode(s: string): string {\n try {\n return decodeURIComponent(s);\n } catch {\n return s;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["cvss40Data"],"sources":["../src/severity/index.ts","../src/status/index.ts","../src/cvss/cvss40-data.json","../src/cvss/index.ts","../src/cpe/index.ts","../src/purl/index.ts"],"sourcesContent":["/**\n * Severity-to-impact mapping utilities.\n *\n * These define the canonical bidirectional mapping between severity labels\n * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),\n * aligned with CVSS 3.x severity bands normalized to 0–1.\n *\n * Threshold ranges (used by impactToSeverity):\n * >=0.9 = critical (CVSS 9.0–10.0)\n * [0.7, 0.9) = high (CVSS 7.0–8.9)\n * [0.4, 0.7) = medium (CVSS 4.0–6.9)\n * (0.0, 0.4) = low (CVSS 0.1–3.9)\n * 0.0 = informational (CVSS 0.0)\n */\n\nconst severityMap: Record<string, number> = {\n critical: 0.9,\n high: 0.7,\n medium: 0.5,\n low: 0.3,\n info: 0.0,\n none: 0.0,\n informational: 0.0,\n information: 0.0,\n};\n\n/**\n * Map a severity string to an impact score.\n *\n * @param severity - Severity level (case-insensitive), or null for no severity assessment\n * @returns Impact score between 0.0 and 1.0; null when input is null; defaults to 0.5 for unrecognized values\n */\nexport function severityToImpact(severity: null): null;\nexport function severityToImpact(severity: string): number;\nexport function severityToImpact(severity: string | null): number | null;\nexport function severityToImpact(severity: string | null): number | null {\n if (severity === null) return null;\n const normalized = severity.toLowerCase();\n return severityMap[normalized] ?? 0.5;\n}\n\n/**\n * Round an HDF impact value to 2 decimal places — its canonical precision —\n * eliminating the representation noise that binary float division leaves behind\n * (e.g. 9.8 / 10 = 0.9800000000000001). Impact is defined on 0.0–1.0 with a\n * natural 0.01 grid (a 1-decimal CVSS score / 10), so this is lossless in\n * intent. Use it wherever impact is COMPUTED (divided or otherwise\n * arithmetically derived), not when assigned from a literal band.\n */\nexport function roundImpact(x: number): number {\n return Math.round(x * 100) / 100;\n}\n\n/**\n * Map an impact score to a severity string.\n *\n * @param impact - Impact score (0.0 to 1.0), or null for no impact assessment\n * @returns Severity level string, or null when input is null\n */\nexport function impactToSeverity(impact: null): null;\nexport function impactToSeverity(impact: number): string;\nexport function impactToSeverity(impact: number | null): string | null;\nexport function impactToSeverity(impact: number | null): string | null {\n if (impact === null) return null;\n if (impact >= 0.9) return 'critical';\n if (impact >= 0.7) return 'high';\n if (impact >= 0.4) return 'medium';\n if (impact > 0.0) return 'low';\n return 'informational';\n}\n\n/**\n * Map a raw CVSS base score (0.0–10.0) to a FIRST qualitative severity band.\n *\n * Bands per FIRST CVSS v3.x / v4.0 spec:\n * none = 0.0\n * low = 0.1 – 3.9\n * medium = 4.0 – 6.9\n * high = 7.0 – 8.9\n * critical = 9.0 – 10.0\n *\n * Out-of-range inputs are clamped: scores < 0.1 → \"none\" (band floor),\n * scores > 10.0 → \"critical\". This matches scanner behavior of treating\n * malformed scores as informational/critical extremes rather than throwing.\n *\n * @param score - CVSS base score\n * @returns FIRST severity band label\n */\nexport function cvssScoreToSeverity(\n score: number,\n): 'none' | 'low' | 'medium' | 'high' | 'critical' {\n if (!Number.isFinite(score) || score < 0.1) return 'none';\n if (score < 4.0) return 'low';\n if (score < 7.0) return 'medium';\n if (score < 9.0) return 'high';\n return 'critical';\n}\n","/**\n * Canonical HDF result-status ordering and effective-status computation.\n *\n * This is the single source of truth for worst-wins roll-up and\n * effective-status selection across hdf-libs (mirrored in Go as\n * hdf-utilities/go status.go). The precedence rules are published in\n * site/docs/architecture/status-determination.md; do not re-implement them.\n */\n\nimport { parseTimestamp } from '../string/index.js';\n\n/** Canonical worst-wins ordering, worst first. */\nexport const STATUS_SEVERITY_ORDER: readonly string[] = [\n 'error',\n 'failed',\n 'passed',\n 'notApplicable',\n 'notReviewed',\n];\n\n/** Severity rank of a status: higher = worse. Unknown statuses rank -1. */\nexport function statusRank(status: string): number {\n const idx = STATUS_SEVERITY_ORDER.indexOf(status);\n return idx === -1 ? -1 : STATUS_SEVERITY_ORDER.length - 1 - idx;\n}\n\n/**\n * Rolls a list of result statuses up to the worst one. An empty list (or a\n * list of only unknown statuses) rolls up to \"notReviewed\".\n */\nexport function worstStatus(statuses: readonly string[]): string {\n let worst = 'notReviewed';\n let worstRank = -1;\n for (const s of statuses) {\n const r = statusRank(s);\n if (r > worstRank) {\n worstRank = r;\n worst = s;\n }\n }\n return worst;\n}\n\n/**\n * Neutral shape of a status override for effective-status computation.\n * Callers map their concrete types (schema objects or raw JSON) onto it.\n */\nexport interface StatusOverrideInput {\n /** Empty/undefined means the override carries no status and never governs. */\n status?: string;\n /** RFC3339 timestamp; undefined sorts earliest. */\n appliedAt?: string;\n /** RFC3339 timestamp; undefined means the override never expires. */\n expiresAt?: string;\n}\n\nfunction isExpired(override: StatusOverrideInput, ref: Date): boolean {\n if (!override.expiresAt) return false;\n // parseTimestamp normalizes zone-less values to UTC (a raw `new Date` would\n // read them as host-local and make expiry host-timezone-dependent).\n const expires = parseTimestamp(override.expiresAt);\n return expires !== null && expires <= ref;\n}\n\n/**\n * Selects the override that governs a requirement: the most recently applied\n * (by appliedAt) non-expired override that carries a status — matching the\n * schema's definition of disposition (\"the most recent non-expired\n * override\"). Returns undefined when no override governs.\n */\nexport function governingStatusOverride(\n overrides: readonly StatusOverrideInput[],\n referenceTimestamp?: string\n): StatusOverrideInput | undefined {\n const i = governingStatusOverrideIndex(overrides, referenceTimestamp);\n return i >= 0 ? overrides[i] : undefined;\n}\n\n/**\n * Returns the index of the most recently applied (by appliedAt) non-expired\n * override among those for which `eligible` returns true, or -1 when none\n * qualifies. Generalizes governing-override selection to per-field\n * eligibility: the schema defines effectiveStatus, effectiveImpact, and\n * disposition each as \"the most recent non-expired override\" carrying the\n * relevant field, so callers pass the field-presence check as the predicate.\n */\nexport function governingOverrideIndex(\n overrides: readonly StatusOverrideInput[],\n eligible: (index: number) => boolean,\n referenceTimestamp?: string\n): number {\n const ref = refTime(referenceTimestamp);\n let governing = -1;\n for (let i = 0; i < overrides.length; i++) {\n const override = overrides[i];\n if (!override || !eligible(i) || isExpired(override, ref)) continue;\n if (governing === -1 || appliedTime(override) > appliedTime(overrides[governing]!)) {\n governing = i;\n }\n }\n return governing;\n}\n\n/**\n * Index variant of {@link governingStatusOverride}: returns -1 when no\n * override governs. Callers holding richer concrete override types use the\n * index to recover their own object.\n */\nexport function governingStatusOverrideIndex(\n overrides: readonly StatusOverrideInput[],\n referenceTimestamp?: string\n): number {\n return governingOverrideIndex(overrides, (i) => !!overrides[i]?.status, referenceTimestamp);\n}\n\nfunction appliedTime(override: StatusOverrideInput): number {\n if (!override.appliedAt) return Number.NEGATIVE_INFINITY;\n return parseTimestamp(override.appliedAt)?.getTime() ?? Number.NEGATIVE_INFINITY;\n}\n\nfunction refTime(referenceTimestamp?: string): Date {\n const parsed = referenceTimestamp ? parseTimestamp(referenceTimestamp) : null;\n return parsed ?? new Date();\n}\n\n/** Neutral shape of a requirement for effective-status computation. */\nexport interface EffectiveStatusInput {\n impact: number;\n /** The stored effectiveStatus field; undefined means unset. */\n effectiveStatus?: string;\n resultStatuses?: readonly string[];\n overrides?: readonly StatusOverrideInput[];\n}\n\n/**\n * Determines a requirement's effective status — the single canonical\n * implementation of the precedence in status-determination.md:\n *\n * 1. impact === 0 → \"notApplicable\", regardless of results or overrides\n * 2. the governing (most recent non-expired) status override's status\n * 3. the stored effectiveStatus, honored only when NO overrides are present —\n * effectiveStatus is state derived from overrides, so when every override\n * has expired it is stale and the result roll-up wins\n * 4. worst-wins roll-up of the result statuses\n * 5. no results → \"notReviewed\"\n */\nexport function computeEffectiveStatus(\n input: EffectiveStatusInput,\n referenceTimestamp?: string\n): string {\n if (input.impact === 0) {\n return 'notApplicable';\n }\n const overrides = input.overrides ?? [];\n if (overrides.length > 0) {\n const governing = governingStatusOverride(overrides, referenceTimestamp);\n if (governing?.status) return governing.status;\n return worstStatus(input.resultStatuses ?? []);\n }\n if (input.effectiveStatus) {\n return input.effectiveStatus;\n }\n return worstStatus(input.resultStatuses ?? []);\n}\n","","/**\n * CVSS vector parsing and validation utilities.\n *\n * Supports CVSS 2.0 (legacy, no prefix), 3.0, 3.1, and 4.0 vector strings.\n * Parsing is permissive: it splits any well-shaped key:value pair separated\n * by `/`. Validation is strict per the FIRST grammar for each version.\n *\n * References:\n * - CVSS 2.0: https://www.first.org/cvss/v2/guide\n * - CVSS 3.1: https://www.first.org/cvss/v3.1/specification-document\n * - CVSS 4.0: https://www.first.org/cvss/v4.0/specification-document\n */\n\nimport cvss40Data from './cvss40-data.json';\n\nexport interface ParsedCvssVector {\n /** \"2.0\" | \"3.0\" | \"3.1\" | \"4.0\" | \"unknown\" */\n version: string;\n /** Metric name -> metric value (e.g. \"AV\" -> \"N\"). Order-preserving via Map. */\n metrics: Map<string, string>;\n}\n\nexport interface CvssValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Allowed values for each metric, keyed by CVSS major version.\n *\n * Sourced from FIRST specifications (linked above). Only metrics present\n * in a given version's grammar appear in that version's map; unknown metrics\n * encountered during validation are ignored (forward-compat).\n */\nconst METRIC_VALUES: Record<string, Record<string, string[]>> = {\n '2.0': {\n AV: ['L', 'A', 'N'],\n AC: ['H', 'M', 'L'],\n Au: ['M', 'S', 'N'],\n C: ['N', 'P', 'C'],\n I: ['N', 'P', 'C'],\n A: ['N', 'P', 'C'],\n // Temporal\n E: ['U', 'POC', 'F', 'H', 'ND'],\n RL: ['OF', 'TF', 'W', 'U', 'ND'],\n RC: ['UC', 'UR', 'C', 'ND'],\n // Environmental\n CDP: ['N', 'L', 'LM', 'MH', 'H', 'ND'],\n TD: ['N', 'L', 'M', 'H', 'ND'],\n CR: ['L', 'M', 'H', 'ND'],\n IR: ['L', 'M', 'H', 'ND'],\n AR: ['L', 'M', 'H', 'ND'],\n },\n '3.0': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '3.1': {\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'R'],\n S: ['U', 'C'],\n C: ['N', 'L', 'H'],\n I: ['N', 'L', 'H'],\n A: ['N', 'L', 'H'],\n // Temporal\n E: ['X', 'U', 'P', 'F', 'H'],\n RL: ['X', 'O', 'T', 'W', 'U'],\n RC: ['X', 'U', 'R', 'C'],\n // Environmental\n CR: ['X', 'L', 'M', 'H'],\n IR: ['X', 'L', 'M', 'H'],\n AR: ['X', 'L', 'M', 'H'],\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'R'],\n MS: ['X', 'U', 'C'],\n MC: ['X', 'N', 'L', 'H'],\n MI: ['X', 'N', 'L', 'H'],\n MA: ['X', 'N', 'L', 'H'],\n },\n '4.0': {\n // Base\n AV: ['N', 'A', 'L', 'P'],\n AC: ['L', 'H'],\n AT: ['N', 'P'],\n PR: ['N', 'L', 'H'],\n UI: ['N', 'P', 'A'],\n VC: ['H', 'L', 'N'],\n VI: ['H', 'L', 'N'],\n VA: ['H', 'L', 'N'],\n SC: ['H', 'L', 'N'],\n SI: ['H', 'L', 'N'],\n SA: ['H', 'L', 'N'],\n // Threat\n E: ['X', 'A', 'P', 'U'],\n // Environmental (modified base) — same enums as their base counterparts plus X\n MAV: ['X', 'N', 'A', 'L', 'P'],\n MAC: ['X', 'L', 'H'],\n MAT: ['X', 'N', 'P'],\n MPR: ['X', 'N', 'L', 'H'],\n MUI: ['X', 'N', 'P', 'A'],\n MVC: ['X', 'H', 'L', 'N'],\n MVI: ['X', 'H', 'L', 'N'],\n MVA: ['X', 'H', 'L', 'N'],\n MSC: ['X', 'H', 'L', 'N'],\n MSI: ['X', 'S', 'H', 'L', 'N'],\n MSA: ['X', 'S', 'H', 'L', 'N'],\n // Requirements\n CR: ['X', 'H', 'M', 'L'],\n IR: ['X', 'H', 'M', 'L'],\n AR: ['X', 'H', 'M', 'L'],\n // Supplemental\n S: ['X', 'N', 'P'],\n AU: ['X', 'N', 'Y'],\n R: ['X', 'A', 'U', 'I'],\n V: ['X', 'D', 'C'],\n RE: ['X', 'L', 'M', 'H'],\n U: ['X', 'Clear', 'Green', 'Amber', 'Red'],\n },\n};\n\nconst REQUIRED_METRICS: Record<string, string[]> = {\n '2.0': ['AV', 'AC', 'Au', 'C', 'I', 'A'],\n '3.0': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '3.1': ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A'],\n '4.0': ['AV', 'AC', 'AT', 'PR', 'UI', 'VC', 'VI', 'VA', 'SC', 'SI', 'SA'],\n};\n\n/**\n * Parse a CVSS vector string into version + metric map.\n *\n * Version detection rule: a leading `CVSS:X.Y` segment yields version \"X.Y\".\n * Otherwise (legacy v2 vectors), version is \"2.0\". Malformed input yields\n * \"unknown\" with an empty metric map; no exceptions are thrown.\n *\n * @param vector - CVSS vector string (e.g. \"CVSS:3.1/AV:N/AC:L/...\")\n * @returns Parsed version + metric map\n */\nexport function parseCvssVector(vector: string): ParsedCvssVector {\n const metrics = new Map<string, string>();\n if (typeof vector !== 'string' || vector.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n const segments = vector.split('/').filter((s) => s.length > 0);\n if (segments.length === 0) {\n return { version: 'unknown', metrics };\n }\n\n let version = '2.0';\n let start = 0;\n const first = segments[0];\n if (first === undefined) {\n return { version: 'unknown', metrics };\n }\n if (first.startsWith('CVSS:')) {\n version = first.slice(5);\n start = 1;\n } else if (!first.includes(':')) {\n // No prefix AND first segment isn't a key:value pair — bail.\n return { version: 'unknown', metrics };\n }\n\n for (let i = start; i < segments.length; i++) {\n const seg = segments[i];\n if (seg === undefined) {\n continue;\n }\n const colon = seg.indexOf(':');\n if (colon <= 0 || colon === seg.length - 1) {\n // Skip malformed segments (no colon, empty key, or empty value).\n continue;\n }\n const key = seg.slice(0, colon);\n const value = seg.slice(colon + 1);\n metrics.set(key, value);\n }\n\n return { version, metrics };\n}\n\n/**\n * Validate a CVSS vector string against the FIRST grammar for its version.\n *\n * Checks:\n * - Version is one of the supported set (2.0, 3.0, 3.1, 4.0).\n * - All required base metrics for the version are present.\n * - All known metric values are within the allowed enum.\n *\n * Unknown metric keys are tolerated (forward-compat with future minor versions\n * or scanner-specific extensions). To check a v2 vector without the CVSS:\n * prefix, callers may pass version=\"2.0\" explicitly; otherwise the parser\n * defaults to \"2.0\" on prefix absence.\n *\n * @param vector - CVSS vector string\n * @param version - Optional explicit version override (else inferred)\n * @returns `{ valid, errors[] }` — never throws\n */\nexport function validateCvssVector(\n vector: string,\n version?: string,\n): CvssValidationResult {\n const errors: string[] = [];\n\n if (typeof vector !== 'string' || vector.length === 0) {\n return { valid: false, errors: ['vector is empty'] };\n }\n\n const parsed = parseCvssVector(vector);\n const v = version ?? parsed.version;\n\n const grammar = METRIC_VALUES[v];\n const required = REQUIRED_METRICS[v];\n if (!grammar || !required) {\n errors.push(`unsupported CVSS version: ${v}`);\n return { valid: false, errors };\n }\n\n for (const req of required) {\n if (!parsed.metrics.has(req)) {\n errors.push(`missing required metric: ${req}`);\n }\n }\n\n for (const [key, value] of parsed.metrics.entries()) {\n const allowed = grammar[key];\n if (!allowed) {\n // Unknown metric: forward-compat — no error.\n continue;\n }\n if (!allowed.includes(value)) {\n errors.push(`invalid value for metric ${key}: ${value} (allowed: ${allowed.join(',')})`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n/** A computed CVSS score. */\nexport interface CvssScore {\n /** CVSS major.minor version the score was computed under (currently always \"3.1\"). */\n version: string;\n /** Base score (0.0–10.0). */\n baseScore: number;\n /**\n * Temporal (Threat) score (0.0–10.0). Equals baseScore when no temporal\n * metrics (E/RL/RC) are present, per the CVSS 3.1 spec.\n */\n temporalScore: number;\n}\n\n// CVSS 3.1 metric weights (FIRST v3.1 specification §7). Privileges Required is\n// scope-dependent, hence two tables.\nconst CVSS31_AV: Record<string, number> = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };\nconst CVSS31_AC: Record<string, number> = { L: 0.77, H: 0.44 };\nconst CVSS31_UI: Record<string, number> = { N: 0.85, R: 0.62 };\nconst CVSS31_CIA: Record<string, number> = { N: 0.0, L: 0.22, H: 0.56 };\nconst CVSS31_PR_UNCHANGED: Record<string, number> = { N: 0.85, L: 0.62, H: 0.27 };\nconst CVSS31_PR_CHANGED: Record<string, number> = { N: 0.85, L: 0.68, H: 0.5 };\nconst CVSS31_E: Record<string, number> = { X: 1.0, H: 1.0, F: 0.97, P: 0.94, U: 0.91 };\nconst CVSS31_RL: Record<string, number> = { X: 1.0, U: 1.0, W: 0.97, T: 0.96, O: 0.95 };\nconst CVSS31_RC: Record<string, number> = { X: 1.0, C: 1.0, R: 0.96, U: 0.92 };\n\n/**\n * CVSS 3.1 Roundup (spec §7.4): the smallest number, to one decimal place, that\n * is >= the input — computed via integer math to avoid the floating-point edge\n * cases 3.0 rounding suffered.\n */\nfunction roundUp31(x: number): number {\n const intInput = Math.round(x * 100000);\n if (intInput % 10000 === 0) {\n return intInput / 100000;\n }\n return (Math.floor(intInput / 10000) + 1) / 10;\n}\n\nfunction cvss31TemporalWeight(\n table: Record<string, number>,\n metrics: Map<string, string>,\n key: string,\n): number {\n const v = metrics.get(key);\n if (v === undefined) {\n return 1.0; // absent → X\n }\n const w = table[v];\n if (w === undefined) {\n throw new Error(`cvss: invalid 3.1 ${key} value \"${v}\"`);\n }\n return w;\n}\n\n/**\n * Compute the CVSS 3.1 Base and Temporal (Threat) scores for a vector string.\n *\n * Only CVSS 3.1 is supported here — CVSS 4.0's MacroVector engine lives\n * separately — so any other version throws rather than returning a wrong number.\n * The peer of the Go `ComputeCvssScore` (kept byte-identical via shared fixtures).\n *\n * @throws if the version is not 3.1, a required base metric is missing, or any\n * metric value is out of the 3.1 enum.\n */\nexport function computeCvssScore(vector: string): CvssScore {\n const { version, metrics } = parseCvssVector(vector);\n if (version !== '3.1') {\n throw new Error(`cvss: score compute supports only CVSS 3.1, got \"${version}\"`);\n }\n for (const k of ['AV', 'AC', 'PR', 'UI', 'S', 'C', 'I', 'A']) {\n if (!metrics.has(k)) {\n throw new Error(`cvss: missing required 3.1 base metric \"${k}\"`);\n }\n }\n\n const scope = metrics.get('S');\n if (scope !== 'U' && scope !== 'C') {\n throw new Error(`cvss: invalid 3.1 Scope value \"${scope ?? ''}\"`);\n }\n const scopeChanged = scope === 'C';\n\n const prTable = scopeChanged ? CVSS31_PR_CHANGED : CVSS31_PR_UNCHANGED;\n const lookup = (table: Record<string, number>, metric: string): number => {\n const key = metrics.get(metric);\n const w = key === undefined ? undefined : table[key];\n if (w === undefined) {\n throw new Error(`cvss: invalid 3.1 metric value ${metric}:\"${key ?? ''}\"`);\n }\n return w;\n };\n const av = lookup(CVSS31_AV, 'AV');\n const ac = lookup(CVSS31_AC, 'AC');\n const ui = lookup(CVSS31_UI, 'UI');\n const pr = lookup(prTable, 'PR');\n const c = lookup(CVSS31_CIA, 'C');\n const i = lookup(CVSS31_CIA, 'I');\n const a = lookup(CVSS31_CIA, 'A');\n\n const iss = 1 - (1 - c) * (1 - i) * (1 - a);\n const impact = scopeChanged\n ? 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15)\n : 6.42 * iss;\n const exploitability = 8.22 * av * ac * pr * ui;\n\n let baseScore: number;\n if (impact <= 0) {\n baseScore = 0.0;\n } else if (scopeChanged) {\n baseScore = roundUp31(Math.min(1.08 * (impact + exploitability), 10));\n } else {\n baseScore = roundUp31(Math.min(impact + exploitability, 10));\n }\n\n const e = cvss31TemporalWeight(CVSS31_E, metrics, 'E');\n const rl = cvss31TemporalWeight(CVSS31_RL, metrics, 'RL');\n const rc = cvss31TemporalWeight(CVSS31_RC, metrics, 'RC');\n const temporalScore = roundUp31(baseScore * e * rl * rc);\n\n return { version: '3.1', baseScore, temporalScore };\n}\n\n// --- CVSS 4.0 scoring (MacroVector engine) ---\n//\n// A faithful port of the FIRST reference calculator (FIRSTdotorg/cvss-v4-calculator:\n// cvss_score.js), driven by the MacroVector lookup + max tables extracted verbatim\n// into cvss40-data.json. The Go peer (go/cvss.go) reads the byte-identical data\n// file and shares the parity fixture, so the two languages score identically.\n\ninterface Cvss40Tables {\n lookup: Record<string, number>;\n maxComposed: {\n eq1: Record<string, string[]>;\n eq2: Record<string, string[]>;\n eq3: Record<string, Record<string, string[]>>;\n eq4: Record<string, string[]>;\n eq5: Record<string, string[]>;\n };\n maxSeverity: {\n eq1: Record<string, number>;\n eq2: Record<string, number>;\n eq3eq6: Record<string, Record<string, number>>;\n eq4: Record<string, number>;\n eq5: Record<string, number>;\n };\n}\nconst CVSS40 = cvss40Data as Cvss40Tables;\n\n// Per-metric severity index tables (FIRST reference cvss_score.js). Lower = worse.\nconst CVSS40_AV: Record<string, number> = { N: 0.0, A: 0.1, L: 0.2, P: 0.3 };\nconst CVSS40_PR: Record<string, number> = { N: 0.0, L: 0.1, H: 0.2 };\nconst CVSS40_UI: Record<string, number> = { N: 0.0, P: 0.1, A: 0.2 };\nconst CVSS40_AC: Record<string, number> = { L: 0.0, H: 0.1 };\nconst CVSS40_AT: Record<string, number> = { N: 0.0, P: 0.1 };\nconst CVSS40_VC: Record<string, number> = { H: 0.0, L: 0.1, N: 0.2 };\nconst CVSS40_SC: Record<string, number> = { H: 0.1, L: 0.2, N: 0.3 };\nconst CVSS40_SI: Record<string, number> = { S: 0.0, H: 0.1, L: 0.2, N: 0.3 };\nconst CVSS40_CR: Record<string, number> = { H: 0.0, M: 0.1, L: 0.2 };\n\n/** Throw on an unexpected missing table entry rather than fabricate a value. */\nfunction req<T>(value: T | undefined, what: string): T {\n if (value === undefined) {\n throw new Error(`cvss: 4.0 data table missing ${what}`);\n }\n return value;\n}\n\n/**\n * Resolve the effective value of a metric, mirroring the reference m(): E:X and\n * unset default to A (worst case); CR/IR/AR:X and unset default to H; a present,\n * non-X Modified (M-prefixed) metric overrides its base.\n */\nfunction cvss40Metric(sel: Map<string, string>, metric: string): string {\n const selected = sel.get(metric) ?? '';\n if (metric === 'E' && (selected === '' || selected === 'X')) {\n return 'A';\n }\n if ((metric === 'CR' || metric === 'IR' || metric === 'AR') && (selected === '' || selected === 'X')) {\n return 'H';\n }\n const modified = sel.get('M' + metric);\n if (modified !== undefined && modified !== 'X') {\n return modified;\n }\n return selected;\n}\n\nfunction cvss40MacroVectorFromSel(sel: Map<string, string>): string {\n const mv = (k: string): string => cvss40Metric(sel, k);\n\n // EQ1 (the \"not all three N\" term of the reference's level-1 clause is\n // redundant here: the level-0 branch already consumes the all-three-N case).\n let eq1 = '2';\n if (mv('AV') === 'N' && mv('PR') === 'N' && mv('UI') === 'N') {\n eq1 = '0';\n } else if ((mv('AV') === 'N' || mv('PR') === 'N' || mv('UI') === 'N') && mv('AV') !== 'P') {\n eq1 = '1';\n }\n\n let eq2 = '1';\n if (mv('AC') === 'L' && mv('AT') === 'N') {\n eq2 = '0';\n }\n\n let eq3 = '2';\n if (mv('VC') === 'H' && mv('VI') === 'H') {\n eq3 = '0';\n } else if (mv('VC') === 'H' || mv('VI') === 'H' || mv('VA') === 'H') {\n eq3 = '1';\n }\n\n let eq4 = '2';\n if (mv('MSI') === 'S' || mv('MSA') === 'S') {\n eq4 = '0';\n } else if (mv('SC') === 'H' || mv('SI') === 'H' || mv('SA') === 'H') {\n eq4 = '1';\n }\n\n let eq5 = '2';\n if (mv('E') === 'A') {\n eq5 = '0';\n } else if (mv('E') === 'P') {\n eq5 = '1';\n }\n\n let eq6 = '1';\n if (\n (mv('CR') === 'H' && mv('VC') === 'H') ||\n (mv('IR') === 'H' && mv('VI') === 'H') ||\n (mv('AR') === 'H' && mv('VA') === 'H')\n ) {\n eq6 = '0';\n }\n\n return eq1 + eq2 + eq3 + eq4 + eq5 + eq6;\n}\n\n/** Parse a CVSS 4.0 vector and return its MacroVector string. */\nexport function cvss40MacroVector(vector: string): string {\n return cvss40MacroVectorFromSel(parseCvssVector(vector).metrics);\n}\n\n/** Pull a metric's value out of a composed max-vector string (reference extractValueMetric). */\nfunction cvss40ExtractMetric(metric: string, s: string): string {\n const idx = s.indexOf(metric);\n if (idx < 0) {\n return '';\n }\n const rest = s.slice(idx + metric.length + 1);\n const slash = rest.indexOf('/');\n return slash >= 0 ? rest.slice(0, slash) : rest;\n}\n\n/** value minus the lower macro's score, or NaN when that lower macro does not exist. */\nfunction cvss40AvailDist(value: number, lowerMacro: string): number {\n const s = CVSS40.lookup[lowerMacro];\n return s === undefined ? NaN : value - s;\n}\n\nfunction cvss40Score(sel: Map<string, string>): number {\n const mv = (k: string): string => cvss40Metric(sel, k);\n\n if (\n mv('VC') === 'N' && mv('VI') === 'N' && mv('VA') === 'N' &&\n mv('SC') === 'N' && mv('SI') === 'N' && mv('SA') === 'N'\n ) {\n return 0.0;\n }\n\n const macro = cvss40MacroVectorFromSel(sel);\n let value = req(CVSS40.lookup[macro], `lookup[${macro}]`);\n\n // Definite per-position digit chars (charAt never yields undefined).\n const c0 = macro.charAt(0);\n const c1 = macro.charAt(1);\n const c2 = macro.charAt(2);\n const c3 = macro.charAt(3);\n const c4 = macro.charAt(4);\n const c5 = macro.charAt(5);\n\n const d = (i: number): number => macro.charCodeAt(i) - 48;\n const eq3 = d(2);\n const eq6 = d(5);\n const rep = (i: number, digit: number): string =>\n macro.slice(0, i) + String(digit) + macro.slice(i + 1);\n\n const availDistEq1 = cvss40AvailDist(value, rep(0, d(0) + 1));\n const availDistEq2 = cvss40AvailDist(value, rep(1, d(1) + 1));\n const availDistEq4 = cvss40AvailDist(value, rep(3, d(3) + 1));\n const availDistEq5 = cvss40AvailDist(value, rep(4, d(4) + 1));\n\n // eq3 and eq6 are coupled.\n let availDistEq3eq6 = NaN;\n if ((eq3 === 1 && eq6 === 1) || (eq3 === 0 && eq6 === 1)) {\n availDistEq3eq6 = cvss40AvailDist(value, rep(2, eq3 + 1));\n } else if (eq3 === 1 && eq6 === 0) {\n availDistEq3eq6 = cvss40AvailDist(value, rep(5, eq6 + 1));\n } else if (eq3 === 0 && eq6 === 0) {\n const left = CVSS40.lookup[rep(5, eq6 + 1)];\n const right = CVSS40.lookup[rep(2, eq3 + 1)];\n if (left !== undefined && right !== undefined) {\n availDistEq3eq6 = value - Math.max(left, right);\n } else if (right !== undefined) {\n availDistEq3eq6 = value - right;\n }\n } else {\n availDistEq3eq6 = cvss40AvailDist(value, macro.slice(0, 2) + String(eq3 + 1) + macro.slice(3, 5) + String(eq6 + 1));\n }\n\n const eq1Maxes = req(CVSS40.maxComposed.eq1[c0], `maxComposed.eq1[${c0}]`);\n const eq2Maxes = req(CVSS40.maxComposed.eq2[c1], `maxComposed.eq2[${c1}]`);\n const eq3eq6Maxes = req(\n req(CVSS40.maxComposed.eq3[c2], `maxComposed.eq3[${c2}]`)[c5],\n `maxComposed.eq3[${c2}][${c5}]`,\n );\n const eq4Maxes = req(CVSS40.maxComposed.eq4[c3], `maxComposed.eq4[${c3}]`);\n const eq5Maxes = req(CVSS40.maxComposed.eq5[c4], `maxComposed.eq5[${c4}]`);\n\n const maxVectors: string[] = [];\n for (const a of eq1Maxes) {\n for (const b of eq2Maxes) {\n for (const c of eq3eq6Maxes) {\n for (const e of eq4Maxes) {\n for (const f of eq5Maxes) {\n maxVectors.push(a + b + c + e + f);\n }\n }\n }\n }\n }\n\n const dist = (table: Record<string, number>, metric: string, maxVector: string): number =>\n req(table[mv(metric)], `level ${metric}:${mv(metric)}`) -\n req(table[cvss40ExtractMetric(metric, maxVector)], `max level ${metric}`);\n\n let sdAV = 0, sdPR = 0, sdUI = 0, sdAC = 0, sdAT = 0, sdVC = 0, sdVI = 0, sdVA = 0;\n let sdSC = 0, sdSI = 0, sdSA = 0, sdCR = 0, sdIR = 0, sdAR = 0;\n for (const maxVector of maxVectors) {\n sdAV = dist(CVSS40_AV, 'AV', maxVector);\n sdPR = dist(CVSS40_PR, 'PR', maxVector);\n sdUI = dist(CVSS40_UI, 'UI', maxVector);\n sdAC = dist(CVSS40_AC, 'AC', maxVector);\n sdAT = dist(CVSS40_AT, 'AT', maxVector);\n sdVC = dist(CVSS40_VC, 'VC', maxVector);\n sdVI = dist(CVSS40_VC, 'VI', maxVector);\n sdVA = dist(CVSS40_VC, 'VA', maxVector);\n sdSC = dist(CVSS40_SC, 'SC', maxVector);\n sdSI = dist(CVSS40_SI, 'SI', maxVector);\n sdSA = dist(CVSS40_SI, 'SA', maxVector);\n sdCR = dist(CVSS40_CR, 'CR', maxVector);\n sdIR = dist(CVSS40_CR, 'IR', maxVector);\n sdAR = dist(CVSS40_CR, 'AR', maxVector);\n if (\n [sdAV, sdPR, sdUI, sdAC, sdAT, sdVC, sdVI, sdVA, sdSC, sdSI, sdSA, sdCR, sdIR, sdAR].some(\n (v) => v < 0,\n )\n ) {\n continue;\n }\n break;\n }\n\n const curEq1 = sdAV + sdPR + sdUI;\n const curEq2 = sdAC + sdAT;\n const curEq3eq6 = sdVC + sdVI + sdVA + sdCR + sdIR + sdAR;\n const curEq4 = sdSC + sdSI + sdSA;\n\n const step = 0.1;\n const maxSevEq1 = req(CVSS40.maxSeverity.eq1[c0], `maxSeverity.eq1[${c0}]`) * step;\n const maxSevEq2 = req(CVSS40.maxSeverity.eq2[c1], `maxSeverity.eq2[${c1}]`) * step;\n const maxSevEq3eq6 =\n req(\n req(CVSS40.maxSeverity.eq3eq6[c2], `maxSeverity.eq3eq6[${c2}]`)[c5],\n `maxSeverity.eq3eq6[${c2}][${c5}]`,\n ) * step;\n const maxSevEq4 = req(CVSS40.maxSeverity.eq4[c3], `maxSeverity.eq4[${c3}]`) * step;\n\n let nExisting = 0;\n let normEq1 = 0, normEq2 = 0, normEq3eq6 = 0, normEq4 = 0;\n const normEq5 = 0; // eq5 proportion is always 0 per the reference\n if (!Number.isNaN(availDistEq1)) {\n nExisting++;\n normEq1 = availDistEq1 * (curEq1 / maxSevEq1);\n }\n if (!Number.isNaN(availDistEq2)) {\n nExisting++;\n normEq2 = availDistEq2 * (curEq2 / maxSevEq2);\n }\n if (!Number.isNaN(availDistEq3eq6)) {\n nExisting++;\n normEq3eq6 = availDistEq3eq6 * (curEq3eq6 / maxSevEq3eq6);\n }\n if (!Number.isNaN(availDistEq4)) {\n nExisting++;\n normEq4 = availDistEq4 * (curEq4 / maxSevEq4);\n }\n if (!Number.isNaN(availDistEq5)) {\n nExisting++;\n }\n\n const meanDistance =\n nExisting === 0 ? 0 : (normEq1 + normEq2 + normEq3eq6 + normEq4 + normEq5) / nExisting;\n\n value -= meanDistance;\n if (value < 0) {\n value = 0.0;\n }\n if (value > 10) {\n value = 10.0;\n }\n return Math.round(value * 10) / 10;\n}\n\n/** Remove the E: segment so the Base score reflects the worst-case default (E:X → E:A). */\nfunction cvss40StripExploitMaturity(vector: string): string {\n return vector\n .split('/')\n .filter((seg) => !seg.startsWith('E:'))\n .join('/');\n}\n\n/**\n * Compute the CVSS 4.0 score for a vector string, split into a Base score\n * (`baseScore`, with Exploit Maturity stripped to its worst-case default E:A) and\n * a Threat score (`temporalScore`, with the E value present in the vector). For a\n * base-only vector the two are equal. The peer of the Go `ComputeCvss40Score`,\n * kept byte-identical via the shared data file and parity fixture.\n *\n * @throws if the version is not 4.0, a required base metric is missing, or any\n * metric value is out of the 4.0 enum.\n */\nexport function computeCvss40Score(vector: string): CvssScore {\n const { version } = parseCvssVector(vector);\n if (version !== '4.0') {\n throw new Error(`cvss: 4.0 score compute supports only CVSS 4.0, got \"${version}\"`);\n }\n const { valid, errors } = validateCvssVector(vector, '4.0');\n if (!valid) {\n throw new Error(`cvss: invalid 4.0 vector: ${errors.join('; ')}`);\n }\n\n const temporalScore = cvss40Score(parseCvssVector(vector).metrics);\n const baseScore = cvss40Score(parseCvssVector(cvss40StripExploitMaturity(vector)).metrics);\n return { version: '4.0', baseScore, temporalScore };\n}\n","/**\n * CPE 2.3 URI parser for HDF converters.\n *\n * Common Platform Enumeration (CPE) 2.3 URIs identify affected products.\n * Container scanners (grype, twistlock, snyk) emit them in vulnerability\n * findings. This module provides an accept-and-warn parser: any input that\n * carries the `cpe:2.3:` prefix is best-effort parsed; deviations from the\n * canonical 13-field form are reported via `warnings` rather than rejected.\n *\n * Canonical form:\n * `cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw:target_hw:other`\n *\n * Reference: NIST IR 7695 (https://csrc.nist.gov/publications/detail/nistir/7695/final)\n */\n\n/** Valid CPE 2.3 `part` values. */\nexport type CpePart = 'a' | 'o' | 'h' | '*';\n\n/**\n * Parsed CPE 2.3 URI.\n *\n * `warnings` collects any deviations from the canonical form (truncation,\n * extra fields, unknown `part`). An empty array means the input was a strict\n * 13-field, valid-part CPE.\n */\nexport interface ParsedCpe {\n /** The original input string, unmodified. */\n raw: string;\n /** Application kind. `'a'` (app), `'o'` (OS), `'h'` (hardware), `'*'` (any). */\n part: CpePart | string;\n vendor: string;\n product: string;\n version: string;\n update: string;\n edition: string;\n language: string;\n swEdition: string;\n targetSw: string;\n targetHw: string;\n other: string;\n /** Deviation messages; empty when the input is a canonical 13-field CPE. */\n warnings: string[];\n}\n\nconst CPE_PREFIX = 'cpe:2.3:';\nconst EXPECTED_TOTAL_FIELDS = 13; // \"cpe\", \"2.3\", and 11 product fields = 13 colon-separated tokens\nconst PRODUCT_FIELD_COUNT = 11; // part + 10 attribute fields\nconst VALID_PARTS = new Set(['a', 'o', 'h', '*']);\n\n/**\n * Split a CPE 2.3 body on unescaped `:` separators.\n *\n * Per CPE 2.3 spec section 6.1.2.4, `:` and `\\` inside a field must be\n * escaped as `\\:` and `\\\\`. We respect those escapes during the split and\n * unescape the result.\n */\nfunction splitOnUnescapedColons(body: string): string[] {\n const result: string[] = [];\n let current = '';\n for (let i = 0; i < body.length; i++) {\n const ch = body[i];\n if (ch === '\\\\' && i + 1 < body.length) {\n const next = body[i + 1];\n // Preserve known escapes (\\: and \\\\) as a literal next character.\n if (next === ':' || next === '\\\\') {\n current += next;\n i++;\n continue;\n }\n // Unknown escape — keep the backslash and continue.\n current += ch;\n continue;\n }\n if (ch === ':') {\n result.push(current);\n current = '';\n continue;\n }\n current += ch;\n }\n result.push(current);\n return result;\n}\n\n/**\n * Parse a CPE 2.3 URI.\n *\n * Accept-and-warn semantics:\n * - Input missing the `cpe:2.3:` prefix → returns `null`.\n * - Input with the prefix but fewer than 11 product fields → fields are\n * padded with `\"*\"` and a `truncated:` warning is added.\n * - Input with more than 11 product fields → extras are dropped and an\n * `\"extra fields ignored\"` warning is added.\n * - Unknown `part` value → kept as-is in the result, `\"unknown part: X\"`\n * warning is added.\n * - `\\:` and `\\\\` escapes inside fields are honored during splitting and\n * unescaped in the returned field values.\n *\n * @param cpeUri - CPE 2.3 URI string\n * @returns Parsed CPE with warnings, or `null` if input lacks the prefix\n *\n * @example\n * ```typescript\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k:*:*:*:*:*:*:*');\n * // { part: 'a', vendor: 'openssl', product: 'openssl', version: '1.1.1k', ... warnings: [] }\n *\n * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k');\n * // truncated input → padded with '*', warnings: ['truncated: expected 13 colon-separated fields, got 6']\n *\n * parseCpe('openssl:1.1.1k'); // null — no prefix\n * ```\n */\nexport function parseCpe(cpeUri: string): ParsedCpe | null {\n if (!cpeUri.startsWith(CPE_PREFIX)) {\n return null;\n }\n\n const warnings: string[] = [];\n const body = cpeUri.slice(CPE_PREFIX.length);\n\n // Special case: bare prefix `cpe:2.3:` — all fields empty, still warn.\n // This is the \"I have nothing useful here\" form; padding with \"*\" would\n // suggest the parser inferred wildcards, which it didn't.\n const isBarePrefix = body.length === 0;\n\n const bodyParts = isBarePrefix ? [] : splitOnUnescapedColons(body);\n\n // Total colon-separated fields including the prefix \"cpe\" and \"2.3\" markers.\n const totalFields = isBarePrefix ? 2 : 2 + bodyParts.length;\n\n if (bodyParts.length > PRODUCT_FIELD_COUNT) {\n warnings.push('extra fields ignored');\n } else if (bodyParts.length < PRODUCT_FIELD_COUNT) {\n warnings.push(\n `truncated: expected ${EXPECTED_TOTAL_FIELDS} colon-separated fields, got ${totalFields}`,\n );\n }\n\n // Pad missing fields. For a bare prefix, pad with \"\" (no inferred data).\n // For partial input, pad missing trailing fields with \"*\" (wildcard).\n const padValue = isBarePrefix ? '' : '*';\n const fields: string[] = bodyParts.slice(0, PRODUCT_FIELD_COUNT);\n while (fields.length < PRODUCT_FIELD_COUNT) {\n fields.push(padValue);\n }\n\n // fields is padded to PRODUCT_FIELD_COUNT above, so every slot is present;\n // the destructuring defaults only satisfy the compiler's indexed-access check.\n const [\n part = '',\n vendor = '',\n product = '',\n version = '',\n update = '',\n edition = '',\n language = '',\n swEdition = '',\n targetSw = '',\n targetHw = '',\n other = '',\n ] = fields;\n\n if (!VALID_PARTS.has(part)) {\n warnings.push(`unknown part: ${part}`);\n }\n\n return {\n raw: cpeUri,\n part,\n vendor,\n product,\n version,\n update,\n edition,\n language,\n swEdition,\n targetSw,\n targetHw,\n other,\n warnings,\n };\n}\n","/**\n * Package URL (PURL) parser for SBOM and CVE-scanner ingestion.\n *\n * PURL is the canonical package identifier used by CycloneDX, SPDX, OSV,\n * GitHub Advisory Database, and most modern scanners. Spec:\n * https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst\n *\n * Grammar: `pkg:type/namespace/name@version?qualifiers#subpath`\n *\n * This parser is intentionally accept-and-warn. Scanners occasionally emit\n * slightly malformed PURLs (qualifier with no `=`, missing name, etc.) and\n * we'd rather record a warning than fail ingestion.\n */\n\n/**\n * Result of parsing a PURL string.\n */\nexport interface ParsedPurl {\n /** Original input string, unmodified. */\n raw: string;\n /** Package type (e.g., \"npm\", \"rpm\", \"pypi\", \"maven\"). Lowercased. */\n type: string;\n /** Namespace segment (e.g., \"redhat\" for RPM, \"org.apache...\" for Maven). */\n namespace: string | null;\n /** Package name. Empty string (with a warning) if the input lacks one. */\n name: string;\n /** Version after the `@` separator. URL-decoded. */\n version: string | null;\n /** Qualifier key-value pairs from the `?` segment. */\n qualifiers: Map<string, string>;\n /** Subpath from the `#` fragment. */\n subpath: string | null;\n /** Deviations or oddities encountered during parsing. */\n warnings: string[];\n}\n\nconst PURL_PREFIX = 'pkg:';\n\n/**\n * Parse a Package URL string.\n *\n * Returns `null` only when the input is missing the mandatory `pkg:` prefix\n * or the type segment. Other deviations are surfaced via `warnings` on the\n * returned object.\n *\n * @param purlStr - PURL string, e.g. `pkg:npm/lodash@4.17.21`\n * @returns Parsed PURL or `null` if the input is unrecoverably malformed\n *\n * @example\n * ```typescript\n * const r = parsePurl('pkg:rpm/redhat/openssl@1.1.1k-7.el8_4?arch=x86_64');\n * r?.type; // \"rpm\"\n * r?.namespace; // \"redhat\"\n * r?.name; // \"openssl\"\n * r?.qualifiers.get('arch'); // \"x86_64\"\n * ```\n */\nexport function parsePurl(purlStr: string): ParsedPurl | null {\n if (!purlStr || !purlStr.startsWith(PURL_PREFIX)) {\n return null;\n }\n\n const raw = purlStr;\n const warnings: string[] = [];\n\n // Strip prefix. Per spec, scheme is case-insensitive but only `pkg:` is\n // valid; we already matched it.\n let remainder = purlStr.slice(PURL_PREFIX.length);\n\n // Per spec, leading slashes after the scheme are stripped. This also means\n // `pkg:/foo` and `pkg:foo` are equivalent, but `pkg:/` with no following\n // type segment is still invalid.\n while (remainder.startsWith('/')) {\n remainder = remainder.slice(1);\n }\n\n if (remainder.length === 0) {\n return null;\n }\n\n // Extract subpath (fragment). Per spec, the fragment is everything after\n // the first `#`. Splitting before qualifiers keeps `#` inside qualifier\n // values out of scope (which is fine — qualifier values do not legally\n // contain raw `#`; they would be percent-encoded).\n let subpath: string | null = null;\n const hashIdx = remainder.indexOf('#');\n if (hashIdx !== -1) {\n subpath = remainder.slice(hashIdx + 1);\n remainder = remainder.slice(0, hashIdx);\n if (subpath.length === 0) {\n subpath = null;\n }\n }\n\n // Extract qualifiers (query string). Everything after the first `?`.\n const qualifiers = new Map<string, string>();\n const qIdx = remainder.indexOf('?');\n if (qIdx !== -1) {\n const qStr = remainder.slice(qIdx + 1);\n remainder = remainder.slice(0, qIdx);\n parseQualifiers(qStr, qualifiers, warnings);\n }\n\n // Strip a trailing slash from the path portion. Spec allows it; we silently\n // normalize.\n while (remainder.endsWith('/')) {\n remainder = remainder.slice(0, -1);\n }\n\n // Type is everything up to the first `/`.\n const firstSlash = remainder.indexOf('/');\n let type: string;\n let pathPart: string;\n if (firstSlash === -1) {\n type = remainder;\n pathPart = '';\n } else {\n type = remainder.slice(0, firstSlash);\n pathPart = remainder.slice(firstSlash + 1);\n }\n\n if (type.length === 0) {\n return null;\n }\n type = type.toLowerCase();\n\n // Split version off the path part. Per spec, the version separator is the\n // LAST `@` in the path — names may contain `@` (e.g., npm scoped packages\n // when not percent-encoded).\n let version: string | null = null;\n const lastAt = pathPart.lastIndexOf('@');\n let nameAndNs = pathPart;\n if (lastAt !== -1) {\n version = pathPart.slice(lastAt + 1);\n nameAndNs = pathPart.slice(0, lastAt);\n version = safeDecode(version);\n }\n\n // Namespace is everything before the last `/` in the name+namespace\n // section; the final segment is the name.\n let namespace: string | null = null;\n let name: string;\n const lastSlash = nameAndNs.lastIndexOf('/');\n if (lastSlash === -1) {\n name = nameAndNs;\n } else {\n namespace = nameAndNs.slice(0, lastSlash);\n name = nameAndNs.slice(lastSlash + 1);\n }\n\n // Decode percent-encoded segments.\n if (namespace !== null) {\n // Decode each path segment independently to preserve embedded `/`.\n namespace = namespace.split('/').map(safeDecode).join('/');\n }\n name = safeDecode(name);\n\n if (name.length === 0) {\n warnings.push('PURL is missing the name segment');\n }\n\n // Normalize empty namespace to null.\n if (namespace !== null && namespace.length === 0) {\n namespace = null;\n }\n\n // Normalize empty version to null.\n if (version !== null && version.length === 0) {\n version = null;\n }\n\n return {\n raw,\n type,\n namespace,\n name,\n version,\n qualifiers,\n subpath,\n warnings,\n };\n}\n\n/**\n * Parse the `?key=value&key=value` qualifier string. Empty segments are\n * skipped silently; segments with no `=` are recorded with empty value and\n * a warning. Values are URL-decoded.\n */\nfunction parseQualifiers(\n qStr: string,\n out: Map<string, string>,\n warnings: string[],\n): void {\n const parts = qStr.split('&');\n for (const part of parts) {\n if (part.length === 0) {\n continue;\n }\n const eq = part.indexOf('=');\n if (eq === -1) {\n out.set(part, '');\n warnings.push(`qualifier \"${part}\" has no value`);\n continue;\n }\n const key = part.slice(0, eq);\n const value = part.slice(eq + 1);\n out.set(key, safeDecode(value));\n }\n}\n\n/**\n * decodeURIComponent that returns the original string on failure rather than\n * throwing. PURLs in the wild occasionally contain stray `%` characters that\n * aren't legitimate percent-encoding.\n */\nfunction safeDecode(s: string): string {\n try {\n return decodeURIComponent(s);\n } catch {\n return s;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/hdf-utilities",
3
- "version": "3.4.4",
3
+ "version": "3.5.0",
4
4
  "description": "Utility functions for HDF libraries (JSON parsing, validation helpers)",
5
5
  "publishConfig": {
6
6
  "access": "public"