@mitre/hdf-utilities 3.4.4 → 3.5.1

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.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