@mitre/hdf-utilities 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/csv/index.d.ts.map +1 -1
- package/dist/csv/index.js.map +1 -1
- package/dist/hash/index.js.map +1 -1
- package/dist/index.d.ts +200 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +884 -1
- package/dist/index.js.map +1 -1
- package/dist/json/index.d.ts.map +1 -1
- package/dist/object/index.js.map +1 -1
- package/dist/string/index.js.map +1 -1
- package/dist/xml/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41,7 +41,890 @@ function impactToSeverity(impact) {
|
|
|
41
41
|
if (impact > 0) return "low";
|
|
42
42
|
return "informational";
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Map a raw CVSS base score (0.0–10.0) to a FIRST qualitative severity band.
|
|
46
|
+
*
|
|
47
|
+
* Bands per FIRST CVSS v3.x / v4.0 spec:
|
|
48
|
+
* none = 0.0
|
|
49
|
+
* low = 0.1 – 3.9
|
|
50
|
+
* medium = 4.0 – 6.9
|
|
51
|
+
* high = 7.0 – 8.9
|
|
52
|
+
* critical = 9.0 – 10.0
|
|
53
|
+
*
|
|
54
|
+
* Out-of-range inputs are clamped: scores < 0.1 → "none" (band floor),
|
|
55
|
+
* scores > 10.0 → "critical". This matches scanner behavior of treating
|
|
56
|
+
* malformed scores as informational/critical extremes rather than throwing.
|
|
57
|
+
*
|
|
58
|
+
* @param score - CVSS base score
|
|
59
|
+
* @returns FIRST severity band label
|
|
60
|
+
*/
|
|
61
|
+
function cvssScoreToSeverity(score) {
|
|
62
|
+
if (!Number.isFinite(score) || score < .1) return "none";
|
|
63
|
+
if (score < 4) return "low";
|
|
64
|
+
if (score < 7) return "medium";
|
|
65
|
+
if (score < 9) return "high";
|
|
66
|
+
return "critical";
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/cvss/index.ts
|
|
70
|
+
/**
|
|
71
|
+
* Allowed values for each metric, keyed by CVSS major version.
|
|
72
|
+
*
|
|
73
|
+
* Sourced from FIRST specifications (linked above). Only metrics present
|
|
74
|
+
* in a given version's grammar appear in that version's map; unknown metrics
|
|
75
|
+
* encountered during validation are ignored (forward-compat).
|
|
76
|
+
*/
|
|
77
|
+
const METRIC_VALUES = {
|
|
78
|
+
"2.0": {
|
|
79
|
+
AV: [
|
|
80
|
+
"L",
|
|
81
|
+
"A",
|
|
82
|
+
"N"
|
|
83
|
+
],
|
|
84
|
+
AC: [
|
|
85
|
+
"H",
|
|
86
|
+
"M",
|
|
87
|
+
"L"
|
|
88
|
+
],
|
|
89
|
+
Au: [
|
|
90
|
+
"M",
|
|
91
|
+
"S",
|
|
92
|
+
"N"
|
|
93
|
+
],
|
|
94
|
+
C: [
|
|
95
|
+
"N",
|
|
96
|
+
"P",
|
|
97
|
+
"C"
|
|
98
|
+
],
|
|
99
|
+
I: [
|
|
100
|
+
"N",
|
|
101
|
+
"P",
|
|
102
|
+
"C"
|
|
103
|
+
],
|
|
104
|
+
A: [
|
|
105
|
+
"N",
|
|
106
|
+
"P",
|
|
107
|
+
"C"
|
|
108
|
+
],
|
|
109
|
+
E: [
|
|
110
|
+
"U",
|
|
111
|
+
"POC",
|
|
112
|
+
"F",
|
|
113
|
+
"H",
|
|
114
|
+
"ND"
|
|
115
|
+
],
|
|
116
|
+
RL: [
|
|
117
|
+
"OF",
|
|
118
|
+
"TF",
|
|
119
|
+
"W",
|
|
120
|
+
"U",
|
|
121
|
+
"ND"
|
|
122
|
+
],
|
|
123
|
+
RC: [
|
|
124
|
+
"UC",
|
|
125
|
+
"UR",
|
|
126
|
+
"C",
|
|
127
|
+
"ND"
|
|
128
|
+
],
|
|
129
|
+
CDP: [
|
|
130
|
+
"N",
|
|
131
|
+
"L",
|
|
132
|
+
"LM",
|
|
133
|
+
"MH",
|
|
134
|
+
"H",
|
|
135
|
+
"ND"
|
|
136
|
+
],
|
|
137
|
+
TD: [
|
|
138
|
+
"N",
|
|
139
|
+
"L",
|
|
140
|
+
"M",
|
|
141
|
+
"H",
|
|
142
|
+
"ND"
|
|
143
|
+
],
|
|
144
|
+
CR: [
|
|
145
|
+
"L",
|
|
146
|
+
"M",
|
|
147
|
+
"H",
|
|
148
|
+
"ND"
|
|
149
|
+
],
|
|
150
|
+
IR: [
|
|
151
|
+
"L",
|
|
152
|
+
"M",
|
|
153
|
+
"H",
|
|
154
|
+
"ND"
|
|
155
|
+
],
|
|
156
|
+
AR: [
|
|
157
|
+
"L",
|
|
158
|
+
"M",
|
|
159
|
+
"H",
|
|
160
|
+
"ND"
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
"3.0": {
|
|
164
|
+
AV: [
|
|
165
|
+
"N",
|
|
166
|
+
"A",
|
|
167
|
+
"L",
|
|
168
|
+
"P"
|
|
169
|
+
],
|
|
170
|
+
AC: ["L", "H"],
|
|
171
|
+
PR: [
|
|
172
|
+
"N",
|
|
173
|
+
"L",
|
|
174
|
+
"H"
|
|
175
|
+
],
|
|
176
|
+
UI: ["N", "R"],
|
|
177
|
+
S: ["U", "C"],
|
|
178
|
+
C: [
|
|
179
|
+
"N",
|
|
180
|
+
"L",
|
|
181
|
+
"H"
|
|
182
|
+
],
|
|
183
|
+
I: [
|
|
184
|
+
"N",
|
|
185
|
+
"L",
|
|
186
|
+
"H"
|
|
187
|
+
],
|
|
188
|
+
A: [
|
|
189
|
+
"N",
|
|
190
|
+
"L",
|
|
191
|
+
"H"
|
|
192
|
+
],
|
|
193
|
+
E: [
|
|
194
|
+
"X",
|
|
195
|
+
"U",
|
|
196
|
+
"P",
|
|
197
|
+
"F",
|
|
198
|
+
"H"
|
|
199
|
+
],
|
|
200
|
+
RL: [
|
|
201
|
+
"X",
|
|
202
|
+
"O",
|
|
203
|
+
"T",
|
|
204
|
+
"W",
|
|
205
|
+
"U"
|
|
206
|
+
],
|
|
207
|
+
RC: [
|
|
208
|
+
"X",
|
|
209
|
+
"U",
|
|
210
|
+
"R",
|
|
211
|
+
"C"
|
|
212
|
+
],
|
|
213
|
+
CR: [
|
|
214
|
+
"X",
|
|
215
|
+
"L",
|
|
216
|
+
"M",
|
|
217
|
+
"H"
|
|
218
|
+
],
|
|
219
|
+
IR: [
|
|
220
|
+
"X",
|
|
221
|
+
"L",
|
|
222
|
+
"M",
|
|
223
|
+
"H"
|
|
224
|
+
],
|
|
225
|
+
AR: [
|
|
226
|
+
"X",
|
|
227
|
+
"L",
|
|
228
|
+
"M",
|
|
229
|
+
"H"
|
|
230
|
+
],
|
|
231
|
+
MAV: [
|
|
232
|
+
"X",
|
|
233
|
+
"N",
|
|
234
|
+
"A",
|
|
235
|
+
"L",
|
|
236
|
+
"P"
|
|
237
|
+
],
|
|
238
|
+
MAC: [
|
|
239
|
+
"X",
|
|
240
|
+
"L",
|
|
241
|
+
"H"
|
|
242
|
+
],
|
|
243
|
+
MPR: [
|
|
244
|
+
"X",
|
|
245
|
+
"N",
|
|
246
|
+
"L",
|
|
247
|
+
"H"
|
|
248
|
+
],
|
|
249
|
+
MUI: [
|
|
250
|
+
"X",
|
|
251
|
+
"N",
|
|
252
|
+
"R"
|
|
253
|
+
],
|
|
254
|
+
MS: [
|
|
255
|
+
"X",
|
|
256
|
+
"U",
|
|
257
|
+
"C"
|
|
258
|
+
],
|
|
259
|
+
MC: [
|
|
260
|
+
"X",
|
|
261
|
+
"N",
|
|
262
|
+
"L",
|
|
263
|
+
"H"
|
|
264
|
+
],
|
|
265
|
+
MI: [
|
|
266
|
+
"X",
|
|
267
|
+
"N",
|
|
268
|
+
"L",
|
|
269
|
+
"H"
|
|
270
|
+
],
|
|
271
|
+
MA: [
|
|
272
|
+
"X",
|
|
273
|
+
"N",
|
|
274
|
+
"L",
|
|
275
|
+
"H"
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
"3.1": {
|
|
279
|
+
AV: [
|
|
280
|
+
"N",
|
|
281
|
+
"A",
|
|
282
|
+
"L",
|
|
283
|
+
"P"
|
|
284
|
+
],
|
|
285
|
+
AC: ["L", "H"],
|
|
286
|
+
PR: [
|
|
287
|
+
"N",
|
|
288
|
+
"L",
|
|
289
|
+
"H"
|
|
290
|
+
],
|
|
291
|
+
UI: ["N", "R"],
|
|
292
|
+
S: ["U", "C"],
|
|
293
|
+
C: [
|
|
294
|
+
"N",
|
|
295
|
+
"L",
|
|
296
|
+
"H"
|
|
297
|
+
],
|
|
298
|
+
I: [
|
|
299
|
+
"N",
|
|
300
|
+
"L",
|
|
301
|
+
"H"
|
|
302
|
+
],
|
|
303
|
+
A: [
|
|
304
|
+
"N",
|
|
305
|
+
"L",
|
|
306
|
+
"H"
|
|
307
|
+
],
|
|
308
|
+
E: [
|
|
309
|
+
"X",
|
|
310
|
+
"U",
|
|
311
|
+
"P",
|
|
312
|
+
"F",
|
|
313
|
+
"H"
|
|
314
|
+
],
|
|
315
|
+
RL: [
|
|
316
|
+
"X",
|
|
317
|
+
"O",
|
|
318
|
+
"T",
|
|
319
|
+
"W",
|
|
320
|
+
"U"
|
|
321
|
+
],
|
|
322
|
+
RC: [
|
|
323
|
+
"X",
|
|
324
|
+
"U",
|
|
325
|
+
"R",
|
|
326
|
+
"C"
|
|
327
|
+
],
|
|
328
|
+
CR: [
|
|
329
|
+
"X",
|
|
330
|
+
"L",
|
|
331
|
+
"M",
|
|
332
|
+
"H"
|
|
333
|
+
],
|
|
334
|
+
IR: [
|
|
335
|
+
"X",
|
|
336
|
+
"L",
|
|
337
|
+
"M",
|
|
338
|
+
"H"
|
|
339
|
+
],
|
|
340
|
+
AR: [
|
|
341
|
+
"X",
|
|
342
|
+
"L",
|
|
343
|
+
"M",
|
|
344
|
+
"H"
|
|
345
|
+
],
|
|
346
|
+
MAV: [
|
|
347
|
+
"X",
|
|
348
|
+
"N",
|
|
349
|
+
"A",
|
|
350
|
+
"L",
|
|
351
|
+
"P"
|
|
352
|
+
],
|
|
353
|
+
MAC: [
|
|
354
|
+
"X",
|
|
355
|
+
"L",
|
|
356
|
+
"H"
|
|
357
|
+
],
|
|
358
|
+
MPR: [
|
|
359
|
+
"X",
|
|
360
|
+
"N",
|
|
361
|
+
"L",
|
|
362
|
+
"H"
|
|
363
|
+
],
|
|
364
|
+
MUI: [
|
|
365
|
+
"X",
|
|
366
|
+
"N",
|
|
367
|
+
"R"
|
|
368
|
+
],
|
|
369
|
+
MS: [
|
|
370
|
+
"X",
|
|
371
|
+
"U",
|
|
372
|
+
"C"
|
|
373
|
+
],
|
|
374
|
+
MC: [
|
|
375
|
+
"X",
|
|
376
|
+
"N",
|
|
377
|
+
"L",
|
|
378
|
+
"H"
|
|
379
|
+
],
|
|
380
|
+
MI: [
|
|
381
|
+
"X",
|
|
382
|
+
"N",
|
|
383
|
+
"L",
|
|
384
|
+
"H"
|
|
385
|
+
],
|
|
386
|
+
MA: [
|
|
387
|
+
"X",
|
|
388
|
+
"N",
|
|
389
|
+
"L",
|
|
390
|
+
"H"
|
|
391
|
+
]
|
|
392
|
+
},
|
|
393
|
+
"4.0": {
|
|
394
|
+
AV: [
|
|
395
|
+
"N",
|
|
396
|
+
"A",
|
|
397
|
+
"L",
|
|
398
|
+
"P"
|
|
399
|
+
],
|
|
400
|
+
AC: ["L", "H"],
|
|
401
|
+
AT: ["N", "P"],
|
|
402
|
+
PR: [
|
|
403
|
+
"N",
|
|
404
|
+
"L",
|
|
405
|
+
"H"
|
|
406
|
+
],
|
|
407
|
+
UI: [
|
|
408
|
+
"N",
|
|
409
|
+
"P",
|
|
410
|
+
"A"
|
|
411
|
+
],
|
|
412
|
+
VC: [
|
|
413
|
+
"H",
|
|
414
|
+
"L",
|
|
415
|
+
"N"
|
|
416
|
+
],
|
|
417
|
+
VI: [
|
|
418
|
+
"H",
|
|
419
|
+
"L",
|
|
420
|
+
"N"
|
|
421
|
+
],
|
|
422
|
+
VA: [
|
|
423
|
+
"H",
|
|
424
|
+
"L",
|
|
425
|
+
"N"
|
|
426
|
+
],
|
|
427
|
+
SC: [
|
|
428
|
+
"H",
|
|
429
|
+
"L",
|
|
430
|
+
"N"
|
|
431
|
+
],
|
|
432
|
+
SI: [
|
|
433
|
+
"H",
|
|
434
|
+
"L",
|
|
435
|
+
"N"
|
|
436
|
+
],
|
|
437
|
+
SA: [
|
|
438
|
+
"H",
|
|
439
|
+
"L",
|
|
440
|
+
"N"
|
|
441
|
+
],
|
|
442
|
+
E: [
|
|
443
|
+
"X",
|
|
444
|
+
"A",
|
|
445
|
+
"P",
|
|
446
|
+
"U"
|
|
447
|
+
],
|
|
448
|
+
MAV: [
|
|
449
|
+
"X",
|
|
450
|
+
"N",
|
|
451
|
+
"A",
|
|
452
|
+
"L",
|
|
453
|
+
"P"
|
|
454
|
+
],
|
|
455
|
+
MAC: [
|
|
456
|
+
"X",
|
|
457
|
+
"L",
|
|
458
|
+
"H"
|
|
459
|
+
],
|
|
460
|
+
MAT: [
|
|
461
|
+
"X",
|
|
462
|
+
"N",
|
|
463
|
+
"P"
|
|
464
|
+
],
|
|
465
|
+
MPR: [
|
|
466
|
+
"X",
|
|
467
|
+
"N",
|
|
468
|
+
"L",
|
|
469
|
+
"H"
|
|
470
|
+
],
|
|
471
|
+
MUI: [
|
|
472
|
+
"X",
|
|
473
|
+
"N",
|
|
474
|
+
"P",
|
|
475
|
+
"A"
|
|
476
|
+
],
|
|
477
|
+
MVC: [
|
|
478
|
+
"X",
|
|
479
|
+
"H",
|
|
480
|
+
"L",
|
|
481
|
+
"N"
|
|
482
|
+
],
|
|
483
|
+
MVI: [
|
|
484
|
+
"X",
|
|
485
|
+
"H",
|
|
486
|
+
"L",
|
|
487
|
+
"N"
|
|
488
|
+
],
|
|
489
|
+
MVA: [
|
|
490
|
+
"X",
|
|
491
|
+
"H",
|
|
492
|
+
"L",
|
|
493
|
+
"N"
|
|
494
|
+
],
|
|
495
|
+
MSC: [
|
|
496
|
+
"X",
|
|
497
|
+
"H",
|
|
498
|
+
"L",
|
|
499
|
+
"N"
|
|
500
|
+
],
|
|
501
|
+
MSI: [
|
|
502
|
+
"X",
|
|
503
|
+
"S",
|
|
504
|
+
"H",
|
|
505
|
+
"L",
|
|
506
|
+
"N"
|
|
507
|
+
],
|
|
508
|
+
MSA: [
|
|
509
|
+
"X",
|
|
510
|
+
"S",
|
|
511
|
+
"H",
|
|
512
|
+
"L",
|
|
513
|
+
"N"
|
|
514
|
+
],
|
|
515
|
+
CR: [
|
|
516
|
+
"X",
|
|
517
|
+
"H",
|
|
518
|
+
"M",
|
|
519
|
+
"L"
|
|
520
|
+
],
|
|
521
|
+
IR: [
|
|
522
|
+
"X",
|
|
523
|
+
"H",
|
|
524
|
+
"M",
|
|
525
|
+
"L"
|
|
526
|
+
],
|
|
527
|
+
AR: [
|
|
528
|
+
"X",
|
|
529
|
+
"H",
|
|
530
|
+
"M",
|
|
531
|
+
"L"
|
|
532
|
+
],
|
|
533
|
+
S: [
|
|
534
|
+
"X",
|
|
535
|
+
"N",
|
|
536
|
+
"P"
|
|
537
|
+
],
|
|
538
|
+
AU: [
|
|
539
|
+
"X",
|
|
540
|
+
"N",
|
|
541
|
+
"Y"
|
|
542
|
+
],
|
|
543
|
+
R: [
|
|
544
|
+
"X",
|
|
545
|
+
"A",
|
|
546
|
+
"U",
|
|
547
|
+
"I"
|
|
548
|
+
],
|
|
549
|
+
V: [
|
|
550
|
+
"X",
|
|
551
|
+
"D",
|
|
552
|
+
"C"
|
|
553
|
+
],
|
|
554
|
+
RE: [
|
|
555
|
+
"X",
|
|
556
|
+
"L",
|
|
557
|
+
"M",
|
|
558
|
+
"H"
|
|
559
|
+
],
|
|
560
|
+
U: [
|
|
561
|
+
"X",
|
|
562
|
+
"Clear",
|
|
563
|
+
"Green",
|
|
564
|
+
"Amber",
|
|
565
|
+
"Red"
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
const REQUIRED_METRICS = {
|
|
570
|
+
"2.0": [
|
|
571
|
+
"AV",
|
|
572
|
+
"AC",
|
|
573
|
+
"Au",
|
|
574
|
+
"C",
|
|
575
|
+
"I",
|
|
576
|
+
"A"
|
|
577
|
+
],
|
|
578
|
+
"3.0": [
|
|
579
|
+
"AV",
|
|
580
|
+
"AC",
|
|
581
|
+
"PR",
|
|
582
|
+
"UI",
|
|
583
|
+
"S",
|
|
584
|
+
"C",
|
|
585
|
+
"I",
|
|
586
|
+
"A"
|
|
587
|
+
],
|
|
588
|
+
"3.1": [
|
|
589
|
+
"AV",
|
|
590
|
+
"AC",
|
|
591
|
+
"PR",
|
|
592
|
+
"UI",
|
|
593
|
+
"S",
|
|
594
|
+
"C",
|
|
595
|
+
"I",
|
|
596
|
+
"A"
|
|
597
|
+
],
|
|
598
|
+
"4.0": [
|
|
599
|
+
"AV",
|
|
600
|
+
"AC",
|
|
601
|
+
"AT",
|
|
602
|
+
"PR",
|
|
603
|
+
"UI",
|
|
604
|
+
"VC",
|
|
605
|
+
"VI",
|
|
606
|
+
"VA",
|
|
607
|
+
"SC",
|
|
608
|
+
"SI",
|
|
609
|
+
"SA"
|
|
610
|
+
]
|
|
611
|
+
};
|
|
612
|
+
/**
|
|
613
|
+
* Parse a CVSS vector string into version + metric map.
|
|
614
|
+
*
|
|
615
|
+
* Version detection rule: a leading `CVSS:X.Y` segment yields version "X.Y".
|
|
616
|
+
* Otherwise (legacy v2 vectors), version is "2.0". Malformed input yields
|
|
617
|
+
* "unknown" with an empty metric map; no exceptions are thrown.
|
|
618
|
+
*
|
|
619
|
+
* @param vector - CVSS vector string (e.g. "CVSS:3.1/AV:N/AC:L/...")
|
|
620
|
+
* @returns Parsed version + metric map
|
|
621
|
+
*/
|
|
622
|
+
function parseCvssVector(vector) {
|
|
623
|
+
const metrics = /* @__PURE__ */ new Map();
|
|
624
|
+
if (typeof vector !== "string" || vector.length === 0) return {
|
|
625
|
+
version: "unknown",
|
|
626
|
+
metrics
|
|
627
|
+
};
|
|
628
|
+
const segments = vector.split("/").filter((s) => s.length > 0);
|
|
629
|
+
if (segments.length === 0) return {
|
|
630
|
+
version: "unknown",
|
|
631
|
+
metrics
|
|
632
|
+
};
|
|
633
|
+
let version = "2.0";
|
|
634
|
+
let start = 0;
|
|
635
|
+
const first = segments[0];
|
|
636
|
+
if (first === void 0) return {
|
|
637
|
+
version: "unknown",
|
|
638
|
+
metrics
|
|
639
|
+
};
|
|
640
|
+
if (first.startsWith("CVSS:")) {
|
|
641
|
+
version = first.slice(5);
|
|
642
|
+
start = 1;
|
|
643
|
+
} else if (!first.includes(":")) return {
|
|
644
|
+
version: "unknown",
|
|
645
|
+
metrics
|
|
646
|
+
};
|
|
647
|
+
for (let i = start; i < segments.length; i++) {
|
|
648
|
+
const seg = segments[i];
|
|
649
|
+
if (seg === void 0) continue;
|
|
650
|
+
const colon = seg.indexOf(":");
|
|
651
|
+
if (colon <= 0 || colon === seg.length - 1) continue;
|
|
652
|
+
const key = seg.slice(0, colon);
|
|
653
|
+
const value = seg.slice(colon + 1);
|
|
654
|
+
metrics.set(key, value);
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
version,
|
|
658
|
+
metrics
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Validate a CVSS vector string against the FIRST grammar for its version.
|
|
663
|
+
*
|
|
664
|
+
* Checks:
|
|
665
|
+
* - Version is one of the supported set (2.0, 3.0, 3.1, 4.0).
|
|
666
|
+
* - All required base metrics for the version are present.
|
|
667
|
+
* - All known metric values are within the allowed enum.
|
|
668
|
+
*
|
|
669
|
+
* Unknown metric keys are tolerated (forward-compat with future minor versions
|
|
670
|
+
* or scanner-specific extensions). To check a v2 vector without the CVSS:
|
|
671
|
+
* prefix, callers may pass version="2.0" explicitly; otherwise the parser
|
|
672
|
+
* defaults to "2.0" on prefix absence.
|
|
673
|
+
*
|
|
674
|
+
* @param vector - CVSS vector string
|
|
675
|
+
* @param version - Optional explicit version override (else inferred)
|
|
676
|
+
* @returns `{ valid, errors[] }` — never throws
|
|
677
|
+
*/
|
|
678
|
+
function validateCvssVector(vector, version) {
|
|
679
|
+
const errors = [];
|
|
680
|
+
if (typeof vector !== "string" || vector.length === 0) return {
|
|
681
|
+
valid: false,
|
|
682
|
+
errors: ["vector is empty"]
|
|
683
|
+
};
|
|
684
|
+
const parsed = parseCvssVector(vector);
|
|
685
|
+
const v = version ?? parsed.version;
|
|
686
|
+
const grammar = METRIC_VALUES[v];
|
|
687
|
+
const required = REQUIRED_METRICS[v];
|
|
688
|
+
if (!grammar || !required) {
|
|
689
|
+
errors.push(`unsupported CVSS version: ${v}`);
|
|
690
|
+
return {
|
|
691
|
+
valid: false,
|
|
692
|
+
errors
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
for (const req of required) if (!parsed.metrics.has(req)) errors.push(`missing required metric: ${req}`);
|
|
696
|
+
for (const [key, value] of parsed.metrics.entries()) {
|
|
697
|
+
const allowed = grammar[key];
|
|
698
|
+
if (!allowed) continue;
|
|
699
|
+
if (!allowed.includes(value)) errors.push(`invalid value for metric ${key}: ${value} (allowed: ${allowed.join(",")})`);
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
valid: errors.length === 0,
|
|
703
|
+
errors
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region src/cpe/index.ts
|
|
708
|
+
const CPE_PREFIX = "cpe:2.3:";
|
|
709
|
+
const EXPECTED_TOTAL_FIELDS = 13;
|
|
710
|
+
const PRODUCT_FIELD_COUNT = 11;
|
|
711
|
+
const VALID_PARTS = new Set([
|
|
712
|
+
"a",
|
|
713
|
+
"o",
|
|
714
|
+
"h",
|
|
715
|
+
"*"
|
|
716
|
+
]);
|
|
717
|
+
/**
|
|
718
|
+
* Split a CPE 2.3 body on unescaped `:` separators.
|
|
719
|
+
*
|
|
720
|
+
* Per CPE 2.3 spec section 6.1.2.4, `:` and `\` inside a field must be
|
|
721
|
+
* escaped as `\:` and `\\`. We respect those escapes during the split and
|
|
722
|
+
* unescape the result.
|
|
723
|
+
*/
|
|
724
|
+
function splitOnUnescapedColons(body) {
|
|
725
|
+
const result = [];
|
|
726
|
+
let current = "";
|
|
727
|
+
for (let i = 0; i < body.length; i++) {
|
|
728
|
+
const ch = body[i];
|
|
729
|
+
if (ch === "\\" && i + 1 < body.length) {
|
|
730
|
+
const next = body[i + 1];
|
|
731
|
+
if (next === ":" || next === "\\") {
|
|
732
|
+
current += next;
|
|
733
|
+
i++;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
current += ch;
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (ch === ":") {
|
|
740
|
+
result.push(current);
|
|
741
|
+
current = "";
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
current += ch;
|
|
745
|
+
}
|
|
746
|
+
result.push(current);
|
|
747
|
+
return result;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Parse a CPE 2.3 URI.
|
|
751
|
+
*
|
|
752
|
+
* Accept-and-warn semantics:
|
|
753
|
+
* - Input missing the `cpe:2.3:` prefix → returns `null`.
|
|
754
|
+
* - Input with the prefix but fewer than 11 product fields → fields are
|
|
755
|
+
* padded with `"*"` and a `truncated:` warning is added.
|
|
756
|
+
* - Input with more than 11 product fields → extras are dropped and an
|
|
757
|
+
* `"extra fields ignored"` warning is added.
|
|
758
|
+
* - Unknown `part` value → kept as-is in the result, `"unknown part: X"`
|
|
759
|
+
* warning is added.
|
|
760
|
+
* - `\:` and `\\` escapes inside fields are honored during splitting and
|
|
761
|
+
* unescaped in the returned field values.
|
|
762
|
+
*
|
|
763
|
+
* @param cpeUri - CPE 2.3 URI string
|
|
764
|
+
* @returns Parsed CPE with warnings, or `null` if input lacks the prefix
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* ```typescript
|
|
768
|
+
* parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k:*:*:*:*:*:*:*');
|
|
769
|
+
* // { part: 'a', vendor: 'openssl', product: 'openssl', version: '1.1.1k', ... warnings: [] }
|
|
770
|
+
*
|
|
771
|
+
* parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k');
|
|
772
|
+
* // truncated input → padded with '*', warnings: ['truncated: expected 13 colon-separated fields, got 6']
|
|
773
|
+
*
|
|
774
|
+
* parseCpe('openssl:1.1.1k'); // null — no prefix
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
function parseCpe(cpeUri) {
|
|
778
|
+
if (!cpeUri.startsWith(CPE_PREFIX)) return null;
|
|
779
|
+
const warnings = [];
|
|
780
|
+
const body = cpeUri.slice(8);
|
|
781
|
+
const isBarePrefix = body.length === 0;
|
|
782
|
+
const bodyParts = isBarePrefix ? [] : splitOnUnescapedColons(body);
|
|
783
|
+
const totalFields = isBarePrefix ? 2 : 2 + bodyParts.length;
|
|
784
|
+
if (bodyParts.length > PRODUCT_FIELD_COUNT) warnings.push("extra fields ignored");
|
|
785
|
+
else if (bodyParts.length < PRODUCT_FIELD_COUNT) warnings.push(`truncated: expected ${EXPECTED_TOTAL_FIELDS} colon-separated fields, got ${totalFields}`);
|
|
786
|
+
const padValue = isBarePrefix ? "" : "*";
|
|
787
|
+
const fields = bodyParts.slice(0, PRODUCT_FIELD_COUNT);
|
|
788
|
+
while (fields.length < PRODUCT_FIELD_COUNT) fields.push(padValue);
|
|
789
|
+
const [part = "", vendor = "", product = "", version = "", update = "", edition = "", language = "", swEdition = "", targetSw = "", targetHw = "", other = ""] = fields;
|
|
790
|
+
if (!VALID_PARTS.has(part)) warnings.push(`unknown part: ${part}`);
|
|
791
|
+
return {
|
|
792
|
+
raw: cpeUri,
|
|
793
|
+
part,
|
|
794
|
+
vendor,
|
|
795
|
+
product,
|
|
796
|
+
version,
|
|
797
|
+
update,
|
|
798
|
+
edition,
|
|
799
|
+
language,
|
|
800
|
+
swEdition,
|
|
801
|
+
targetSw,
|
|
802
|
+
targetHw,
|
|
803
|
+
other,
|
|
804
|
+
warnings
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
//#endregion
|
|
808
|
+
//#region src/purl/index.ts
|
|
809
|
+
const PURL_PREFIX = "pkg:";
|
|
810
|
+
/**
|
|
811
|
+
* Parse a Package URL string.
|
|
812
|
+
*
|
|
813
|
+
* Returns `null` only when the input is missing the mandatory `pkg:` prefix
|
|
814
|
+
* or the type segment. Other deviations are surfaced via `warnings` on the
|
|
815
|
+
* returned object.
|
|
816
|
+
*
|
|
817
|
+
* @param purlStr - PURL string, e.g. `pkg:npm/lodash@4.17.21`
|
|
818
|
+
* @returns Parsed PURL or `null` if the input is unrecoverably malformed
|
|
819
|
+
*
|
|
820
|
+
* @example
|
|
821
|
+
* ```typescript
|
|
822
|
+
* const r = parsePurl('pkg:rpm/redhat/openssl@1.1.1k-7.el8_4?arch=x86_64');
|
|
823
|
+
* r?.type; // "rpm"
|
|
824
|
+
* r?.namespace; // "redhat"
|
|
825
|
+
* r?.name; // "openssl"
|
|
826
|
+
* r?.qualifiers.get('arch'); // "x86_64"
|
|
827
|
+
* ```
|
|
828
|
+
*/
|
|
829
|
+
function parsePurl(purlStr) {
|
|
830
|
+
if (!purlStr || !purlStr.startsWith(PURL_PREFIX)) return null;
|
|
831
|
+
const raw = purlStr;
|
|
832
|
+
const warnings = [];
|
|
833
|
+
let remainder = purlStr.slice(4);
|
|
834
|
+
while (remainder.startsWith("/")) remainder = remainder.slice(1);
|
|
835
|
+
if (remainder.length === 0) return null;
|
|
836
|
+
let subpath = null;
|
|
837
|
+
const hashIdx = remainder.indexOf("#");
|
|
838
|
+
if (hashIdx !== -1) {
|
|
839
|
+
subpath = remainder.slice(hashIdx + 1);
|
|
840
|
+
remainder = remainder.slice(0, hashIdx);
|
|
841
|
+
if (subpath.length === 0) subpath = null;
|
|
842
|
+
}
|
|
843
|
+
const qualifiers = /* @__PURE__ */ new Map();
|
|
844
|
+
const qIdx = remainder.indexOf("?");
|
|
845
|
+
if (qIdx !== -1) {
|
|
846
|
+
const qStr = remainder.slice(qIdx + 1);
|
|
847
|
+
remainder = remainder.slice(0, qIdx);
|
|
848
|
+
parseQualifiers(qStr, qualifiers, warnings);
|
|
849
|
+
}
|
|
850
|
+
while (remainder.endsWith("/")) remainder = remainder.slice(0, -1);
|
|
851
|
+
const firstSlash = remainder.indexOf("/");
|
|
852
|
+
let type;
|
|
853
|
+
let pathPart;
|
|
854
|
+
if (firstSlash === -1) {
|
|
855
|
+
type = remainder;
|
|
856
|
+
pathPart = "";
|
|
857
|
+
} else {
|
|
858
|
+
type = remainder.slice(0, firstSlash);
|
|
859
|
+
pathPart = remainder.slice(firstSlash + 1);
|
|
860
|
+
}
|
|
861
|
+
if (type.length === 0) return null;
|
|
862
|
+
type = type.toLowerCase();
|
|
863
|
+
let version = null;
|
|
864
|
+
const lastAt = pathPart.lastIndexOf("@");
|
|
865
|
+
let nameAndNs = pathPart;
|
|
866
|
+
if (lastAt !== -1) {
|
|
867
|
+
version = pathPart.slice(lastAt + 1);
|
|
868
|
+
nameAndNs = pathPart.slice(0, lastAt);
|
|
869
|
+
version = safeDecode(version);
|
|
870
|
+
}
|
|
871
|
+
let namespace = null;
|
|
872
|
+
let name;
|
|
873
|
+
const lastSlash = nameAndNs.lastIndexOf("/");
|
|
874
|
+
if (lastSlash === -1) name = nameAndNs;
|
|
875
|
+
else {
|
|
876
|
+
namespace = nameAndNs.slice(0, lastSlash);
|
|
877
|
+
name = nameAndNs.slice(lastSlash + 1);
|
|
878
|
+
}
|
|
879
|
+
if (namespace !== null) namespace = namespace.split("/").map(safeDecode).join("/");
|
|
880
|
+
name = safeDecode(name);
|
|
881
|
+
if (name.length === 0) warnings.push("PURL is missing the name segment");
|
|
882
|
+
if (namespace !== null && namespace.length === 0) namespace = null;
|
|
883
|
+
if (version !== null && version.length === 0) version = null;
|
|
884
|
+
return {
|
|
885
|
+
raw,
|
|
886
|
+
type,
|
|
887
|
+
namespace,
|
|
888
|
+
name,
|
|
889
|
+
version,
|
|
890
|
+
qualifiers,
|
|
891
|
+
subpath,
|
|
892
|
+
warnings
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Parse the `?key=value&key=value` qualifier string. Empty segments are
|
|
897
|
+
* skipped silently; segments with no `=` are recorded with empty value and
|
|
898
|
+
* a warning. Values are URL-decoded.
|
|
899
|
+
*/
|
|
900
|
+
function parseQualifiers(qStr, out, warnings) {
|
|
901
|
+
const parts = qStr.split("&");
|
|
902
|
+
for (const part of parts) {
|
|
903
|
+
if (part.length === 0) continue;
|
|
904
|
+
const eq = part.indexOf("=");
|
|
905
|
+
if (eq === -1) {
|
|
906
|
+
out.set(part, "");
|
|
907
|
+
warnings.push(`qualifier "${part}" has no value`);
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const key = part.slice(0, eq);
|
|
911
|
+
const value = part.slice(eq + 1);
|
|
912
|
+
out.set(key, safeDecode(value));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* decodeURIComponent that returns the original string on failure rather than
|
|
917
|
+
* throwing. PURLs in the wild occasionally contain stray `%` characters that
|
|
918
|
+
* aren't legitimate percent-encoding.
|
|
919
|
+
*/
|
|
920
|
+
function safeDecode(s) {
|
|
921
|
+
try {
|
|
922
|
+
return decodeURIComponent(s);
|
|
923
|
+
} catch {
|
|
924
|
+
return s;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
44
927
|
//#endregion
|
|
45
|
-
export { buildCsv, buildCsvArray, buildXml, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, generateHash, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, parseCsv, parseCsvArray, parseJSON, parseTimestamp, parseXml, parseXmlWithArrays, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, stringifyJSON, stripHtml, verifyHash };
|
|
928
|
+
export { buildCsv, buildCsvArray, buildXml, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, generateHash, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, stringifyJSON, stripHtml, validateCvssVector, verifyHash };
|
|
46
929
|
|
|
47
930
|
//# sourceMappingURL=index.js.map
|