@mitre/hdf-converters 3.3.0 → 3.3.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/detect.d.ts.map +1 -1
- package/dist/detect.js +1 -1
- package/dist/index.d.ts +2 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +375 -265
- package/dist/index.js.map +1 -1
- package/dist/{register-all-C3lYICDC.js → register-all-DxZN6IJ4.js} +22 -2
- package/dist/register-all-DxZN6IJ4.js.map +1 -0
- package/package.json +7 -7
- package/dist/register-all-C3lYICDC.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { n as detectConverter, t as registerAllFingerprints } from "./register-all-
|
|
1
|
+
import { n as detectConverter, t as registerAllFingerprints } from "./register-all-DxZN6IJ4.js";
|
|
2
2
|
import { flattenOverlays } from "@mitre/hdf-parsers";
|
|
3
|
-
import { buildCsv, buildXml, cvssScoreToSeverity, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, sha256, stripHtml as stripHTML } from "@mitre/hdf-utilities";
|
|
3
|
+
import { buildCsv, buildXml, cvssScoreToSeverity, formatTimestamp, formatTimestampSeconds, impactToSeverity, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, sha256, stripHtml as stripHTML, trimUtcFraction } from "@mitre/hdf-utilities";
|
|
4
4
|
import { Applicability, AuthorizationStatus, CVSSSeverity, CategorizationLevel, ControlType, Ecosystem, EvidenceType, HashAlgorithm, IdentityType, Justification, MilestoneStatus, OverrideType, PlanType, ResultStatus, TargetType, VerificationMethodEnum, Version, createDescription, createMinimalBaseline, createRequirement, createResult, severityToImpact } from "@mitre/hdf-schema";
|
|
5
|
-
import { DEFAULT_COMPONENT_MANAGEMENT_NIST_TAGS, DEFAULT_REMEDIATION_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS as DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getCCINistMappings, getCweNistControl, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, nistToCci } from "@mitre/hdf-mappings";
|
|
5
|
+
import { DEFAULT_COMPONENT_MANAGEMENT_NIST_TAGS, DEFAULT_REMEDIATION_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS as DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1, awsConfigMappedRevisions, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getCCINistMappings, getCurrentNistRevision, getCweNistControl, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistToCci } from "@mitre/hdf-mappings";
|
|
6
6
|
//#region shared/typescript/converterutil.ts
|
|
7
7
|
/**
|
|
8
8
|
* Shared utilities for TypeScript HDF converters.
|
|
@@ -249,7 +249,21 @@ function buildHdfResults(opts) {
|
|
|
249
249
|
if (opts.components) hdf.components = opts.components;
|
|
250
250
|
if (opts.timestamp) hdf.timestamp = opts.timestamp;
|
|
251
251
|
if (opts.statistics) hdf.statistics = opts.statistics;
|
|
252
|
-
return
|
|
252
|
+
return serializeHdf(hdf);
|
|
253
|
+
}
|
|
254
|
+
const ISO_UTC_WITH_FRACTION = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$/;
|
|
255
|
+
function hdfTimestampReplacer(_key, value) {
|
|
256
|
+
if (typeof value === "string" && ISO_UTC_WITH_FRACTION.test(value)) return trimUtcFraction(value);
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Serialize an HDF document to pretty-printed JSON, normalizing Date-derived
|
|
261
|
+
* timestamps to canonical trimmed-UTC RFC3339 (byte-identical to the Go
|
|
262
|
+
* converters). Use this instead of a bare `JSON.stringify` for any HDF output
|
|
263
|
+
* so the fractional-second trim is applied consistently.
|
|
264
|
+
*/
|
|
265
|
+
function serializeHdf(doc, space = 2) {
|
|
266
|
+
return JSON.stringify(doc, hdfTimestampReplacer, space);
|
|
253
267
|
}
|
|
254
268
|
/**
|
|
255
269
|
* Captures a NIST 800-53 control identifier and its base sub-control number,
|
|
@@ -413,14 +427,13 @@ function buildNoFindingsRequirement(id, codeDesc, startTime) {
|
|
|
413
427
|
* - Results: snake_case → camelCase for all fields
|
|
414
428
|
* - Overlay flattening: merge overlay/wrapper baselines so every requirement has results
|
|
415
429
|
*/
|
|
416
|
-
/** Valid severity values per HDF v2.0 schema. */
|
|
417
|
-
const VALID_SEVERITIES = new Set([
|
|
430
|
+
/** Valid severity values per HDF v2.0 schema (matches the Go converter). */
|
|
431
|
+
const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
418
432
|
"critical",
|
|
419
433
|
"high",
|
|
420
434
|
"medium",
|
|
421
435
|
"low",
|
|
422
|
-
"informational"
|
|
423
|
-
"none"
|
|
436
|
+
"informational"
|
|
424
437
|
]);
|
|
425
438
|
/**
|
|
426
439
|
* Convert tags.severity string to a valid severity value.
|
|
@@ -432,17 +445,6 @@ function tagSeverityToSeverity(tagSeverity) {
|
|
|
432
445
|
return VALID_SEVERITIES.has(normalized) ? normalized : null;
|
|
433
446
|
}
|
|
434
447
|
/**
|
|
435
|
-
* Derive severity from numeric impact score.
|
|
436
|
-
* Follows InSpec conventions: 0.9+ critical, 0.7+ high, 0.5+ medium, >0 low, 0 none.
|
|
437
|
-
*/
|
|
438
|
-
function impactToSeverity$2(impact) {
|
|
439
|
-
if (impact >= .9) return "critical";
|
|
440
|
-
if (impact >= .7) return "high";
|
|
441
|
-
if (impact >= .5) return "medium";
|
|
442
|
-
if (impact > 0) return "low";
|
|
443
|
-
return "none";
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
448
|
* Normalize status values from v1.0 to v2.0 format.
|
|
447
449
|
* Converts snake_case to camelCase.
|
|
448
450
|
*/
|
|
@@ -491,32 +493,22 @@ function computeEffectiveStatus(impact, results) {
|
|
|
491
493
|
* Convert v1.0 result to v2.0 result.
|
|
492
494
|
* Transforms snake_case field names to camelCase.
|
|
493
495
|
*/
|
|
496
|
+
const LEGACY_ZERO_TIME = "0001-01-01T00:00:00Z";
|
|
497
|
+
function legacyStartTime(s) {
|
|
498
|
+
if (s === void 0) return LEGACY_ZERO_TIME;
|
|
499
|
+
const d = parseTimestamp(s);
|
|
500
|
+
return d ? formatTimestamp(d) : LEGACY_ZERO_TIME;
|
|
501
|
+
}
|
|
494
502
|
function convertResult(v1Result) {
|
|
495
503
|
const v2Result = { status: normalizeStatus$1(v1Result.status) };
|
|
496
|
-
|
|
504
|
+
v2Result.codeDesc = v1Result.code_desc ?? "";
|
|
497
505
|
if (v1Result.run_time !== void 0) v2Result.runTime = v1Result.run_time;
|
|
498
|
-
|
|
506
|
+
v2Result.startTime = legacyStartTime(v1Result.start_time);
|
|
499
507
|
if (v1Result.message !== void 0) v2Result.message = v1Result.message;
|
|
500
508
|
if (v1Result.exception !== void 0) v2Result.exception = v1Result.exception;
|
|
501
509
|
if (v1Result.backtrace !== void 0) v2Result.backtrace = v1Result.backtrace;
|
|
502
|
-
if (v1Result.resource_class !== void 0) v2Result.
|
|
503
|
-
if (v1Result.resource_params !== void 0) v2Result.resourceParams = v1Result.resource_params;
|
|
510
|
+
if (v1Result.resource_class !== void 0) v2Result.resource = v1Result.resource_class;
|
|
504
511
|
if (v1Result.resource_id !== void 0) v2Result.resourceId = v1Result.resource_id;
|
|
505
|
-
if (v1Result.skip_message !== void 0) v2Result.skipMessage = v1Result.skip_message;
|
|
506
|
-
const knownFields = new Set([
|
|
507
|
-
"status",
|
|
508
|
-
"code_desc",
|
|
509
|
-
"run_time",
|
|
510
|
-
"start_time",
|
|
511
|
-
"message",
|
|
512
|
-
"exception",
|
|
513
|
-
"backtrace",
|
|
514
|
-
"resource_class",
|
|
515
|
-
"resource_params",
|
|
516
|
-
"resource_id",
|
|
517
|
-
"skip_message"
|
|
518
|
-
]);
|
|
519
|
-
for (const [key, value] of Object.entries(v1Result)) if (!knownFields.has(key) && !(key in v2Result)) v2Result[key] = value;
|
|
520
512
|
return v2Result;
|
|
521
513
|
}
|
|
522
514
|
/**
|
|
@@ -529,37 +521,19 @@ function convertControl(v1Control) {
|
|
|
529
521
|
impact: v1Control.impact
|
|
530
522
|
};
|
|
531
523
|
if (v1Control.title !== void 0) v2Req.title = v1Control.title;
|
|
532
|
-
if (v1Control.desc !== void 0) v2Req.desc = v1Control.desc;
|
|
533
524
|
if (v1Control.descriptions !== void 0) v2Req.descriptions = v1Control.descriptions;
|
|
534
|
-
if (v1Control.refs !== void 0) v2Req.refs = v1Control.refs;
|
|
535
525
|
if (v1Control.tags !== void 0) v2Req.tags = v1Control.tags;
|
|
536
526
|
if (v1Control.code !== void 0) v2Req.code = v1Control.code;
|
|
537
527
|
if (v1Control.source_location !== void 0) v2Req.sourceLocation = v1Control.source_location;
|
|
538
|
-
if (v1Control.waiver_data !== void 0) v2Req.waiverData = v1Control.waiver_data;
|
|
539
528
|
if (v1Control.status !== void 0) v2Req.effectiveStatus = normalizeStatus$1(v1Control.status);
|
|
540
529
|
if (v1Control.results && Array.isArray(v1Control.results)) v2Req.results = v1Control.results.map(convertResult);
|
|
541
530
|
if (!v2Req.effectiveStatus) v2Req.effectiveStatus = computeEffectiveStatus(v1Control.impact, v2Req.results ?? []);
|
|
542
|
-
v2Req.severity = tagSeverityToSeverity(v1Control.tags?.severity) ?? impactToSeverity
|
|
531
|
+
v2Req.severity = tagSeverityToSeverity(v1Control.tags?.severity) ?? impactToSeverity(v1Control.impact);
|
|
543
532
|
const rawNist = v1Control.tags?.nist;
|
|
544
533
|
if (Array.isArray(rawNist)) {
|
|
545
534
|
const controlType = deriveControlTypeFromTags(rawNist.filter((v) => typeof v === "string"));
|
|
546
535
|
if (controlType !== void 0) v2Req.controlType = controlType;
|
|
547
536
|
}
|
|
548
|
-
const knownFields = new Set([
|
|
549
|
-
"id",
|
|
550
|
-
"title",
|
|
551
|
-
"desc",
|
|
552
|
-
"descriptions",
|
|
553
|
-
"impact",
|
|
554
|
-
"refs",
|
|
555
|
-
"tags",
|
|
556
|
-
"code",
|
|
557
|
-
"source_location",
|
|
558
|
-
"waiver_data",
|
|
559
|
-
"results",
|
|
560
|
-
"status"
|
|
561
|
-
]);
|
|
562
|
-
for (const [key, value] of Object.entries(v1Control)) if (!knownFields.has(key) && !(key in v2Req)) v2Req[key] = value;
|
|
563
537
|
return v2Req;
|
|
564
538
|
}
|
|
565
539
|
/**
|
|
@@ -572,7 +546,7 @@ function convertGroup(v1Group) {
|
|
|
572
546
|
requirements: v1Group.controls
|
|
573
547
|
};
|
|
574
548
|
if (v1Group.title !== void 0) v2Group.title = v1Group.title;
|
|
575
|
-
const knownFields = new Set([
|
|
549
|
+
const knownFields = /* @__PURE__ */ new Set([
|
|
576
550
|
"id",
|
|
577
551
|
"title",
|
|
578
552
|
"controls"
|
|
@@ -598,7 +572,7 @@ function convertDependency(v1Dep) {
|
|
|
598
572
|
if (v1Dep.compliance !== void 0) v2Dep.compliance = v1Dep.compliance;
|
|
599
573
|
if (v1Dep.status !== void 0) v2Dep.status = v1Dep.status;
|
|
600
574
|
if (v1Dep.skip_message !== void 0) v2Dep.skipMessage = v1Dep.skip_message;
|
|
601
|
-
const knownFields = new Set([
|
|
575
|
+
const knownFields = /* @__PURE__ */ new Set([
|
|
602
576
|
"name",
|
|
603
577
|
"url",
|
|
604
578
|
"path",
|
|
@@ -627,9 +601,9 @@ function convertProfile(v1Profile) {
|
|
|
627
601
|
if (v1Profile.summary !== void 0) v2Baseline.summary = v1Profile.summary;
|
|
628
602
|
if (v1Profile.license !== void 0) v2Baseline.license = v1Profile.license;
|
|
629
603
|
if (v1Profile.copyright !== void 0) v2Baseline.copyright = v1Profile.copyright;
|
|
630
|
-
if (v1Profile.copyright_email !== void 0) v2Baseline.
|
|
631
|
-
if (v1Profile.supports
|
|
632
|
-
if (v1Profile.attributes
|
|
604
|
+
if (v1Profile.copyright_email !== void 0) v2Baseline.copyrightEmail = v1Profile.copyright_email;
|
|
605
|
+
if (v1Profile.supports?.length) v2Baseline.supports = v1Profile.supports;
|
|
606
|
+
if (v1Profile.attributes?.length) v2Baseline.inputs = v1Profile.attributes;
|
|
633
607
|
if (v1Profile.status !== void 0) v2Baseline.status = v1Profile.status;
|
|
634
608
|
if (v1Profile.sha256) v2Baseline.integrity = {
|
|
635
609
|
algorithm: "sha256",
|
|
@@ -638,30 +612,9 @@ function convertProfile(v1Profile) {
|
|
|
638
612
|
if (v1Profile.parent_profile !== void 0) v2Baseline.parentBaseline = v1Profile.parent_profile;
|
|
639
613
|
if (v1Profile.status_message !== void 0) v2Baseline.statusMessage = v1Profile.status_message;
|
|
640
614
|
if (v1Profile.skip_message !== void 0) v2Baseline.skipMessage = v1Profile.skip_message;
|
|
641
|
-
if (v1Profile.groups
|
|
615
|
+
if (v1Profile.groups?.length) v2Baseline.groups = v1Profile.groups.map(convertGroup);
|
|
642
616
|
if (v1Profile.controls && Array.isArray(v1Profile.controls)) v2Baseline.requirements = v1Profile.controls.map(convertControl);
|
|
643
|
-
if (v1Profile.depends
|
|
644
|
-
const knownFields = new Set([
|
|
645
|
-
"name",
|
|
646
|
-
"version",
|
|
647
|
-
"title",
|
|
648
|
-
"maintainer",
|
|
649
|
-
"summary",
|
|
650
|
-
"license",
|
|
651
|
-
"copyright",
|
|
652
|
-
"copyright_email",
|
|
653
|
-
"supports",
|
|
654
|
-
"attributes",
|
|
655
|
-
"groups",
|
|
656
|
-
"controls",
|
|
657
|
-
"sha256",
|
|
658
|
-
"depends",
|
|
659
|
-
"parent_profile",
|
|
660
|
-
"status",
|
|
661
|
-
"status_message",
|
|
662
|
-
"skip_message"
|
|
663
|
-
]);
|
|
664
|
-
for (const [key, value] of Object.entries(v1Profile)) if (!knownFields.has(key) && !(key in v2Baseline)) v2Baseline[key] = value;
|
|
617
|
+
if (v1Profile.depends?.length) v2Baseline.depends = v1Profile.depends.map(convertDependency);
|
|
665
618
|
return v2Baseline;
|
|
666
619
|
}
|
|
667
620
|
/**
|
|
@@ -694,17 +647,21 @@ function convertV1ToV2(v1Data) {
|
|
|
694
647
|
baselines: (v1Data.profiles || []).map(convertProfile),
|
|
695
648
|
statistics: v1Data.statistics || {}
|
|
696
649
|
};
|
|
697
|
-
if (v1Data.platform)
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
650
|
+
if (v1Data.platform) {
|
|
651
|
+
const component = {
|
|
652
|
+
type: "host",
|
|
653
|
+
name: v1Data.platform.name
|
|
654
|
+
};
|
|
655
|
+
if (v1Data.platform.target_id) {
|
|
656
|
+
component.osName = v1Data.platform.name;
|
|
657
|
+
if (v1Data.platform.release !== void 0) component.osVersion = v1Data.platform.release;
|
|
658
|
+
}
|
|
659
|
+
v2.components = [component];
|
|
660
|
+
}
|
|
704
661
|
if (v1Data.generator) v2.generator = v1Data.generator;
|
|
705
662
|
v2.tool = { name: "Heimdall Data Format v1" };
|
|
706
663
|
if (v1Data.timestamp) v2.timestamp = v1Data.timestamp;
|
|
707
|
-
const knownV1Fields = new Set([
|
|
664
|
+
const knownV1Fields = /* @__PURE__ */ new Set([
|
|
708
665
|
"version",
|
|
709
666
|
"platform",
|
|
710
667
|
"profiles",
|
|
@@ -1069,8 +1026,9 @@ async function convertJunitToHdf(input) {
|
|
|
1069
1026
|
if (!input || !input.trim()) throw new Error("Empty input");
|
|
1070
1027
|
validateInputSize(input, "junit");
|
|
1071
1028
|
const { suites, name } = parseJUnitXML(input);
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1029
|
+
const scanTime = resolveScanTime(suites);
|
|
1030
|
+
const requirements = buildRequirements(suites, scanTime);
|
|
1031
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("junit-no-findings", `JUnit scanned ${noFindingsTarget(name, suites)} and reported zero findings.`, scanTime));
|
|
1074
1032
|
return buildHdfResults({
|
|
1075
1033
|
generatorName: "junit-to-hdf",
|
|
1076
1034
|
converterVersion: CONVERTER_VERSION$2,
|
|
@@ -1081,9 +1039,16 @@ async function convertJunitToHdf(input) {
|
|
|
1081
1039
|
type: TargetType.Application,
|
|
1082
1040
|
name
|
|
1083
1041
|
}],
|
|
1084
|
-
timestamp:
|
|
1042
|
+
timestamp: scanTime
|
|
1085
1043
|
});
|
|
1086
1044
|
}
|
|
1045
|
+
function resolveScanTime(suites) {
|
|
1046
|
+
for (const suite of suites) if (suite.timestamp) {
|
|
1047
|
+
const parsed = parseTimestamp(suite.timestamp);
|
|
1048
|
+
if (parsed) return parsed;
|
|
1049
|
+
}
|
|
1050
|
+
return /* @__PURE__ */ new Date();
|
|
1051
|
+
}
|
|
1087
1052
|
function parseJUnitXML(input) {
|
|
1088
1053
|
const parsed = parseXmlWithArrays(input, ARRAY_TAGS$2);
|
|
1089
1054
|
if (parsed.testsuites) return {
|
|
@@ -1100,7 +1065,7 @@ function parseJUnitXML(input) {
|
|
|
1100
1065
|
}
|
|
1101
1066
|
throw new Error("Input is not a JUnit XML document: expected <testsuites> or <testsuite> root element");
|
|
1102
1067
|
}
|
|
1103
|
-
function buildRequirements(suites) {
|
|
1068
|
+
function buildRequirements(suites, scanTime) {
|
|
1104
1069
|
const { items: limitedSuites, truncated: truncatedSuites } = limitArray(suites);
|
|
1105
1070
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
1106
1071
|
if (truncatedSuites) console.warn(`WARNING: Input truncated at ${limitedSuites.length} test suite items (original: ${suites.length})`);
|
|
@@ -1110,19 +1075,21 @@ function buildRequirements(suites) {
|
|
|
1110
1075
|
const { items: limitedTestCases, truncated: truncatedTC } = limitArray(testcases);
|
|
1111
1076
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
1112
1077
|
if (truncatedTC) console.warn(`WARNING: Input truncated at ${limitedTestCases.length} test case items (original: ${testcases.length})`);
|
|
1113
|
-
for (const tc of limitedTestCases) requirements.push(testCaseToRequirement(tc,
|
|
1078
|
+
for (const tc of limitedTestCases) requirements.push(testCaseToRequirement(tc, scanTime));
|
|
1114
1079
|
}
|
|
1115
1080
|
return requirements;
|
|
1116
1081
|
}
|
|
1117
|
-
function testCaseToRequirement(tc,
|
|
1082
|
+
function testCaseToRequirement(tc, scanTime) {
|
|
1118
1083
|
const id = buildID(tc);
|
|
1119
1084
|
const { status, message } = resolveStatus(tc);
|
|
1120
|
-
const resultOptions = {
|
|
1085
|
+
const resultOptions = {
|
|
1086
|
+
codeDesc: buildCodeDesc$9(tc),
|
|
1087
|
+
startTime: scanTime
|
|
1088
|
+
};
|
|
1121
1089
|
if (tc.time) {
|
|
1122
1090
|
const parsed = parseFloat(tc.time);
|
|
1123
1091
|
if (!isNaN(parsed)) resultOptions.runTime = parsed;
|
|
1124
1092
|
}
|
|
1125
|
-
if (suiteTimestamp) resultOptions.startTime = suiteTimestamp;
|
|
1126
1093
|
const result = createResult(status, message ?? "", resultOptions);
|
|
1127
1094
|
const descriptions = [{
|
|
1128
1095
|
label: "default",
|
|
@@ -1227,6 +1194,13 @@ const STATUS_MAP = {
|
|
|
1227
1194
|
* @param input - Raw XML string (XCCDF Benchmark with TestResult, or ARF asset-report-collection)
|
|
1228
1195
|
* @returns Stringified HDF Results JSON
|
|
1229
1196
|
*/
|
|
1197
|
+
function parseStartTime(raw) {
|
|
1198
|
+
if (raw) {
|
|
1199
|
+
const t = parseTimestamp(raw);
|
|
1200
|
+
if (t) return t;
|
|
1201
|
+
}
|
|
1202
|
+
return /* @__PURE__ */ new Date();
|
|
1203
|
+
}
|
|
1230
1204
|
async function convertXccdfResultsToHdf(input) {
|
|
1231
1205
|
if (!input || !input.trim()) throw new Error("Empty input");
|
|
1232
1206
|
validateInputSize(input, "xccdf-results");
|
|
@@ -1245,20 +1219,21 @@ async function convertBenchmarkResultsToHdf(benchmark, rawInput) {
|
|
|
1245
1219
|
const { items: limitedRuleResults, truncated: truncatedRR } = limitArray(ruleResults);
|
|
1246
1220
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
1247
1221
|
if (truncatedRR) console.warn(`WARNING: Input truncated at ${limitedRuleResults.length} rule-result items (original: ${ruleResults.length})`);
|
|
1248
|
-
const
|
|
1222
|
+
const scanTime = parseStartTime(testResult["start-time"]);
|
|
1223
|
+
const requirements = limitedRuleResults.map((rr) => ruleResultToRequirement(rr, ruleIndex, scanTime));
|
|
1249
1224
|
if (requirements.length === 0) {
|
|
1250
1225
|
const target = xccdfTargetName(testResult, benchmark);
|
|
1251
|
-
requirements.push(buildNoFindingsRequirement("xccdf-results-no-findings", `XCCDF scanned ${target} and reported zero findings.`,
|
|
1226
|
+
requirements.push(buildNoFindingsRequirement("xccdf-results-no-findings", `XCCDF scanned ${target} and reported zero findings.`, scanTime));
|
|
1252
1227
|
}
|
|
1253
1228
|
const resultsChecksum = await inputChecksum(rawInput);
|
|
1254
1229
|
const baseline = createMinimalBaseline(extractText(benchmark.title) || "XCCDF Benchmark", requirements, { resultsChecksum });
|
|
1255
1230
|
const components = buildTargets(testResult);
|
|
1256
|
-
const timestamp =
|
|
1231
|
+
const timestamp = scanTime;
|
|
1257
1232
|
let durationSeconds;
|
|
1258
1233
|
if (testResult["start-time"] && testResult["end-time"]) {
|
|
1259
|
-
const start =
|
|
1260
|
-
const end =
|
|
1261
|
-
if (
|
|
1234
|
+
const start = parseTimestamp(testResult["start-time"])?.getTime();
|
|
1235
|
+
const end = parseTimestamp(testResult["end-time"])?.getTime();
|
|
1236
|
+
if (start !== void 0 && end !== void 0 && end >= start) durationSeconds = (end - start) / 1e3;
|
|
1262
1237
|
}
|
|
1263
1238
|
const hdf = {
|
|
1264
1239
|
baselines: [baseline],
|
|
@@ -1274,7 +1249,7 @@ async function convertBenchmarkResultsToHdf(benchmark, rawInput) {
|
|
|
1274
1249
|
timestamp
|
|
1275
1250
|
};
|
|
1276
1251
|
if (durationSeconds !== void 0) hdf.statistics = { duration: durationSeconds };
|
|
1277
|
-
return
|
|
1252
|
+
return serializeHdf(hdf);
|
|
1278
1253
|
}
|
|
1279
1254
|
async function convertArfCollection(arc, rawInput) {
|
|
1280
1255
|
const resultsChecksum = await inputChecksum(rawInput);
|
|
@@ -1291,20 +1266,24 @@ async function convertArfCollection(arc, rawInput) {
|
|
|
1291
1266
|
for (const report of arc.reports?.report ?? []) {
|
|
1292
1267
|
const testResult = report.content?.TestResult;
|
|
1293
1268
|
if (!testResult?.id) continue;
|
|
1294
|
-
if (testResult["start-time"] && !firstTimestamp)
|
|
1269
|
+
if (testResult["start-time"] && !firstTimestamp) {
|
|
1270
|
+
const t = parseTimestamp(testResult["start-time"]);
|
|
1271
|
+
if (t) firstTimestamp = t;
|
|
1272
|
+
}
|
|
1295
1273
|
if (testResult["start-time"] && testResult["end-time"]) {
|
|
1296
|
-
const start =
|
|
1297
|
-
const end =
|
|
1298
|
-
if (
|
|
1274
|
+
const start = parseTimestamp(testResult["start-time"])?.getTime();
|
|
1275
|
+
const end = parseTimestamp(testResult["end-time"])?.getTime();
|
|
1276
|
+
if (start !== void 0 && end !== void 0 && end >= start) totalDuration += (end - start) / 1e3;
|
|
1299
1277
|
}
|
|
1278
|
+
const scanTime = parseStartTime(testResult["start-time"]);
|
|
1300
1279
|
const ruleResults = testResult["rule-result"] ?? [];
|
|
1301
1280
|
const { items: limitedARFRuleResults, truncated: truncatedARFRR } = limitArray(ruleResults);
|
|
1302
1281
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
1303
1282
|
if (truncatedARFRR) console.warn(`WARNING: Input truncated at ${limitedARFRuleResults.length} rule-result items (original: ${ruleResults.length})`);
|
|
1304
|
-
const requirements = limitedARFRuleResults.map((rr) => ruleResultToRequirement(rr, ruleIndex));
|
|
1283
|
+
const requirements = limitedARFRuleResults.map((rr) => ruleResultToRequirement(rr, ruleIndex, scanTime));
|
|
1305
1284
|
if (requirements.length === 0) {
|
|
1306
1285
|
const target = xccdfTargetName(testResult, benchmark);
|
|
1307
|
-
requirements.push(buildNoFindingsRequirement("xccdf-results-no-findings", `XCCDF scanned ${target} and reported zero findings.`,
|
|
1286
|
+
requirements.push(buildNoFindingsRequirement("xccdf-results-no-findings", `XCCDF scanned ${target} and reported zero findings.`, scanTime));
|
|
1308
1287
|
}
|
|
1309
1288
|
let baselineName = "";
|
|
1310
1289
|
if (benchmark) baselineName = extractText(benchmark.title) || "";
|
|
@@ -1337,7 +1316,7 @@ async function convertArfCollection(arc, rawInput) {
|
|
|
1337
1316
|
timestamp: firstTimestamp ?? /* @__PURE__ */ new Date()
|
|
1338
1317
|
};
|
|
1339
1318
|
if (totalDuration > 0) hdf.statistics = { duration: totalDuration };
|
|
1340
|
-
return
|
|
1319
|
+
return serializeHdf(hdf);
|
|
1341
1320
|
}
|
|
1342
1321
|
/**
|
|
1343
1322
|
* Find the XCCDF Benchmark embedded in an ARF data-stream-collection.
|
|
@@ -1393,7 +1372,7 @@ function buildRuleIndex(benchmark) {
|
|
|
1393
1372
|
/**
|
|
1394
1373
|
* Convert a single <rule-result> into an EvaluatedRequirement.
|
|
1395
1374
|
*/
|
|
1396
|
-
function ruleResultToRequirement(rr, ruleIndex) {
|
|
1375
|
+
function ruleResultToRequirement(rr, ruleIndex, scanTime) {
|
|
1397
1376
|
const ruleId = rr.idref ?? "";
|
|
1398
1377
|
const ruleDef = ruleIndex.get(ruleId);
|
|
1399
1378
|
const id = extractVersion$1(ruleDef?.version) || ruleId;
|
|
@@ -1411,7 +1390,16 @@ function ruleResultToRequirement(rr, ruleIndex) {
|
|
|
1411
1390
|
label: "fix",
|
|
1412
1391
|
data: fixtext
|
|
1413
1392
|
});
|
|
1414
|
-
|
|
1393
|
+
if (descriptions.length === 0) descriptions.push({
|
|
1394
|
+
label: "default",
|
|
1395
|
+
data: ""
|
|
1396
|
+
});
|
|
1397
|
+
const status = STATUS_MAP[(rr.result ?? "").toLowerCase()] ?? ResultStatus.NotReviewed;
|
|
1398
|
+
const perRuleTime = rr.time ? parseTimestamp(rr.time) : null;
|
|
1399
|
+
const result = createResult(status, "", {
|
|
1400
|
+
codeDesc: `XCCDF rule ${id}`,
|
|
1401
|
+
startTime: perRuleTime ?? scanTime
|
|
1402
|
+
});
|
|
1415
1403
|
const tags = {};
|
|
1416
1404
|
let nistTags = [];
|
|
1417
1405
|
const cciIds = extractCCIs(rr.ident ?? ruleDef?.ident ?? []);
|
|
@@ -1589,7 +1577,7 @@ const VULN_ATTR_ORDER = [
|
|
|
1589
1577
|
"Class",
|
|
1590
1578
|
"STIG_UUID"
|
|
1591
1579
|
];
|
|
1592
|
-
const CORE_VULN_ATTR = new Set([
|
|
1580
|
+
const CORE_VULN_ATTR = /* @__PURE__ */ new Set([
|
|
1593
1581
|
"Vuln_Num",
|
|
1594
1582
|
"Severity",
|
|
1595
1583
|
"Group_Title",
|
|
@@ -1625,7 +1613,9 @@ function parseCkl(input) {
|
|
|
1625
1613
|
webDBSite: str(a["WEB_DB_SITE"]),
|
|
1626
1614
|
webDBInstance: str(a["WEB_DB_INSTANCE"])
|
|
1627
1615
|
},
|
|
1628
|
-
stigs: istigs.map((is) => {
|
|
1616
|
+
stigs: istigs.map((is, i) => {
|
|
1617
|
+
const vulns = is.VULN ?? [];
|
|
1618
|
+
if (vulns.length === 0) throw new Error(`parse ckl: <iSTIG> block ${i + 1} contains no <VULN> rules`);
|
|
1629
1619
|
const si = is.STIG_INFO?.SI_DATA ?? [];
|
|
1630
1620
|
const siVal = (name) => str(si.find((d) => d.SID_NAME === name)?.SID_DATA);
|
|
1631
1621
|
return {
|
|
@@ -1635,7 +1625,7 @@ function parseCkl(input) {
|
|
|
1635
1625
|
releaseInfo: siVal("releaseinfo"),
|
|
1636
1626
|
uuid: siVal("uuid"),
|
|
1637
1627
|
classification: siVal("classification"),
|
|
1638
|
-
vulns:
|
|
1628
|
+
vulns: vulns.map(cklVulnToModel)
|
|
1639
1629
|
};
|
|
1640
1630
|
})
|
|
1641
1631
|
};
|
|
@@ -1645,7 +1635,7 @@ function cklVulnToModel(v) {
|
|
|
1645
1635
|
const attr = (name) => str(data.find((sd) => sd.VULN_ATTRIBUTE === name)?.ATTRIBUTE_DATA);
|
|
1646
1636
|
const ccis = [];
|
|
1647
1637
|
const extra = {};
|
|
1648
|
-
const promoted = new Set([
|
|
1638
|
+
const promoted = /* @__PURE__ */ new Set([
|
|
1649
1639
|
"CCI_REF",
|
|
1650
1640
|
"Vuln_Num",
|
|
1651
1641
|
"Severity",
|
|
@@ -1794,16 +1784,20 @@ function parseCklb(input) {
|
|
|
1794
1784
|
techArea: nz(t.technology_area),
|
|
1795
1785
|
classification: nz(t.classification)
|
|
1796
1786
|
};
|
|
1797
|
-
const stigs = doc.stigs.map((s) =>
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1787
|
+
const stigs = doc.stigs.map((s, i) => {
|
|
1788
|
+
const rules = s.rules ?? [];
|
|
1789
|
+
if (rules.length === 0) throw new Error(`parse cklb: stigs[${i}] contains no rules[]`);
|
|
1790
|
+
return {
|
|
1791
|
+
stigID: nz(s.stig_id),
|
|
1792
|
+
title: nz(s.stig_name) || nz(s.display_name),
|
|
1793
|
+
displayName: nz(s.display_name),
|
|
1794
|
+
version: nz(s.version),
|
|
1795
|
+
releaseInfo: nz(s.release_info),
|
|
1796
|
+
uuid: nz(s.uuid),
|
|
1797
|
+
referenceIdentifier: nz(s.reference_identifier),
|
|
1798
|
+
vulns: rules.map(cklbRuleToModel)
|
|
1799
|
+
};
|
|
1800
|
+
});
|
|
1807
1801
|
return {
|
|
1808
1802
|
format: "cklb",
|
|
1809
1803
|
cklbVersion: nz(doc.cklb_version),
|
|
@@ -1906,8 +1900,9 @@ const CONVERTER_VERSION = "1.0.0";
|
|
|
1906
1900
|
* Original-format metadata is stashed in extensions/tags for round-trip.
|
|
1907
1901
|
*/
|
|
1908
1902
|
function checklistToHdf(cl, resultsChecksum, generatorName) {
|
|
1903
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
1909
1904
|
const hdf = {
|
|
1910
|
-
baselines: cl.stigs.map((s) => stigToBaseline(s, resultsChecksum)),
|
|
1905
|
+
baselines: cl.stigs.map((s) => stigToBaseline(s, resultsChecksum, scanTime)),
|
|
1911
1906
|
generator: {
|
|
1912
1907
|
name: generatorName,
|
|
1913
1908
|
version: CONVERTER_VERSION
|
|
@@ -1916,7 +1911,7 @@ function checklistToHdf(cl, resultsChecksum, generatorName) {
|
|
|
1916
1911
|
name: "DISA STIG Viewer",
|
|
1917
1912
|
format: cl.format === "cklb" ? "CKLB" : "CKL"
|
|
1918
1913
|
},
|
|
1919
|
-
timestamp:
|
|
1914
|
+
timestamp: scanTime
|
|
1920
1915
|
};
|
|
1921
1916
|
const component = assetToComponent(cl.asset);
|
|
1922
1917
|
if (component) hdf.components = [component];
|
|
@@ -1924,15 +1919,15 @@ function checklistToHdf(cl, resultsChecksum, generatorName) {
|
|
|
1924
1919
|
if (Object.keys(ext).length > 0) hdf.extensions = ext;
|
|
1925
1920
|
return hdf;
|
|
1926
1921
|
}
|
|
1927
|
-
function stigToBaseline(s, resultsChecksum) {
|
|
1928
|
-
const baseline = createMinimalBaseline("STIG Checklist Scan", s.vulns.map(vulnToRequirement), { resultsChecksum });
|
|
1922
|
+
function stigToBaseline(s, resultsChecksum, scanTime) {
|
|
1923
|
+
const baseline = createMinimalBaseline("STIG Checklist Scan", s.vulns.map((v) => vulnToRequirement(v, scanTime)), { resultsChecksum });
|
|
1929
1924
|
if (s.title) baseline.title = s.title;
|
|
1930
1925
|
if (s.version) baseline.version = s.version;
|
|
1931
1926
|
const ext = baselineExtensions(s);
|
|
1932
1927
|
if (Object.keys(ext).length > 0) baseline.extensions = ext;
|
|
1933
1928
|
return baseline;
|
|
1934
1929
|
}
|
|
1935
|
-
function vulnToRequirement(v) {
|
|
1930
|
+
function vulnToRequirement(v, scanTime) {
|
|
1936
1931
|
const severity = (v.severity ?? "").toLowerCase();
|
|
1937
1932
|
const impact = severity ? severityToImpact(severity) : .5;
|
|
1938
1933
|
const descriptions = [{
|
|
@@ -1948,7 +1943,10 @@ function vulnToRequirement(v) {
|
|
|
1948
1943
|
data: stripHTML(v.fixText)
|
|
1949
1944
|
});
|
|
1950
1945
|
const message = [v.findingDetails, v.comments].map((s) => (s ?? "").trim()).filter(Boolean).join("\n\n");
|
|
1951
|
-
const result = createResult(statusToHdf(v.status), message, {
|
|
1946
|
+
const result = createResult(statusToHdf(v.status), message, {
|
|
1947
|
+
codeDesc: `STIG rule ${v.ruleVer ?? ""}`,
|
|
1948
|
+
startTime: scanTime
|
|
1949
|
+
});
|
|
1952
1950
|
const tags = {};
|
|
1953
1951
|
let nistTags = [];
|
|
1954
1952
|
if (v.ccis.length > 0) {
|
|
@@ -2238,7 +2236,7 @@ function synthesizePurl(ecosystem, name, version) {
|
|
|
2238
2236
|
if (ecosystem === Ecosystem.Generic) return void 0;
|
|
2239
2237
|
return `pkg:${ecosystem}/${name}@${version}`;
|
|
2240
2238
|
}
|
|
2241
|
-
function buildRequirement$16(vulnID, vulns, packageManager) {
|
|
2239
|
+
function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
2242
2240
|
const rep = vulns[0];
|
|
2243
2241
|
const cweIDs = rep.identifiers.CWE ?? [];
|
|
2244
2242
|
const nist = mapCWEToNIST(cweIDs, DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
@@ -2253,7 +2251,10 @@ function buildRequirement$16(vulnID, vulns, packageManager) {
|
|
|
2253
2251
|
label: "default",
|
|
2254
2252
|
data: rep.description
|
|
2255
2253
|
}];
|
|
2256
|
-
const results = vulns.map((vuln) => createResult(ResultStatus.Failed, void 0, {
|
|
2254
|
+
const results = vulns.map((vuln) => createResult(ResultStatus.Failed, void 0, {
|
|
2255
|
+
codeDesc: formatDependencyPath(vuln.from),
|
|
2256
|
+
startTime: scanTime
|
|
2257
|
+
}));
|
|
2257
2258
|
const req = createRequirement(vulnID, rep.title, descriptions, severityToImpact(rep.severity), results, { tags });
|
|
2258
2259
|
const controlType = deriveControlTypeFromTags(nist);
|
|
2259
2260
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -2276,7 +2277,7 @@ function buildRequirement$16(vulnID, vulns, packageManager) {
|
|
|
2276
2277
|
/**
|
|
2277
2278
|
* Converts a single Snyk project report to an HDF baseline.
|
|
2278
2279
|
*/
|
|
2279
|
-
function convertSingleProject(report, resultsChecksum) {
|
|
2280
|
+
function convertSingleProject(report, resultsChecksum, scanTime) {
|
|
2280
2281
|
const { items: limitedVulns, truncated: truncatedVulns } = limitArray(report.vulnerabilities);
|
|
2281
2282
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
2282
2283
|
if (truncatedVulns) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${report.vulnerabilities.length})`);
|
|
@@ -2287,10 +2288,10 @@ function convertSingleProject(report, resultsChecksum) {
|
|
|
2287
2288
|
else groups.set(vuln.id, [vuln]);
|
|
2288
2289
|
}
|
|
2289
2290
|
const requirements = [];
|
|
2290
|
-
for (const [vulnID, vulns] of groups) requirements.push(buildRequirement$16(vulnID, vulns, report.packageManager));
|
|
2291
|
+
for (const [vulnID, vulns] of groups) requirements.push(buildRequirement$16(vulnID, vulns, scanTime, report.packageManager));
|
|
2291
2292
|
if (requirements.length === 0) {
|
|
2292
2293
|
const target = report.projectName ?? report.path ?? "project";
|
|
2293
|
-
requirements.push(buildNoFindingsRequirement("snyk-no-findings", `Snyk scanned ${target} and reported zero vulnerable components.`,
|
|
2294
|
+
requirements.push(buildNoFindingsRequirement("snyk-no-findings", `Snyk scanned ${target} and reported zero vulnerable components.`, scanTime));
|
|
2294
2295
|
}
|
|
2295
2296
|
return createMinimalBaseline("Snyk Scan", requirements, {
|
|
2296
2297
|
resultsChecksum,
|
|
@@ -2314,6 +2315,7 @@ async function convertSnykToHdf(input) {
|
|
|
2314
2315
|
const detected = detectConverter(input);
|
|
2315
2316
|
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input);
|
|
2316
2317
|
const resultsChecksum = await inputChecksum(input);
|
|
2318
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
2317
2319
|
const parsed = parseJSON(input);
|
|
2318
2320
|
if (!parsed || typeof parsed !== "object") throw new Error("snyk: invalid JSON");
|
|
2319
2321
|
let baselines;
|
|
@@ -2322,10 +2324,10 @@ async function convertSnykToHdf(input) {
|
|
|
2322
2324
|
const { items: limitedProjects, truncated: truncatedProjects } = limitArray(parsed);
|
|
2323
2325
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
2324
2326
|
if (truncatedProjects) console.warn(`WARNING: Input truncated at ${limitedProjects.length} project items (original: ${parsed.length})`);
|
|
2325
|
-
baselines = limitedProjects.map((report) => convertSingleProject(report, resultsChecksum));
|
|
2327
|
+
baselines = limitedProjects.map((report) => convertSingleProject(report, resultsChecksum, scanTime));
|
|
2326
2328
|
targetName = limitedProjects[0]?.projectName ?? limitedProjects[0]?.path ?? "";
|
|
2327
2329
|
} else {
|
|
2328
|
-
baselines = [convertSingleProject(parsed, resultsChecksum)];
|
|
2330
|
+
baselines = [convertSingleProject(parsed, resultsChecksum, scanTime)];
|
|
2329
2331
|
targetName = parsed.projectName ?? parsed.path ?? "";
|
|
2330
2332
|
}
|
|
2331
2333
|
return buildHdfResults({
|
|
@@ -2338,12 +2340,12 @@ async function convertSnykToHdf(input) {
|
|
|
2338
2340
|
name: targetName,
|
|
2339
2341
|
type: TargetType.Application
|
|
2340
2342
|
}],
|
|
2341
|
-
timestamp:
|
|
2343
|
+
timestamp: scanTime
|
|
2342
2344
|
});
|
|
2343
2345
|
}
|
|
2344
2346
|
//#endregion
|
|
2345
2347
|
//#region converters/grype-to-hdf/typescript/converter.ts
|
|
2346
|
-
const IMPACT_MAPPING$8 = new Map([
|
|
2348
|
+
const IMPACT_MAPPING$8 = /* @__PURE__ */ new Map([
|
|
2347
2349
|
["critical", .9],
|
|
2348
2350
|
["high", .7],
|
|
2349
2351
|
["medium", .5],
|
|
@@ -2581,7 +2583,7 @@ async function convertGrypeToHdf(input) {
|
|
|
2581
2583
|
type: TargetType.Artifact,
|
|
2582
2584
|
name: targetName
|
|
2583
2585
|
}],
|
|
2584
|
-
timestamp: grypeData.descriptor?.timestamp ?
|
|
2586
|
+
timestamp: (grypeData.descriptor?.timestamp ? parseTimestamp(grypeData.descriptor.timestamp) : null) ?? /* @__PURE__ */ new Date()
|
|
2585
2587
|
});
|
|
2586
2588
|
}
|
|
2587
2589
|
//#endregion
|
|
@@ -2664,8 +2666,8 @@ function calculateTiming(hosts) {
|
|
|
2664
2666
|
const lastHost = hosts[hosts.length - 1];
|
|
2665
2667
|
const startTimeStr = getHostPropertyValue(firstHost, "HOST_START");
|
|
2666
2668
|
const endTimeStr = getHostPropertyValue(lastHost, "HOST_END") || getHostPropertyValue(lastHost, "HOST_START");
|
|
2667
|
-
const startTime = startTimeStr ? new Date(
|
|
2668
|
-
const endTime = endTimeStr ?
|
|
2669
|
+
const startTime = startTimeStr ? parseTimestamp(startTimeStr) ?? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date();
|
|
2670
|
+
const endTime = endTimeStr ? parseTimestamp(endTimeStr) ?? startTime : startTime;
|
|
2669
2671
|
return {
|
|
2670
2672
|
startTime,
|
|
2671
2673
|
endTime,
|
|
@@ -2689,7 +2691,7 @@ function convertReportHostToBaseline(host, policyName, version, resultsChecksum)
|
|
|
2689
2691
|
if (requirements.length === 0) {
|
|
2690
2692
|
const target = host.name || getHostPropertyValue(host, "host-ip") || "host";
|
|
2691
2693
|
const startTimeStr = getHostPropertyValue(host, "HOST_START");
|
|
2692
|
-
const startTime = startTimeStr ? new Date(
|
|
2694
|
+
const startTime = startTimeStr ? parseTimestamp(startTimeStr) ?? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date();
|
|
2693
2695
|
requirements = [buildNoFindingsRequirement("nessus-no-findings", `Nessus scanned ${target} and reported zero findings.`, startTime)];
|
|
2694
2696
|
}
|
|
2695
2697
|
return createMinimalBaseline(`Nessus ${policyName}`, requirements, {
|
|
@@ -2856,8 +2858,8 @@ function buildEpss(item, host) {
|
|
|
2856
2858
|
function epssDate(host) {
|
|
2857
2859
|
const hs = getHostPropertyValue(host, "HOST_START");
|
|
2858
2860
|
if (hs) {
|
|
2859
|
-
const d =
|
|
2860
|
-
if (
|
|
2861
|
+
const d = parseTimestamp(hs);
|
|
2862
|
+
if (d) return d.toISOString().slice(0, 10);
|
|
2861
2863
|
}
|
|
2862
2864
|
}
|
|
2863
2865
|
function buildDescriptions(item, isCompliance) {
|
|
@@ -2928,7 +2930,7 @@ function buildResult$1(item, host, isCompliance) {
|
|
|
2928
2930
|
const codeDesc = getCodeDesc(item);
|
|
2929
2931
|
const message = item.plugin_output || item["compliance-actual-value"];
|
|
2930
2932
|
const startTimeStr = getHostPropertyValue(host, "HOST_START");
|
|
2931
|
-
const startTime = startTimeStr ? new Date(
|
|
2933
|
+
const startTime = startTimeStr ? parseTimestamp(startTimeStr) ?? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date();
|
|
2932
2934
|
return {
|
|
2933
2935
|
status,
|
|
2934
2936
|
codeDesc,
|
|
@@ -3163,7 +3165,7 @@ function createResultFromIssue(issue, componentMap) {
|
|
|
3163
3165
|
const codeDesc = `${component?.path || component?.longName || issue.component}${issue.line ? ` LINE : ${issue.line}` : ""}`;
|
|
3164
3166
|
return createResult(status, issue.message, {
|
|
3165
3167
|
codeDesc,
|
|
3166
|
-
startTime:
|
|
3168
|
+
startTime: parseTimestamp(issue.creationDate) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
3167
3169
|
});
|
|
3168
3170
|
}
|
|
3169
3171
|
function extractSourceLocation(issue, componentMap) {
|
|
@@ -3200,6 +3202,28 @@ function buildNistTags$3(sourceIdentifier, ruleName) {
|
|
|
3200
3202
|
if (byName) return [byName];
|
|
3201
3203
|
return [];
|
|
3202
3204
|
}
|
|
3205
|
+
/**
|
|
3206
|
+
* Flag rules whose NIST mappings exist at a revision other than the one
|
|
3207
|
+
* currently selected — they emit no NIST tags here even though a mapping
|
|
3208
|
+
* exists elsewhere, a likely sign of a wrong revision selection. Rules
|
|
3209
|
+
* unmapped at every revision are not flagged. Throws in strict mode; otherwise
|
|
3210
|
+
* logs a single aggregated warning.
|
|
3211
|
+
*/
|
|
3212
|
+
function checkRevisionAlignment(rules) {
|
|
3213
|
+
const rev = getCurrentNistRevision();
|
|
3214
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3215
|
+
const lines = [];
|
|
3216
|
+
for (const rule of rules) {
|
|
3217
|
+
const covered = awsConfigMappedRevisions(rule.Source.SourceIdentifier, rule.ConfigRuleName);
|
|
3218
|
+
if (covered.length === 0 || covered.includes(rev) || seen.has(rule.ConfigRuleName)) continue;
|
|
3219
|
+
seen.add(rule.ConfigRuleName);
|
|
3220
|
+
lines.push(` - ${rule.ConfigRuleName} (mapped at Rev ${covered.join(", ")})`);
|
|
3221
|
+
}
|
|
3222
|
+
if (lines.length === 0) return;
|
|
3223
|
+
const detail = `${lines.length} AWS Config rule(s) have NIST 800-53 mappings at a different revision than the requested Rev ${rev}; their NIST tags were omitted:\n${lines.join("\n")}`;
|
|
3224
|
+
if (isNistStrict()) throw new Error(`aws-config: ${detail}\nre-run with a matching revision, or disable strict mode to convert with the gaps`);
|
|
3225
|
+
console.warn(`WARNING: ${detail}`);
|
|
3226
|
+
}
|
|
3203
3227
|
function buildCheckText(rule) {
|
|
3204
3228
|
const parts = [`ARN: ${rule.ConfigRuleArn || "N/A"}`, `Source Identifier: ${rule.Source.SourceIdentifier || "N/A"}`];
|
|
3205
3229
|
if (rule.InputParameters && rule.InputParameters !== "{}") {
|
|
@@ -3220,7 +3244,7 @@ function buildResult(r) {
|
|
|
3220
3244
|
const status = mapComplianceStatus(r.ComplianceType);
|
|
3221
3245
|
const codeDesc = buildCodeDesc$7(q);
|
|
3222
3246
|
const message = buildResultMessage(codeDesc, r.Annotation, status);
|
|
3223
|
-
const startTime = r.ConfigRuleInvokedTime ?
|
|
3247
|
+
const startTime = (r.ConfigRuleInvokedTime ? parseTimestamp(r.ConfigRuleInvokedTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
3224
3248
|
return createResult(status, message ?? codeDesc, {
|
|
3225
3249
|
codeDesc,
|
|
3226
3250
|
...startTime ? { startTime } : {}
|
|
@@ -3280,6 +3304,7 @@ async function convertAwsConfigToHdf(input) {
|
|
|
3280
3304
|
const { items: limitedRules, truncated: truncatedRules } = limitArray(data.ConfigRules);
|
|
3281
3305
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
3282
3306
|
if (truncatedRules) console.warn(`WARNING: Input truncated at ${limitedRules.length} ConfigRule items (original: ${data.ConfigRules.length})`);
|
|
3307
|
+
checkRevisionAlignment(limitedRules);
|
|
3283
3308
|
const baseline = {
|
|
3284
3309
|
...createMinimalBaseline("AWS Config", limitedRules.map(buildRequirement$15), { resultsChecksum }),
|
|
3285
3310
|
title: "AWS Config Compliance Results",
|
|
@@ -3329,17 +3354,20 @@ function getImpact$8(severity) {
|
|
|
3329
3354
|
/**
|
|
3330
3355
|
* Converts a single CheckovCheck to an HDF RequirementResult.
|
|
3331
3356
|
*/
|
|
3332
|
-
function checkToResult(check) {
|
|
3357
|
+
function checkToResult(check, scanTime) {
|
|
3333
3358
|
const status = mapStatus$2(check.check_result.result);
|
|
3334
3359
|
const codeDesc = `Resource: ${check.resource}\nFile: ${check.file_path} (lines ${JSON.stringify(check.file_line_range)})`;
|
|
3335
3360
|
let message;
|
|
3336
3361
|
if (status === ResultStatus.NotReviewed && check.check_result.suppress_comment) message = check.check_result.suppress_comment;
|
|
3337
|
-
return createResult(status, message ?? "", {
|
|
3362
|
+
return createResult(status, message ?? "", {
|
|
3363
|
+
codeDesc,
|
|
3364
|
+
startTime: scanTime
|
|
3365
|
+
});
|
|
3338
3366
|
}
|
|
3339
3367
|
/**
|
|
3340
3368
|
* Converts a group of checks sharing a check_id into one EvaluatedRequirement.
|
|
3341
3369
|
*/
|
|
3342
|
-
function buildRequirement$14(checkId, checks) {
|
|
3370
|
+
function buildRequirement$14(checkId, checks, scanTime) {
|
|
3343
3371
|
const rep = checks[0];
|
|
3344
3372
|
const impact = getImpact$8(rep.severity);
|
|
3345
3373
|
const tags = { nist: [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS] };
|
|
@@ -3351,7 +3379,7 @@ function buildRequirement$14(checkId, checks) {
|
|
|
3351
3379
|
label: "check",
|
|
3352
3380
|
data: rep.guideline
|
|
3353
3381
|
});
|
|
3354
|
-
const results = checks.map(checkToResult);
|
|
3382
|
+
const results = checks.map((check) => checkToResult(check, scanTime));
|
|
3355
3383
|
const req = createRequirement(checkId, rep.check_name, descriptions, impact, results, { tags });
|
|
3356
3384
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
3357
3385
|
const controlType = deriveControlTypeFromTags([...DEFAULT_STATIC_ANALYSIS_NIST_TAGS]);
|
|
@@ -3382,6 +3410,7 @@ async function convertCheckovToHdf(input) {
|
|
|
3382
3410
|
const detected = detectConverter(input);
|
|
3383
3411
|
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input);
|
|
3384
3412
|
const resultsChecksum = await inputChecksum(input);
|
|
3413
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
3385
3414
|
const reports = parseInput(input);
|
|
3386
3415
|
const allChecks = [];
|
|
3387
3416
|
const checkTypes = [];
|
|
@@ -3403,11 +3432,11 @@ async function convertCheckovToHdf(input) {
|
|
|
3403
3432
|
else groups.set(check.check_id, [check]);
|
|
3404
3433
|
}
|
|
3405
3434
|
const requirements = [];
|
|
3406
|
-
for (const [checkId, checks] of groups) requirements.push(buildRequirement$14(checkId, checks));
|
|
3435
|
+
for (const [checkId, checks] of groups) requirements.push(buildRequirement$14(checkId, checks, scanTime));
|
|
3407
3436
|
const format = checkTypes.join(", ");
|
|
3408
3437
|
if (requirements.length === 0) {
|
|
3409
3438
|
const target = format || "input";
|
|
3410
|
-
requirements.push(buildNoFindingsRequirement("checkov-no-findings", `Checkov scanned ${target} and reported zero findings.`,
|
|
3439
|
+
requirements.push(buildNoFindingsRequirement("checkov-no-findings", `Checkov scanned ${target} and reported zero findings.`, scanTime));
|
|
3411
3440
|
}
|
|
3412
3441
|
const baseline = createMinimalBaseline("Checkov Scan", requirements, { resultsChecksum });
|
|
3413
3442
|
return buildHdfResults({
|
|
@@ -3417,7 +3446,7 @@ async function convertCheckovToHdf(input) {
|
|
|
3417
3446
|
toolVersion: version,
|
|
3418
3447
|
toolFormat: format,
|
|
3419
3448
|
baselines: [baseline],
|
|
3420
|
-
timestamp:
|
|
3449
|
+
timestamp: scanTime
|
|
3421
3450
|
});
|
|
3422
3451
|
}
|
|
3423
3452
|
//#endregion
|
|
@@ -3448,19 +3477,22 @@ function formatSkipMessage(suppressions) {
|
|
|
3448
3477
|
/**
|
|
3449
3478
|
* Converts a single GosecIssue to an HDF RequirementResult.
|
|
3450
3479
|
*/
|
|
3451
|
-
function issueToResult(issue) {
|
|
3480
|
+
function issueToResult(issue, scanTime) {
|
|
3452
3481
|
const suppressed = isSuppressed(issue);
|
|
3453
3482
|
const status = suppressed ? ResultStatus.NotReviewed : ResultStatus.Failed;
|
|
3454
3483
|
let message;
|
|
3455
3484
|
if (suppressed) message = formatSkipMessage(issue.suppressions) ?? "No justification provided";
|
|
3456
3485
|
else message = `${issue.confidence} confidence of rule violation at:\n${issue.code}`;
|
|
3457
3486
|
const codeDesc = `Rule ${issue.rule_id} violation detected at:\nFile: ${issue.file}\nLine: ${issue.line}\nColumn: ${issue.column}`;
|
|
3458
|
-
return createResult(status, message, {
|
|
3487
|
+
return createResult(status, message, {
|
|
3488
|
+
codeDesc,
|
|
3489
|
+
startTime: scanTime
|
|
3490
|
+
});
|
|
3459
3491
|
}
|
|
3460
3492
|
/**
|
|
3461
3493
|
* Converts a group of issues sharing a rule_id into one EvaluatedRequirement.
|
|
3462
3494
|
*/
|
|
3463
|
-
function buildRequirement$13(ruleId, issues) {
|
|
3495
|
+
function buildRequirement$13(ruleId, issues, scanTime) {
|
|
3464
3496
|
const rep = issues[0];
|
|
3465
3497
|
const impact = IMPACT_MAPPING$6[rep.severity.toUpperCase()] ?? .5;
|
|
3466
3498
|
const nist = mapCWEToNIST([rep.cwe.id], DEFAULT_REMEDIATION_NIST_TAGS$1);
|
|
@@ -3471,7 +3503,7 @@ function buildRequirement$13(ruleId, issues) {
|
|
|
3471
3503
|
url: rep.cwe.url
|
|
3472
3504
|
}
|
|
3473
3505
|
};
|
|
3474
|
-
const results = issues.map(issueToResult);
|
|
3506
|
+
const results = issues.map((issue) => issueToResult(issue, scanTime));
|
|
3475
3507
|
const descriptions = [{
|
|
3476
3508
|
label: "default",
|
|
3477
3509
|
data: rep.details
|
|
@@ -3511,9 +3543,10 @@ async function convertGosecToHdf(input) {
|
|
|
3511
3543
|
if (existing) existing.push(issue);
|
|
3512
3544
|
else groups.set(issue.rule_id, [issue]);
|
|
3513
3545
|
}
|
|
3546
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
3514
3547
|
const requirements = [];
|
|
3515
|
-
for (const [ruleId, issues] of groups) requirements.push(buildRequirement$13(ruleId, issues));
|
|
3516
|
-
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("gosec-no-findings", "gosec scanned Go codebase and reported zero findings.",
|
|
3548
|
+
for (const [ruleId, issues] of groups) requirements.push(buildRequirement$13(ruleId, issues, scanTime));
|
|
3549
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("gosec-no-findings", "gosec scanned Go codebase and reported zero findings.", scanTime));
|
|
3517
3550
|
const baseline = createMinimalBaseline("gosec Scan", requirements, { resultsChecksum });
|
|
3518
3551
|
return buildHdfResults({
|
|
3519
3552
|
generatorName: "gosec-to-hdf",
|
|
@@ -3521,7 +3554,7 @@ async function convertGosecToHdf(input) {
|
|
|
3521
3554
|
toolName: "gosec",
|
|
3522
3555
|
toolVersion: report.GosecVersion || void 0,
|
|
3523
3556
|
baselines: [baseline],
|
|
3524
|
-
timestamp:
|
|
3557
|
+
timestamp: scanTime
|
|
3525
3558
|
});
|
|
3526
3559
|
}
|
|
3527
3560
|
//#endregion
|
|
@@ -3661,6 +3694,15 @@ function selectSite(sites) {
|
|
|
3661
3694
|
}
|
|
3662
3695
|
return best;
|
|
3663
3696
|
}
|
|
3697
|
+
const ZAP_RFC1123_LIKE = /^[A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2}$/;
|
|
3698
|
+
function parseZapTimestamp(s) {
|
|
3699
|
+
const trimmed = s.trim();
|
|
3700
|
+
if (ZAP_RFC1123_LIKE.test(trimmed)) {
|
|
3701
|
+
const d = /* @__PURE__ */ new Date(`${trimmed} GMT`);
|
|
3702
|
+
if (!isNaN(d.getTime())) return d;
|
|
3703
|
+
}
|
|
3704
|
+
return parseTimestamp(s) ?? void 0;
|
|
3705
|
+
}
|
|
3664
3706
|
async function convertZapToHdf(input) {
|
|
3665
3707
|
validateInputSize(input, "zap");
|
|
3666
3708
|
registerAllFingerprints();
|
|
@@ -3762,12 +3804,15 @@ async function convertZapToHdf(input) {
|
|
|
3762
3804
|
},
|
|
3763
3805
|
tool
|
|
3764
3806
|
};
|
|
3765
|
-
if (zapData["@generated"])
|
|
3766
|
-
|
|
3807
|
+
if (zapData["@generated"]) {
|
|
3808
|
+
const ts = parseZapTimestamp(zapData["@generated"]);
|
|
3809
|
+
if (ts) hdf.timestamp = ts;
|
|
3810
|
+
}
|
|
3811
|
+
return serializeHdf(hdf);
|
|
3767
3812
|
}
|
|
3768
3813
|
//#endregion
|
|
3769
3814
|
//#region converters/cyclonedx-to-hdf/typescript/converter.ts
|
|
3770
|
-
const CVSS_METHODS = new Set([
|
|
3815
|
+
const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
3771
3816
|
"CVSSv2",
|
|
3772
3817
|
"CVSSv3",
|
|
3773
3818
|
"CVSSv31",
|
|
@@ -3832,6 +3877,8 @@ async function convertCyclonedxToHdf(input) {
|
|
|
3832
3877
|
if ((!bom.components || bom.components.length === 0) && (!bom.vulnerabilities || bom.vulnerabilities.length === 0)) throw new Error("cyclonedx: input has neither components nor vulnerabilities");
|
|
3833
3878
|
if (!bom.vulnerabilities || bom.vulnerabilities.length === 0) throw new Error("cyclonedx: this file is an SBOM inventory with no vulnerabilities; to import SBOM data into a system document, use:\n hdf system create --from <sbom-file> --component-name <name>");
|
|
3834
3879
|
const resultsChecksum = await inputChecksum(input);
|
|
3880
|
+
const parsedTimestamp = bom.metadata?.timestamp ? parseTimestamp(bom.metadata.timestamp) ?? void 0 : void 0;
|
|
3881
|
+
const scanTime = parsedTimestamp && !isNaN(parsedTimestamp.getTime()) ? parsedTimestamp : /* @__PURE__ */ new Date();
|
|
3835
3882
|
const allComponents = flattenComponents(bom.components ?? []);
|
|
3836
3883
|
const componentLookup = /* @__PURE__ */ new Map();
|
|
3837
3884
|
for (const comp of allComponents) if (comp["bom-ref"]) componentLookup.set(comp["bom-ref"], comp);
|
|
@@ -3870,7 +3917,13 @@ async function convertCyclonedxToHdf(input) {
|
|
|
3870
3917
|
data: fixParts.join("\n\n")
|
|
3871
3918
|
});
|
|
3872
3919
|
const affects = vuln.affects ?? [];
|
|
3873
|
-
const results = affects.length > 0 ? affects.map((affect) => createResult(ResultStatus.Failed, void 0, {
|
|
3920
|
+
const results = affects.length > 0 ? affects.map((affect) => createResult(ResultStatus.Failed, void 0, {
|
|
3921
|
+
codeDesc: formatCodeDesc$4(componentLookup, affect.ref),
|
|
3922
|
+
startTime: scanTime
|
|
3923
|
+
})) : [createResult(ResultStatus.Failed, void 0, {
|
|
3924
|
+
codeDesc: `Vulnerability ${vuln.id}`,
|
|
3925
|
+
startTime: scanTime
|
|
3926
|
+
})];
|
|
3874
3927
|
const title = vuln.source?.name ? `${vuln.id} (${vuln.source.name})` : vuln.id;
|
|
3875
3928
|
const req = createRequirement(vuln.id, title, descriptions, impact, results, { tags });
|
|
3876
3929
|
const controlType = deriveControlTypeFromTags(nist);
|
|
@@ -3889,7 +3942,7 @@ async function convertCyclonedxToHdf(input) {
|
|
|
3889
3942
|
name: targetName,
|
|
3890
3943
|
type: TargetType.Application
|
|
3891
3944
|
}],
|
|
3892
|
-
timestamp:
|
|
3945
|
+
timestamp: scanTime
|
|
3893
3946
|
});
|
|
3894
3947
|
}
|
|
3895
3948
|
//#endregion
|
|
@@ -4060,7 +4113,7 @@ async function convertSplunkToHdf(input) {
|
|
|
4060
4113
|
const descriptions = convertDescriptions(control.descriptions);
|
|
4061
4114
|
const results = Array.isArray(control.results) ? control.results.map((result) => createResult(mapStatus$1(result.status), result.message, {
|
|
4062
4115
|
codeDesc: result.code_desc,
|
|
4063
|
-
startTime:
|
|
4116
|
+
startTime: parseTimestamp(result.start_time) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z"),
|
|
4064
4117
|
runTime: result.run_time,
|
|
4065
4118
|
exception: result.exception,
|
|
4066
4119
|
backtrace: result.backtrace
|
|
@@ -4083,7 +4136,7 @@ async function convertSplunkToHdf(input) {
|
|
|
4083
4136
|
allBaselines.push(createMinimalBaseline(profile.name, requirements, baselineOptions));
|
|
4084
4137
|
}
|
|
4085
4138
|
}
|
|
4086
|
-
|
|
4139
|
+
return serializeHdf({
|
|
4087
4140
|
baselines: allBaselines,
|
|
4088
4141
|
components: [{
|
|
4089
4142
|
name: targetName,
|
|
@@ -4095,8 +4148,7 @@ async function convertSplunkToHdf(input) {
|
|
|
4095
4148
|
name: "splunk-to-hdf",
|
|
4096
4149
|
version: "1.0.0"
|
|
4097
4150
|
}
|
|
4098
|
-
};
|
|
4099
|
-
return JSON.stringify(hdf, null, 2);
|
|
4151
|
+
});
|
|
4100
4152
|
}
|
|
4101
4153
|
//#endregion
|
|
4102
4154
|
//#region converters/hdf-to-xml/typescript/converter.ts
|
|
@@ -4306,7 +4358,7 @@ async function convertGitlabToHdf(input) {
|
|
|
4306
4358
|
const result = {
|
|
4307
4359
|
status: ResultStatus.Failed,
|
|
4308
4360
|
codeDesc: buildCodeDesc$4(scanType, vuln.location),
|
|
4309
|
-
startTime: startTime ? new Date(
|
|
4361
|
+
startTime: startTime ? parseTimestamp(startTime) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z") : /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
4310
4362
|
};
|
|
4311
4363
|
const impact = gitlabSeverityToImpact(vuln.severity ?? "Unknown");
|
|
4312
4364
|
const req = {
|
|
@@ -4324,7 +4376,7 @@ async function convertGitlabToHdf(input) {
|
|
|
4324
4376
|
}
|
|
4325
4377
|
const label = scanTypeLabel(scanType);
|
|
4326
4378
|
if (requirements.length === 0) {
|
|
4327
|
-
const ts = startTime ? new Date(
|
|
4379
|
+
const ts = startTime ? parseTimestamp(startTime) ?? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date();
|
|
4328
4380
|
requirements.push(buildNoFindingsRequirement("gitlab-no-findings", `GitLab ${label} scan via ${scannerName} reported zero findings.`, ts));
|
|
4329
4381
|
}
|
|
4330
4382
|
const baseline = createMinimalBaseline("GitLab Security Scan", requirements, {
|
|
@@ -4352,8 +4404,11 @@ async function convertGitlabToHdf(input) {
|
|
|
4352
4404
|
},
|
|
4353
4405
|
tool
|
|
4354
4406
|
};
|
|
4355
|
-
if (report.scan?.end_time)
|
|
4356
|
-
|
|
4407
|
+
if (report.scan?.end_time) {
|
|
4408
|
+
const endTime = parseTimestamp(report.scan.end_time);
|
|
4409
|
+
if (endTime) hdf.timestamp = endTime;
|
|
4410
|
+
}
|
|
4411
|
+
return serializeHdf(hdf);
|
|
4357
4412
|
}
|
|
4358
4413
|
//#endregion
|
|
4359
4414
|
//#region converters/trufflehog-to-hdf/typescript/converter.ts
|
|
@@ -4420,8 +4475,8 @@ function buildCodeDesc$3(f) {
|
|
|
4420
4475
|
*/
|
|
4421
4476
|
function getTimestamp(f) {
|
|
4422
4477
|
if (f.SourceMetadata?.Data?.Git?.timestamp) {
|
|
4423
|
-
const ts =
|
|
4424
|
-
if (
|
|
4478
|
+
const ts = parseTimestamp(f.SourceMetadata.Data.Git.timestamp);
|
|
4479
|
+
if (ts) return ts;
|
|
4425
4480
|
}
|
|
4426
4481
|
return /* @__PURE__ */ new Date();
|
|
4427
4482
|
}
|
|
@@ -4512,7 +4567,7 @@ async function convertTrufflehogToHdf(input) {
|
|
|
4512
4567
|
name: repoURL,
|
|
4513
4568
|
type: TargetType.Repository
|
|
4514
4569
|
}];
|
|
4515
|
-
return
|
|
4570
|
+
return serializeHdf(hdf);
|
|
4516
4571
|
}
|
|
4517
4572
|
//#endregion
|
|
4518
4573
|
//#region converters/burpsuite-to-hdf/typescript/converter.ts
|
|
@@ -4594,8 +4649,8 @@ async function convertBurpsuiteToHdf(input) {
|
|
|
4594
4649
|
},
|
|
4595
4650
|
tool
|
|
4596
4651
|
};
|
|
4597
|
-
if (exportTime) hdf.timestamp =
|
|
4598
|
-
return
|
|
4652
|
+
if (exportTime) hdf.timestamp = parseTimestamp(exportTime) ?? void 0;
|
|
4653
|
+
return serializeHdf(hdf);
|
|
4599
4654
|
}
|
|
4600
4655
|
function buildRequirement$11(issueType, issues) {
|
|
4601
4656
|
const rep = issues[0];
|
|
@@ -4702,15 +4757,31 @@ function formatSummary(f) {
|
|
|
4702
4757
|
`IP Address, Port, Instance : ${f["IP Address, Port, Instance"] ?? ""}`
|
|
4703
4758
|
].join("\n");
|
|
4704
4759
|
}
|
|
4760
|
+
const MONTH_ABBR = {
|
|
4761
|
+
Jan: "01",
|
|
4762
|
+
Feb: "02",
|
|
4763
|
+
Mar: "03",
|
|
4764
|
+
Apr: "04",
|
|
4765
|
+
May: "05",
|
|
4766
|
+
Jun: "06",
|
|
4767
|
+
Jul: "07",
|
|
4768
|
+
Aug: "08",
|
|
4769
|
+
Sep: "09",
|
|
4770
|
+
Oct: "10",
|
|
4771
|
+
Nov: "11",
|
|
4772
|
+
Dec: "12"
|
|
4773
|
+
};
|
|
4705
4774
|
/**
|
|
4706
|
-
* Parses DBProtect date
|
|
4775
|
+
* Parses DBProtect date formats ("Feb 18 2021 15:57" or "2021-02-18 15:55").
|
|
4776
|
+
* Month-name values are normalized to ISO so parseTimestamp interprets them as
|
|
4777
|
+
* UTC, matching the Go peer and keeping output host-timezone-independent.
|
|
4707
4778
|
*/
|
|
4708
4779
|
function parseDate(dateStr) {
|
|
4709
4780
|
const trimmed = dateStr.trim();
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
return
|
|
4781
|
+
const ZERO = /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
4782
|
+
if (!trimmed) return ZERO;
|
|
4783
|
+
const month = /^([A-Z][a-z]{2}) (\d{1,2}) (\d{4}) (\d{2}:\d{2})$/.exec(trimmed);
|
|
4784
|
+
return parseTimestamp(month ? `${month[3]}-${MONTH_ABBR[month[1]] ?? "00"}-${month[2].padStart(2, "0")} ${month[4]}` : trimmed) ?? ZERO;
|
|
4714
4785
|
}
|
|
4715
4786
|
/**
|
|
4716
4787
|
* Builds a single EvaluatedRequirement from a group of findings sharing a Check ID.
|
|
@@ -4989,7 +5060,7 @@ function buildRequirement$9(vuln, packageTypes, distro) {
|
|
|
4989
5060
|
label: "default",
|
|
4990
5061
|
data: vuln.description
|
|
4991
5062
|
}];
|
|
4992
|
-
const startTime = vuln.discoveredDate ?
|
|
5063
|
+
const startTime = (vuln.discoveredDate ? parseTimestamp(vuln.discoveredDate) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
4993
5064
|
const results = [createResult(ResultStatus.Failed, void 0, {
|
|
4994
5065
|
codeDesc: formatCodeDesc$2(vuln),
|
|
4995
5066
|
startTime
|
|
@@ -5121,7 +5192,7 @@ function buildRequirement$8(finding, timestamp) {
|
|
|
5121
5192
|
const codeDesc = finding.vulnerability.recommendation ?? "No recommendation available";
|
|
5122
5193
|
const results = [createResult(ResultStatus.Failed, void 0, {
|
|
5123
5194
|
codeDesc,
|
|
5124
|
-
startTime: timestamp ?
|
|
5195
|
+
startTime: (timestamp ? parseTimestamp(timestamp) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
5125
5196
|
})];
|
|
5126
5197
|
const req = createRequirement(finding.matrix, getTitle$1(finding), descriptions, getImpact$5(finding.vulnerability.severity), results, { tags });
|
|
5127
5198
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
@@ -5242,7 +5313,7 @@ function formatCodeDesc$1(entry) {
|
|
|
5242
5313
|
/**
|
|
5243
5314
|
* Builds a single EvaluatedRequirement from a group of entries sharing an ID.
|
|
5244
5315
|
*/
|
|
5245
|
-
function buildRequirement$7(entryID, entries) {
|
|
5316
|
+
function buildRequirement$7(entryID, entries, scanTime) {
|
|
5246
5317
|
const rep = entries[0];
|
|
5247
5318
|
const cweIDs = extractCWEs$1(rep);
|
|
5248
5319
|
const nist = mapCWEToNIST(cweIDs, DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
@@ -5255,7 +5326,10 @@ function buildRequirement$7(entryID, entries) {
|
|
|
5255
5326
|
label: "default",
|
|
5256
5327
|
data: formatDescription(rep)
|
|
5257
5328
|
}];
|
|
5258
|
-
const results = entries.map((entry) => createResult(ResultStatus.Failed, void 0, {
|
|
5329
|
+
const results = entries.map((entry) => createResult(ResultStatus.Failed, void 0, {
|
|
5330
|
+
codeDesc: formatCodeDesc$1(entry),
|
|
5331
|
+
startTime: scanTime
|
|
5332
|
+
}));
|
|
5259
5333
|
const req = createRequirement(entryID, rep.summary, descriptions, severityToImpact(rep.severity), results, { tags });
|
|
5260
5334
|
const controlType = deriveControlTypeFromTags(nist);
|
|
5261
5335
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -5315,6 +5389,7 @@ async function convertJfrogXrayToHdf(input) {
|
|
|
5315
5389
|
const resultsChecksum = await inputChecksum(input);
|
|
5316
5390
|
const parsed = parseJSON(input);
|
|
5317
5391
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.data)) throw new Error("jfrog-xray: invalid JSON structure");
|
|
5392
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
5318
5393
|
const { items: limitedEntries, truncated } = limitArray(parsed.data);
|
|
5319
5394
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
5320
5395
|
if (truncated) console.warn(`WARNING: Input truncated at ${limitedEntries.length} entries (original: ${parsed.data.length})`);
|
|
@@ -5331,8 +5406,8 @@ async function convertJfrogXrayToHdf(input) {
|
|
|
5331
5406
|
else groups.set(id, [entry]);
|
|
5332
5407
|
}
|
|
5333
5408
|
const requirements = [];
|
|
5334
|
-
for (const [entryID, entries] of groups) requirements.push(buildRequirement$7(entryID, entries));
|
|
5335
|
-
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("jfrog-xray-no-findings", "JFrog Xray scanned the target artifact and reported zero vulnerable components.",
|
|
5409
|
+
for (const [entryID, entries] of groups) requirements.push(buildRequirement$7(entryID, entries, scanTime));
|
|
5410
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("jfrog-xray-no-findings", "JFrog Xray scanned the target artifact and reported zero vulnerable components.", scanTime));
|
|
5336
5411
|
return buildHdfResults({
|
|
5337
5412
|
generatorName: "jfrog-xray-to-hdf",
|
|
5338
5413
|
converterVersion: "1.0.0",
|
|
@@ -5343,7 +5418,7 @@ async function convertJfrogXrayToHdf(input) {
|
|
|
5343
5418
|
name: "JFrog Xray Scan",
|
|
5344
5419
|
type: TargetType.Application
|
|
5345
5420
|
}],
|
|
5346
|
-
timestamp:
|
|
5421
|
+
timestamp: scanTime
|
|
5347
5422
|
});
|
|
5348
5423
|
}
|
|
5349
5424
|
//#endregion
|
|
@@ -5387,7 +5462,7 @@ function vulnMessage(vuln) {
|
|
|
5387
5462
|
/**
|
|
5388
5463
|
* Builds a single EvaluatedRequirement from a NeuVector vulnerability.
|
|
5389
5464
|
*/
|
|
5390
|
-
function buildRequirement$6(vuln) {
|
|
5465
|
+
function buildRequirement$6(vuln, scanTime) {
|
|
5391
5466
|
const cweIDs = extractCWEs(vuln.description);
|
|
5392
5467
|
const nist = mapCWEToNIST(cweIDs, DEFAULT_REMEDIATION_NIST_TAGS);
|
|
5393
5468
|
const tags = {
|
|
@@ -5399,7 +5474,10 @@ function buildRequirement$6(vuln) {
|
|
|
5399
5474
|
label: "default",
|
|
5400
5475
|
data: vuln.description
|
|
5401
5476
|
}];
|
|
5402
|
-
const results = [createResult(ResultStatus.Failed, vulnMessage(vuln), {
|
|
5477
|
+
const results = [createResult(ResultStatus.Failed, vulnMessage(vuln), {
|
|
5478
|
+
codeDesc: "",
|
|
5479
|
+
startTime: scanTime
|
|
5480
|
+
})];
|
|
5403
5481
|
const req = createRequirement(vulnID(vuln), vulnTitle(vuln), descriptions, getImpact$4(vuln), results, { tags });
|
|
5404
5482
|
const controlType = deriveControlTypeFromTags(nist);
|
|
5405
5483
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -5450,17 +5528,17 @@ async function convertNeuvectorToHdf(input) {
|
|
|
5450
5528
|
const { items: limitedVulns, truncated } = limitArray(scan.report.vulnerabilities ?? []);
|
|
5451
5529
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
5452
5530
|
if (truncated) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${scan.report.vulnerabilities.length})`);
|
|
5531
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
5453
5532
|
const seen = /* @__PURE__ */ new Set();
|
|
5454
5533
|
const requirements = [];
|
|
5455
5534
|
for (const vuln of limitedVulns) {
|
|
5456
5535
|
const id = vulnID(vuln);
|
|
5457
5536
|
if (seen.has(id)) continue;
|
|
5458
5537
|
seen.add(id);
|
|
5459
|
-
requirements.push(buildRequirement$6(vuln));
|
|
5538
|
+
requirements.push(buildRequirement$6(vuln, scanTime));
|
|
5460
5539
|
}
|
|
5461
|
-
const now = /* @__PURE__ */ new Date();
|
|
5462
5540
|
const target = targetNameFromReport(scan.report);
|
|
5463
|
-
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("neuvector-no-findings", `NeuVector scanned ${target} and reported zero vulnerable components.`,
|
|
5541
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("neuvector-no-findings", `NeuVector scanned ${target} and reported zero vulnerable components.`, scanTime));
|
|
5464
5542
|
return buildHdfResults({
|
|
5465
5543
|
generatorName: "neuvector-to-hdf",
|
|
5466
5544
|
converterVersion: "1.0.0",
|
|
@@ -5478,7 +5556,7 @@ async function convertNeuvectorToHdf(input) {
|
|
|
5478
5556
|
registry: scan.report.registry
|
|
5479
5557
|
}
|
|
5480
5558
|
}],
|
|
5481
|
-
timestamp:
|
|
5559
|
+
timestamp: scanTime
|
|
5482
5560
|
});
|
|
5483
5561
|
}
|
|
5484
5562
|
//#endregion
|
|
@@ -5553,7 +5631,7 @@ function buildRequirement$5(desc, vulns, snippetMap, startTimeStr) {
|
|
|
5553
5631
|
const results = limitedVulns.map((vuln) => ({
|
|
5554
5632
|
status: ResultStatus.Failed,
|
|
5555
5633
|
codeDesc: buildCodeDesc$2(vuln, snippetMap),
|
|
5556
|
-
startTime: new Date(
|
|
5634
|
+
startTime: parseTimestamp(startTimeStr) ?? /* @__PURE__ */ new Date()
|
|
5557
5635
|
}));
|
|
5558
5636
|
const req = {
|
|
5559
5637
|
id: desc.classID ?? "unknown",
|
|
@@ -5619,8 +5697,8 @@ async function convertFortifyToHdf(input) {
|
|
|
5619
5697
|
format: "FVDL"
|
|
5620
5698
|
}
|
|
5621
5699
|
};
|
|
5622
|
-
if (createdDate) hdfResult.timestamp = new Date(
|
|
5623
|
-
return
|
|
5700
|
+
if (createdDate) hdfResult.timestamp = parseTimestamp(startTimeStr) ?? /* @__PURE__ */ new Date();
|
|
5701
|
+
return serializeHdf(hdfResult);
|
|
5624
5702
|
}
|
|
5625
5703
|
//#endregion
|
|
5626
5704
|
//#region converters/prisma-to-hdf/typescript/converter.ts
|
|
@@ -5704,7 +5782,7 @@ function makeTitle(rec) {
|
|
|
5704
5782
|
/**
|
|
5705
5783
|
* Builds a single EvaluatedRequirement from a Prisma record.
|
|
5706
5784
|
*/
|
|
5707
|
-
function buildRequirement$4(rec) {
|
|
5785
|
+
function buildRequirement$4(rec, scanTime) {
|
|
5708
5786
|
const id = makeRequirementID(rec);
|
|
5709
5787
|
const title = makeTitle(rec);
|
|
5710
5788
|
const codeDesc = makeCodeDesc(rec);
|
|
@@ -5718,7 +5796,10 @@ function buildRequirement$4(rec) {
|
|
|
5718
5796
|
label: "default",
|
|
5719
5797
|
data: rec.Description
|
|
5720
5798
|
}];
|
|
5721
|
-
const results = [createResult(ResultStatus.Failed, message, {
|
|
5799
|
+
const results = [createResult(ResultStatus.Failed, message, {
|
|
5800
|
+
codeDesc,
|
|
5801
|
+
startTime: scanTime
|
|
5802
|
+
})];
|
|
5722
5803
|
const req = createRequirement(id, title, descriptions, getImpact$3(rec.Severity), results, { tags });
|
|
5723
5804
|
const controlType = deriveControlTypeFromTags(nist);
|
|
5724
5805
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -5781,8 +5862,8 @@ function groupByHostname(records) {
|
|
|
5781
5862
|
/**
|
|
5782
5863
|
* Builds an HDF baseline from all records for a single host.
|
|
5783
5864
|
*/
|
|
5784
|
-
function buildBaseline(hostname, records, resultsChecksum) {
|
|
5785
|
-
return createMinimalBaseline("Prisma Cloud Scan", limitArrayWithWarning(records, "finding").map((rec) => buildRequirement$4(rec)), {
|
|
5865
|
+
function buildBaseline(hostname, records, resultsChecksum, scanTime) {
|
|
5866
|
+
return createMinimalBaseline("Prisma Cloud Scan", limitArrayWithWarning(records, "finding").map((rec) => buildRequirement$4(rec, scanTime)), {
|
|
5786
5867
|
resultsChecksum,
|
|
5787
5868
|
title: `Prisma Cloud Scan (${hostname})`
|
|
5788
5869
|
});
|
|
@@ -5801,16 +5882,17 @@ async function convertPrismaToHdf(input) {
|
|
|
5801
5882
|
for (const col of REQUIRED_COLUMNS) if (!headers.includes(col)) throw new Error(`prisma: missing required CSV column "${col}"`);
|
|
5802
5883
|
const records = parseCsv(input);
|
|
5803
5884
|
const resultsChecksum = await inputChecksum(input);
|
|
5885
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
5804
5886
|
const baselines = [];
|
|
5805
5887
|
const components = [];
|
|
5806
|
-
if (records.length === 0) baselines.push(createMinimalBaseline("Prisma Cloud Scan", [buildNoFindingsRequirement("prisma-no-findings", "Prisma Cloud scanned the workload and reported zero vulnerable components.",
|
|
5888
|
+
if (records.length === 0) baselines.push(createMinimalBaseline("Prisma Cloud Scan", [buildNoFindingsRequirement("prisma-no-findings", "Prisma Cloud scanned the workload and reported zero vulnerable components.", scanTime)], {
|
|
5807
5889
|
resultsChecksum,
|
|
5808
5890
|
title: "Prisma Cloud Scan"
|
|
5809
5891
|
}));
|
|
5810
5892
|
else {
|
|
5811
5893
|
const hostGroups = groupByHostname(records);
|
|
5812
5894
|
for (const [hostname, hostRecords] of hostGroups) {
|
|
5813
|
-
baselines.push(buildBaseline(hostname, hostRecords, resultsChecksum));
|
|
5895
|
+
baselines.push(buildBaseline(hostname, hostRecords, resultsChecksum, scanTime));
|
|
5814
5896
|
components.push({
|
|
5815
5897
|
name: hostname,
|
|
5816
5898
|
type: TargetType.Host
|
|
@@ -5824,7 +5906,7 @@ async function convertPrismaToHdf(input) {
|
|
|
5824
5906
|
toolFormat: "CSV",
|
|
5825
5907
|
baselines,
|
|
5826
5908
|
components,
|
|
5827
|
-
timestamp:
|
|
5909
|
+
timestamp: scanTime
|
|
5828
5910
|
});
|
|
5829
5911
|
}
|
|
5830
5912
|
//#endregion
|
|
@@ -5840,6 +5922,15 @@ const IMPACT_MAPPING$3 = {
|
|
|
5840
5922
|
function getImpact$2(severity) {
|
|
5841
5923
|
return IMPACT_MAPPING$3[severity.toLowerCase()] ?? .5;
|
|
5842
5924
|
}
|
|
5925
|
+
const NETSPARKER_US_DATETIME = /^\d{2}\/\d{2}\/\d{4} \d{1,2}:\d{2} (AM|PM)$/;
|
|
5926
|
+
function parseNetsparkerTimestamp(s) {
|
|
5927
|
+
const trimmed = s.trim();
|
|
5928
|
+
if (NETSPARKER_US_DATETIME.test(trimmed)) {
|
|
5929
|
+
const d = /* @__PURE__ */ new Date(`${trimmed} GMT`);
|
|
5930
|
+
if (!isNaN(d.getTime())) return d;
|
|
5931
|
+
}
|
|
5932
|
+
return parseTimestamp(s);
|
|
5933
|
+
}
|
|
5843
5934
|
function formatCodeDesc(request) {
|
|
5844
5935
|
const parts = [];
|
|
5845
5936
|
parts.push(`http-request : ${request?.content ?? ""}`);
|
|
@@ -5921,7 +6012,7 @@ function buildRequirement$3(vuln, initiated) {
|
|
|
5921
6012
|
});
|
|
5922
6013
|
const codeDesc = formatCodeDesc(vuln["http-request"]);
|
|
5923
6014
|
const message = formatMessage$1(vuln["http-response"]);
|
|
5924
|
-
const startTime = initiated ?
|
|
6015
|
+
const startTime = (initiated ? parseNetsparkerTimestamp(initiated) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
5925
6016
|
const results = [{
|
|
5926
6017
|
status: ResultStatus.Failed,
|
|
5927
6018
|
codeDesc,
|
|
@@ -5967,8 +6058,7 @@ async function convertNetsparkerToHdf(input) {
|
|
|
5967
6058
|
const targetName = target.url ?? "Unknown";
|
|
5968
6059
|
const requirements = limitedVulns.map((vuln) => buildRequirement$3(vuln, initiated));
|
|
5969
6060
|
if (requirements.length === 0) {
|
|
5970
|
-
const
|
|
5971
|
-
const startTime = isNaN(initiatedDate.getTime()) ? /* @__PURE__ */ new Date() : initiatedDate;
|
|
6061
|
+
const startTime = (initiated ? parseNetsparkerTimestamp(initiated) : null) ?? /* @__PURE__ */ new Date();
|
|
5972
6062
|
requirements.push(buildNoFindingsRequirement("netsparker-no-findings", `${toolName} scanned ${targetName} and reported zero findings.`, startTime));
|
|
5973
6063
|
}
|
|
5974
6064
|
return buildHdfResults({
|
|
@@ -6075,7 +6165,7 @@ function buildRequirement$2(ruleID, finding, startTime) {
|
|
|
6075
6165
|
});
|
|
6076
6166
|
const resultObj = createResult(getStatus$1(finding.checked_items, finding.flagged_items), getMessage(finding.checked_items, finding.flagged_items, finding.items), {
|
|
6077
6167
|
codeDesc: finding.description,
|
|
6078
|
-
startTime: startTime ?
|
|
6168
|
+
startTime: (startTime ? parseTimestamp(startTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
6079
6169
|
});
|
|
6080
6170
|
const req = createRequirement(ruleID, finding.description, descriptions, getImpact$1(finding.level), [resultObj], { tags });
|
|
6081
6171
|
const controlType = deriveControlTypeFromTags(nist);
|
|
@@ -6120,7 +6210,7 @@ async function convertScoutsuiteToHdf(input) {
|
|
|
6120
6210
|
provider: report.provider_code ?? report.provider_name
|
|
6121
6211
|
}
|
|
6122
6212
|
}],
|
|
6123
|
-
timestamp: report.last_run.time ?
|
|
6213
|
+
timestamp: report.last_run.time ? parseTimestamp(report.last_run.time) ?? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date()
|
|
6124
6214
|
});
|
|
6125
6215
|
}
|
|
6126
6216
|
//#endregion
|
|
@@ -6217,7 +6307,7 @@ function buildRequirementFromResult(result, filename) {
|
|
|
6217
6307
|
}];
|
|
6218
6308
|
const scannerName = result.response.service_name;
|
|
6219
6309
|
const startTimeStr = result.response.milestones?.service_started ?? "";
|
|
6220
|
-
const startTime = startTimeStr ?
|
|
6310
|
+
const startTime = (startTimeStr ? parseTimestamp(startTimeStr) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
6221
6311
|
const score = result.result.score;
|
|
6222
6312
|
const status = determineStatus(score);
|
|
6223
6313
|
let results;
|
|
@@ -6305,7 +6395,7 @@ async function convertConveyorToHdf(input) {
|
|
|
6305
6395
|
/** Attribute prefix used by fast-xml-parser — all XML attributes are accessed via `@_name`. */
|
|
6306
6396
|
const A = "@_";
|
|
6307
6397
|
/** Veracode severity level (0-5) to HDF impact mapping. */
|
|
6308
|
-
const IMPACT_MAPPING$2 = new Map([
|
|
6398
|
+
const IMPACT_MAPPING$2 = /* @__PURE__ */ new Map([
|
|
6309
6399
|
["5", .9],
|
|
6310
6400
|
["4", .7],
|
|
6311
6401
|
["3", .5],
|
|
@@ -6327,9 +6417,7 @@ function decodeXmlEntities(s) {
|
|
|
6327
6417
|
/** Parse Veracode timestamp format ("2021-12-29 22:16:36 UTC") to Date. */
|
|
6328
6418
|
function parseVeracodeTimestamp(ts) {
|
|
6329
6419
|
if (!ts) return void 0;
|
|
6330
|
-
|
|
6331
|
-
const d = new Date(decoded.replace(" UTC", "Z").replace(" ", "T"));
|
|
6332
|
-
return isNaN(d.getTime()) ? void 0 : d;
|
|
6420
|
+
return parseTimestamp(decodeXmlEntities(ts).replace(" UTC", "Z").replace(" ", "T")) ?? void 0;
|
|
6333
6421
|
}
|
|
6334
6422
|
/** Format description paragraphs into text. */
|
|
6335
6423
|
function formatDesc(desc) {
|
|
@@ -6647,7 +6735,7 @@ function buildRequirement$1(cs, profiles, createdDateTime) {
|
|
|
6647
6735
|
});
|
|
6648
6736
|
}
|
|
6649
6737
|
const codeDesc = cs.implementationStatus || "No implementation status provided";
|
|
6650
|
-
const startTime = createdDateTime ?
|
|
6738
|
+
const startTime = (createdDateTime ? parseTimestamp(createdDateTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
6651
6739
|
const req = createRequirement(id, title, descriptions, impact, [createResult(status, void 0, {
|
|
6652
6740
|
codeDesc,
|
|
6653
6741
|
...startTime ? { startTime } : {}
|
|
@@ -6829,7 +6917,7 @@ function extractSubscriptionID(resourcePath) {
|
|
|
6829
6917
|
/**
|
|
6830
6918
|
* Converts a group of assessments sharing an assessment ID into one EvaluatedRequirement.
|
|
6831
6919
|
*/
|
|
6832
|
-
function buildRequirement(assessmentID, assessments) {
|
|
6920
|
+
function buildRequirement(assessmentID, assessments, scanTime) {
|
|
6833
6921
|
const rep = assessments[0];
|
|
6834
6922
|
const meta = rep.properties.metadata;
|
|
6835
6923
|
const impact = IMPACT_MAPPING$1[meta.severity.toLowerCase()] ?? .5;
|
|
@@ -6850,7 +6938,7 @@ function buildRequirement(assessmentID, assessments) {
|
|
|
6850
6938
|
label: "fix",
|
|
6851
6939
|
data: meta.remediationDescription
|
|
6852
6940
|
});
|
|
6853
|
-
const results = assessments.map(buildResultFromAssessment);
|
|
6941
|
+
const results = assessments.map((a) => buildResultFromAssessment(a, scanTime));
|
|
6854
6942
|
const req = createRequirement(assessmentID, rep.properties.displayName, descriptions, impact, results, { tags });
|
|
6855
6943
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
6856
6944
|
return req;
|
|
@@ -6858,10 +6946,13 @@ function buildRequirement(assessmentID, assessments) {
|
|
|
6858
6946
|
/**
|
|
6859
6947
|
* Converts a single assessment into an HDF RequirementResult.
|
|
6860
6948
|
*/
|
|
6861
|
-
function buildResultFromAssessment(a) {
|
|
6949
|
+
function buildResultFromAssessment(a, scanTime) {
|
|
6862
6950
|
const status = mapStatus(a.properties.status.code);
|
|
6863
6951
|
const codeDesc = `Resource: ${a.properties.resourceDetails.id}`;
|
|
6864
|
-
return createResult(status, a.properties.status.description || a.properties.status.cause || void 0, {
|
|
6952
|
+
return createResult(status, a.properties.status.description || a.properties.status.cause || void 0, {
|
|
6953
|
+
codeDesc,
|
|
6954
|
+
startTime: scanTime
|
|
6955
|
+
});
|
|
6865
6956
|
}
|
|
6866
6957
|
/**
|
|
6867
6958
|
* Converts Microsoft Defender for Cloud assessment output to HDF format.
|
|
@@ -6871,6 +6962,7 @@ function buildResultFromAssessment(a) {
|
|
|
6871
6962
|
*/
|
|
6872
6963
|
async function convertMsftDefenderCloudToHdf(input) {
|
|
6873
6964
|
validateInputSize(input, "msft-defender-cloud");
|
|
6965
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
6874
6966
|
const resultsChecksum = await inputChecksum(input);
|
|
6875
6967
|
const raw = parseJSON(input);
|
|
6876
6968
|
if (!raw || typeof raw !== "object") throw new Error("Invalid Defender for Cloud structure: not a valid JSON object");
|
|
@@ -6885,11 +6977,11 @@ async function convertMsftDefenderCloudToHdf(input) {
|
|
|
6885
6977
|
else groups.set(a.name, [a]);
|
|
6886
6978
|
}
|
|
6887
6979
|
const requirements = [];
|
|
6888
|
-
for (const [assessmentID, assessments] of groups) requirements.push(buildRequirement(assessmentID, assessments));
|
|
6980
|
+
for (const [assessmentID, assessments] of groups) requirements.push(buildRequirement(assessmentID, assessments, scanTime));
|
|
6889
6981
|
const subscriptionID = limitedAssessments.length > 0 ? extractSubscriptionID(limitedAssessments[0].id) : "";
|
|
6890
6982
|
if (requirements.length === 0) {
|
|
6891
6983
|
const targetName = subscriptionID || "Unknown";
|
|
6892
|
-
requirements.push(buildNoFindingsRequirement("msft-defender-cloud-no-findings", `Microsoft Defender for Cloud scanned ${targetName} and reported zero findings.`,
|
|
6984
|
+
requirements.push(buildNoFindingsRequirement("msft-defender-cloud-no-findings", `Microsoft Defender for Cloud scanned ${targetName} and reported zero findings.`, scanTime));
|
|
6893
6985
|
}
|
|
6894
6986
|
const baseline = createMinimalBaseline("Microsoft Defender for Cloud Assessments", requirements, { resultsChecksum });
|
|
6895
6987
|
const components = [];
|
|
@@ -6910,7 +7002,7 @@ async function convertMsftDefenderCloudToHdf(input) {
|
|
|
6910
7002
|
toolFormat: "JSON",
|
|
6911
7003
|
baselines: [baseline],
|
|
6912
7004
|
components,
|
|
6913
|
-
timestamp:
|
|
7005
|
+
timestamp: scanTime
|
|
6914
7006
|
});
|
|
6915
7007
|
}
|
|
6916
7008
|
//#endregion
|
|
@@ -7007,13 +7099,26 @@ function buildTags$1(alert) {
|
|
|
7007
7099
|
return tags;
|
|
7008
7100
|
}
|
|
7009
7101
|
/**
|
|
7102
|
+
* Resolves a clean per-finding source timestamp, falling back to the conversion time.
|
|
7103
|
+
*/
|
|
7104
|
+
function resolveStartTime(alert, scanTime) {
|
|
7105
|
+
const first = alert.firstActivityDateTime ? parseTimestamp(alert.firstActivityDateTime) : null;
|
|
7106
|
+
if (first) return first;
|
|
7107
|
+
const created = alert.createdDateTime ? parseTimestamp(alert.createdDateTime) : null;
|
|
7108
|
+
if (created) return created;
|
|
7109
|
+
return scanTime;
|
|
7110
|
+
}
|
|
7111
|
+
/**
|
|
7010
7112
|
* Converts a single MDE alert to an HDF EvaluatedRequirement.
|
|
7011
7113
|
*/
|
|
7012
|
-
function alertToRequirement(alert) {
|
|
7114
|
+
function alertToRequirement(alert, scanTime) {
|
|
7013
7115
|
const impact = severityToImpact$1(alert.severity);
|
|
7014
7116
|
const status = statusToResultStatus(alert.status, alert.classification);
|
|
7015
7117
|
const codeDesc = formatEvidence(alert.evidence ?? []);
|
|
7016
|
-
const results = [createResult(status, formatMessage(alert), {
|
|
7118
|
+
const results = [createResult(status, formatMessage(alert), {
|
|
7119
|
+
codeDesc,
|
|
7120
|
+
startTime: resolveStartTime(alert, scanTime)
|
|
7121
|
+
})];
|
|
7017
7122
|
const descriptions = [{
|
|
7018
7123
|
label: "default",
|
|
7019
7124
|
data: alert.description
|
|
@@ -7043,10 +7148,11 @@ async function convertMsftDefenderEndpointToHdf(input) {
|
|
|
7043
7148
|
const response = parseJSON(input);
|
|
7044
7149
|
if (!response || typeof response !== "object") throw new Error("Invalid Microsoft Defender for Endpoint structure: not a valid JSON object");
|
|
7045
7150
|
if (!Array.isArray(response.value)) throw new Error("Invalid Microsoft Defender for Endpoint structure: missing or invalid value array");
|
|
7151
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
7046
7152
|
const { items: limitedAlerts, truncated } = limitArray(response.value);
|
|
7047
7153
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
7048
7154
|
if (truncated) console.warn(`WARNING: Input truncated at ${limitedAlerts.length} alert items (original: ${response.value.length})`);
|
|
7049
|
-
const requirements = limitedAlerts.map(alertToRequirement);
|
|
7155
|
+
const requirements = limitedAlerts.map((alert) => alertToRequirement(alert, scanTime));
|
|
7050
7156
|
const seenTargets = /* @__PURE__ */ new Set();
|
|
7051
7157
|
const components = [];
|
|
7052
7158
|
for (const alert of limitedAlerts) {
|
|
@@ -7056,14 +7162,14 @@ async function convertMsftDefenderEndpointToHdf(input) {
|
|
|
7056
7162
|
components.push(target);
|
|
7057
7163
|
}
|
|
7058
7164
|
}
|
|
7059
|
-
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("msft-defender-endpoint-no-findings", "Microsoft Defender for Endpoint scanned the tenant and reported zero findings.",
|
|
7165
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("msft-defender-endpoint-no-findings", "Microsoft Defender for Endpoint scanned the tenant and reported zero findings.", scanTime));
|
|
7060
7166
|
return buildHdfResults({
|
|
7061
7167
|
generatorName: "msft-defender-endpoint-to-hdf",
|
|
7062
7168
|
converterVersion: "1.0.0",
|
|
7063
7169
|
toolName: "Microsoft Defender for Endpoint",
|
|
7064
7170
|
baselines: [createMinimalBaseline("Microsoft Defender for Endpoint Scan", requirements, { resultsChecksum })],
|
|
7065
7171
|
components,
|
|
7066
|
-
timestamp:
|
|
7172
|
+
timestamp: scanTime
|
|
7067
7173
|
});
|
|
7068
7174
|
}
|
|
7069
7175
|
//#endregion
|
|
@@ -7097,7 +7203,7 @@ function wrap(value) {
|
|
|
7097
7203
|
return { "#text": value };
|
|
7098
7204
|
}
|
|
7099
7205
|
/** Map HDF impact (0.0-1.0) to XCCDF severity string. */
|
|
7100
|
-
function impactToSeverity$
|
|
7206
|
+
function impactToSeverity$2(impact) {
|
|
7101
7207
|
if (impact >= .7) return "high";
|
|
7102
7208
|
if (impact >= .4) return "medium";
|
|
7103
7209
|
if (impact >= .1) return "low";
|
|
@@ -7151,7 +7257,7 @@ function buildRuleObj(req) {
|
|
|
7151
7257
|
const ruleId = sanitizeXccdfId("xccdf_hdf_rule_" + req.id + "_rule");
|
|
7152
7258
|
const rule = {
|
|
7153
7259
|
[`${ATTR}id`]: ruleId,
|
|
7154
|
-
[`${ATTR}severity`]: impactToSeverity$
|
|
7260
|
+
[`${ATTR}severity`]: impactToSeverity$2(req.impact),
|
|
7155
7261
|
[`${ATTR}selected`]: "true",
|
|
7156
7262
|
title: wrap(req.title ?? req.id)
|
|
7157
7263
|
};
|
|
@@ -7341,7 +7447,7 @@ function nistTagToControlId(tag) {
|
|
|
7341
7447
|
* Converts a 0.0-1.0 impact value to an OSCAL severity string.
|
|
7342
7448
|
* This is the reverse of extractRiskSeverity.
|
|
7343
7449
|
*/
|
|
7344
|
-
function impactToSeverity(impact) {
|
|
7450
|
+
function impactToSeverity$1(impact) {
|
|
7345
7451
|
if (impact >= .9) return "critical";
|
|
7346
7452
|
if (impact >= .7) return "high";
|
|
7347
7453
|
if (impact >= .4) return "moderate";
|
|
@@ -7496,7 +7602,7 @@ function requirementToFindingSet(req, timestamp) {
|
|
|
7496
7602
|
let risk;
|
|
7497
7603
|
if (req.impact > 0) {
|
|
7498
7604
|
const riskUUID = crypto.randomUUID();
|
|
7499
|
-
const severity = impactToSeverity(req.impact);
|
|
7605
|
+
const severity = impactToSeverity$1(req.impact);
|
|
7500
7606
|
risk = {
|
|
7501
7607
|
uuid: riskUUID,
|
|
7502
7608
|
title: `Risk for ${req.id}`,
|
|
@@ -8614,7 +8720,7 @@ function buildVulnerability(group) {
|
|
|
8614
8720
|
v.flags.push({
|
|
8615
8721
|
label: String(o.justification),
|
|
8616
8722
|
product_ids: pids,
|
|
8617
|
-
date: new Date(o.appliedAt)
|
|
8723
|
+
date: formatTimestampSeconds(new Date(o.appliedAt))
|
|
8618
8724
|
});
|
|
8619
8725
|
}
|
|
8620
8726
|
emitted = true;
|
|
@@ -8678,7 +8784,7 @@ function buildVulnerability(group) {
|
|
|
8678
8784
|
return v;
|
|
8679
8785
|
}
|
|
8680
8786
|
function buildDocument(amendments, converterVersion) {
|
|
8681
|
-
const now = (/* @__PURE__ */ new Date())
|
|
8787
|
+
const now = formatTimestampSeconds(/* @__PURE__ */ new Date());
|
|
8682
8788
|
const publisherName = amendments.appliedBy?.identifier || "HDF Amendments Export";
|
|
8683
8789
|
const trackingId = amendments.amendmentId || "HDF-VEX-EXPORT";
|
|
8684
8790
|
const docVersion = amendments.version || "1";
|
|
@@ -8758,7 +8864,7 @@ async function convertHdfToOpenVex(input, converterVersion) {
|
|
|
8758
8864
|
"@id": await buildDocumentID(input, amendments),
|
|
8759
8865
|
author,
|
|
8760
8866
|
role,
|
|
8761
|
-
timestamp: (earliest ?? /* @__PURE__ */ new Date())
|
|
8867
|
+
timestamp: formatTimestampSeconds(earliest ?? /* @__PURE__ */ new Date()),
|
|
8762
8868
|
version: 1,
|
|
8763
8869
|
statements
|
|
8764
8870
|
};
|
|
@@ -8775,7 +8881,7 @@ function overrideToStatement(o) {
|
|
|
8775
8881
|
"@id": `https://nvd.nist.gov/vuln/detail/${o.requirementId}`
|
|
8776
8882
|
},
|
|
8777
8883
|
status: String(canonical),
|
|
8778
|
-
timestamp: new Date(o.appliedAt)
|
|
8884
|
+
timestamp: formatTimestampSeconds(new Date(o.appliedAt)),
|
|
8779
8885
|
products: productsFor(o)
|
|
8780
8886
|
};
|
|
8781
8887
|
if (canonical === VexStatus.NotAffected) {
|
|
@@ -8942,7 +9048,7 @@ function allMilestonesCompleted(o) {
|
|
|
8942
9048
|
}
|
|
8943
9049
|
function buildMetadata(a, docTime, converterVersion) {
|
|
8944
9050
|
const metadata = {
|
|
8945
|
-
timestamp: docTime
|
|
9051
|
+
timestamp: formatTimestampSeconds(docTime),
|
|
8946
9052
|
tools: [{
|
|
8947
9053
|
vendor: "mitre",
|
|
8948
9054
|
name: "hdf-to-cyclonedx-vex",
|
|
@@ -9655,7 +9761,7 @@ async function convertOscalPoamToHdf(input) {
|
|
|
9655
9761
|
for (const item of poam["poam-items"]) overrides.push(poamItemToOverride(item, riskMap, poam));
|
|
9656
9762
|
const systemRef = poam["import-ssp"]?.href || void 0;
|
|
9657
9763
|
const appliedBy = extractAppliedBy(poam.metadata);
|
|
9658
|
-
|
|
9764
|
+
return serializeHdf({
|
|
9659
9765
|
name: toKebabCase(poam.metadata.title, "oscal-poam"),
|
|
9660
9766
|
overrides,
|
|
9661
9767
|
integrity,
|
|
@@ -9666,8 +9772,7 @@ async function convertOscalPoamToHdf(input) {
|
|
|
9666
9772
|
name: "oscal-poam-to-hdf",
|
|
9667
9773
|
version: "1.0.0"
|
|
9668
9774
|
}
|
|
9669
|
-
};
|
|
9670
|
-
return JSON.stringify(amendments, null, 2);
|
|
9775
|
+
});
|
|
9671
9776
|
}
|
|
9672
9777
|
function buildRiskMap$1(risks) {
|
|
9673
9778
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -9740,8 +9845,8 @@ function poamItemAppliedBy(poam) {
|
|
|
9740
9845
|
}
|
|
9741
9846
|
function poamItemAppliedAt(poam) {
|
|
9742
9847
|
if (poam.metadata["last-modified"]) {
|
|
9743
|
-
const t =
|
|
9744
|
-
if (
|
|
9848
|
+
const t = parseTimestamp(String(poam.metadata["last-modified"]));
|
|
9849
|
+
if (t) return t;
|
|
9745
9850
|
}
|
|
9746
9851
|
return /* @__PURE__ */ new Date();
|
|
9747
9852
|
}
|
|
@@ -9799,18 +9904,23 @@ async function convertOscalSarToHdf(input) {
|
|
|
9799
9904
|
if (!doc["assessment-results"]) throw new Error("oscal-assessment-results: input is not an assessment-results document (root key is not 'assessment-results')");
|
|
9800
9905
|
const sar = doc["assessment-results"];
|
|
9801
9906
|
const meta = extractMetadata(sar.metadata);
|
|
9907
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
9802
9908
|
const baselines = [];
|
|
9803
9909
|
for (const result of sar.results) {
|
|
9804
|
-
|
|
9910
|
+
if (!result.findings || result.findings.length === 0) {
|
|
9911
|
+
console.warn(`WARNING: Skipping assessment result "${result.title || result.uuid}": no findings (empty result set)`);
|
|
9912
|
+
continue;
|
|
9913
|
+
}
|
|
9914
|
+
const baseline = await resultToEvaluatedBaseline(result, sar, input, scanTime);
|
|
9805
9915
|
baselines.push(baseline);
|
|
9806
9916
|
}
|
|
9807
9917
|
const planRef = sar["import-ap"]?.href || void 0;
|
|
9808
9918
|
let timestamp;
|
|
9809
9919
|
if (meta.lastModified) {
|
|
9810
|
-
const t =
|
|
9811
|
-
if (
|
|
9920
|
+
const t = parseTimestamp(meta.lastModified);
|
|
9921
|
+
if (t) timestamp = t;
|
|
9812
9922
|
}
|
|
9813
|
-
return
|
|
9923
|
+
return serializeHdf({
|
|
9814
9924
|
baselines,
|
|
9815
9925
|
generator: {
|
|
9816
9926
|
name: "oscal-assessment-results-to-hdf",
|
|
@@ -9822,9 +9932,9 @@ async function convertOscalSarToHdf(input) {
|
|
|
9822
9932
|
},
|
|
9823
9933
|
timestamp: timestamp ?? /* @__PURE__ */ new Date(),
|
|
9824
9934
|
planRef
|
|
9825
|
-
}
|
|
9935
|
+
});
|
|
9826
9936
|
}
|
|
9827
|
-
async function resultToEvaluatedBaseline(result, sar, rawInput) {
|
|
9937
|
+
async function resultToEvaluatedBaseline(result, sar, rawInput, scanTime) {
|
|
9828
9938
|
const checksum = await inputChecksum(rawInput);
|
|
9829
9939
|
const obsMap = buildObservationMap(result.observations ?? []);
|
|
9830
9940
|
const riskMap = buildRiskMap(result.risks ?? []);
|
|
@@ -9841,7 +9951,7 @@ async function resultToEvaluatedBaseline(result, sar, rawInput) {
|
|
|
9841
9951
|
}
|
|
9842
9952
|
const requirements = [];
|
|
9843
9953
|
for (const controlId of controlOrder) {
|
|
9844
|
-
const req = findingsToEvaluatedRequirement(controlId, controlMap.get(controlId), obsMap, riskMap, result);
|
|
9954
|
+
const req = findingsToEvaluatedRequirement(controlId, controlMap.get(controlId), obsMap, riskMap, result, scanTime);
|
|
9845
9955
|
requirements.push(req);
|
|
9846
9956
|
}
|
|
9847
9957
|
const baseline = createMinimalBaseline(sarBaselineName(result, sar), requirements, {
|
|
@@ -9853,26 +9963,26 @@ async function resultToEvaluatedBaseline(result, sar, rawInput) {
|
|
|
9853
9963
|
if (result.description) baseline.description = result.description;
|
|
9854
9964
|
return baseline;
|
|
9855
9965
|
}
|
|
9856
|
-
function findingsToEvaluatedRequirement(controlId, findings, obsMap, riskMap, result) {
|
|
9966
|
+
function findingsToEvaluatedRequirement(controlId, findings, obsMap, riskMap, result, scanTime) {
|
|
9857
9967
|
const nistTag = controlIdToNistTag(controlId);
|
|
9858
9968
|
const title = findings[0].title || nistTag;
|
|
9859
9969
|
const impact = sarFindingsImpact(findings, riskMap);
|
|
9860
9970
|
const descriptions = sarBuildDescriptions(findings, obsMap);
|
|
9861
9971
|
const results = [];
|
|
9862
|
-
for (const f of findings) results.push(findingToRequirementResult(f, obsMap, riskMap, result));
|
|
9972
|
+
for (const f of findings) results.push(findingToRequirementResult(f, obsMap, riskMap, result, scanTime));
|
|
9863
9973
|
const req = createRequirement(nistTag, title, descriptions, impact, results, { tags: { nist: [nistTag] } });
|
|
9864
9974
|
const controlType = deriveControlTypeFromTags([nistTag]);
|
|
9865
9975
|
if (controlType !== void 0) req.controlType = controlType;
|
|
9866
9976
|
return req;
|
|
9867
9977
|
}
|
|
9868
|
-
function findingToRequirementResult(f, obsMap, riskMap, result) {
|
|
9978
|
+
function findingToRequirementResult(f, obsMap, riskMap, result, scanTime) {
|
|
9869
9979
|
const status = mapFindingStatus(f);
|
|
9870
9980
|
const codeDesc = buildCodeDesc(f, obsMap);
|
|
9871
9981
|
const message = buildRiskMessage(f, riskMap);
|
|
9872
9982
|
const startTime = parseResultStartTime(result);
|
|
9873
9983
|
return createResult(status, message || void 0, {
|
|
9874
9984
|
codeDesc,
|
|
9875
|
-
startTime: startTime.getTime() > 0 ? startTime :
|
|
9985
|
+
startTime: startTime.getTime() > 0 ? startTime : scanTime
|
|
9876
9986
|
});
|
|
9877
9987
|
}
|
|
9878
9988
|
function extractControlIdFromFinding(f) {
|
|
@@ -9967,8 +10077,8 @@ function buildRiskMap(risks) {
|
|
|
9967
10077
|
}
|
|
9968
10078
|
function parseResultStartTime(result) {
|
|
9969
10079
|
if (result.start) {
|
|
9970
|
-
const t =
|
|
9971
|
-
if (
|
|
10080
|
+
const t = parseTimestamp(String(result.start));
|
|
10081
|
+
if (t) return t;
|
|
9972
10082
|
}
|
|
9973
10083
|
return /* @__PURE__ */ new Date(0);
|
|
9974
10084
|
}
|