@mitre/hdf-converters 3.4.4 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as detectConverter, t as registerAllFingerprints } from "./register-all-WRR-vAZf.js";
2
2
  import { flattenOverlays } from "@mitre/hdf-parsers";
3
- import { buildCsv, buildXml, cvssScoreToSeverity, formatTimestamp, formatTimestampSeconds, impactToSeverity, normalizeHdfTimestamps, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, severityToImpact, sha256, stripHtml as stripHTML, trimUtcFraction } from "@mitre/hdf-utilities";
4
- import { Applicability, AuthorizationStatus, CVSSSeverity, CategorizationLevel, ControlType, Ecosystem, EvidenceType, HashAlgorithm, IdentityType, Justification, MilestoneStatus, OverrideType, PlanType, ResultStatus, TargetType, VerificationMethodEnum, Version, createDescription, createMinimalBaseline, createRequirement, createResult, severityToImpact as severityToImpact$1 } from "@mitre/hdf-schema";
3
+ import { buildCsv, buildXml, computeCvssScore, computeEffectiveStatus, cvssScoreToSeverity, formatTimestamp, formatTimestampSeconds, impactToSeverity, normalizeHdfTimestamps, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, roundImpact, severityToImpact, sha256, stripHtml as stripHTML, trimUtcFraction, worstStatus } from "@mitre/hdf-utilities";
4
+ import { Applicability, AuthorizationStatus, CVSSSeverity, CategorizationLevel, ControlType, Ecosystem, EvidenceType, HashAlgorithm, IdentityType, Justification, MilestoneStatus, OverrideType, PlanType, ResultStatus, Severity, TargetType, VerificationMethodEnum, Version, createDescription, createMinimalBaseline, createRequirement, createResult, severityToImpact as severityToImpact$1 } from "@mitre/hdf-schema";
5
5
  import { DEFAULT_COMPONENT_MANAGEMENT_NIST_TAGS, DEFAULT_CONFIG_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, getAwsConfigNistControlsBySubstring, getCCINistMappings, getCurrentNistRevision, getCweNistControl, getHipcheckNistControls, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistToCci } from "@mitre/hdf-mappings";
6
6
  //#region shared/typescript/converterutil.ts
7
7
  /**
@@ -150,6 +150,7 @@ const DEFAULT_MAX_INPUT_SIZE = 50 * 1024 * 1024;
150
150
  * @throws Error if input exceeds maxSize
151
151
  */
152
152
  function validateInputSize(input, converterName, maxSize = DEFAULT_MAX_INPUT_SIZE) {
153
+ if (maxSize <= 0) maxSize = DEFAULT_MAX_INPUT_SIZE;
153
154
  if (input.length > maxSize) throw new Error(`${converterName}: input exceeds maximum allowed size of ${maxSize} characters`);
154
155
  }
155
156
  /**
@@ -487,35 +488,15 @@ function normalizeStatus$1(status) {
487
488
  }[status] || "notReviewed";
488
489
  }
489
490
  /**
490
- * Compute effectiveStatus from impact and v2 results.
491
- * Implements InSpec enhanced outcomes precedence:
492
- * impact=0 → notApplicable
493
- * error > failed > passed > notApplicable > notReviewed
494
- *
495
- * See docs/design/status-determination.md for full specification.
491
+ * Compute effectiveStatus from impact and v2 results via the canonical
492
+ * worst-wins ordering in @mitre/hdf-utilities (impact=0 → notApplicable;
493
+ * error > failed > passed > notApplicable > notReviewed).
496
494
  */
497
- function computeEffectiveStatus(impact, results) {
498
- if (impact === 0) return "notApplicable";
499
- if (results.length === 0) return "notReviewed";
500
- let hasFailed = false;
501
- let hasPassed = false;
502
- let hasNotApplicable = false;
503
- for (const r of results) switch (r.status) {
504
- case "error": return "error";
505
- case "failed":
506
- hasFailed = true;
507
- break;
508
- case "passed":
509
- hasPassed = true;
510
- break;
511
- case "notApplicable":
512
- hasNotApplicable = true;
513
- break;
514
- }
515
- if (hasFailed) return "failed";
516
- if (hasPassed) return "passed";
517
- if (hasNotApplicable) return "notApplicable";
518
- return "notReviewed";
495
+ function computeEffectiveStatus$1(impact, results) {
496
+ return computeEffectiveStatus({
497
+ impact,
498
+ resultStatuses: results.map((r) => r.status)
499
+ });
519
500
  }
520
501
  /**
521
502
  * Convert v1.0 result to v2.0 result.
@@ -613,7 +594,7 @@ function convertControl(v1Control) {
613
594
  if (v1Control.source_location !== void 0) v2Req.sourceLocation = v1Control.source_location;
614
595
  if (v1Control.status !== void 0) v2Req.effectiveStatus = normalizeStatus$1(v1Control.status);
615
596
  if (v1Control.results && Array.isArray(v1Control.results)) v2Req.results = v1Control.results.map(convertResult);
616
- if (!v2Req.effectiveStatus) v2Req.effectiveStatus = computeEffectiveStatus(v1Control.impact, v2Req.results ?? []);
597
+ if (!v2Req.effectiveStatus) v2Req.effectiveStatus = computeEffectiveStatus$1(v1Control.impact, v2Req.results ?? []);
617
598
  v2Req.severity = tagSeverityToSeverity(v1Control.tags?.severity) ?? impactToSeverity(v1Control.impact);
618
599
  const rawNist = v1Control.tags?.nist;
619
600
  if (Array.isArray(rawNist)) {
@@ -753,7 +734,47 @@ function convertProfile(v1Profile) {
753
734
  if (v1Profile.depends?.length) v2Baseline.depends = v1Profile.depends.map(convertDependency);
754
735
  return v2Baseline;
755
736
  }
756
- function convertV1ToV2(v1Data) {
737
+ /** Parse the leading major-version integer; -1 when the string has none. */
738
+ function inspecMajor(version) {
739
+ const major = /^(\d+)/.exec(version ?? "")?.[1];
740
+ return major !== void 0 ? parseInt(major, 10) : -1;
741
+ }
742
+ /**
743
+ * Map the source's top-level version to tool metadata. A genuine InSpec
744
+ * exec-json run carries the InSpec CLI version (major >= 2 for every modern
745
+ * release), so those flip the tool to InSpec/exec-json. A legacy HDF v1
746
+ * document with no InSpec provenance (e.g. a bare "1.0.0" format marker) keeps
747
+ * the historical label.
748
+ */
749
+ function toolIdentity(version) {
750
+ if (inspecMajor(version) >= 2) return {
751
+ name: "InSpec",
752
+ version,
753
+ format: "exec-json"
754
+ };
755
+ return { name: "Heimdall Data Format v1" };
756
+ }
757
+ /**
758
+ * The assessment's execution time for the top-level timestamp. An explicit
759
+ * source timestamp wins; otherwise it is the latest (last-observed) result
760
+ * start_time — the assessment's effective as-of instant, the value
761
+ * `hdf events derive` consumes. Returns undefined only when the source carries
762
+ * no usable time; never the wall clock. Canonical trimmed-UTC form.
763
+ */
764
+ function documentTimestamp(v1Data) {
765
+ if (v1Data.timestamp) {
766
+ const t = parseTimestamp(v1Data.timestamp);
767
+ if (t) return formatTimestamp(t);
768
+ }
769
+ let latest;
770
+ for (const profile of v1Data.profiles || []) for (const control of profile.controls || []) for (const result of control.results || []) {
771
+ if (!result.start_time) continue;
772
+ const t = parseTimestamp(result.start_time);
773
+ if (t && (!latest || t > latest)) latest = t;
774
+ }
775
+ return latest ? formatTimestamp(latest) : void 0;
776
+ }
777
+ function convertV1ToV2(v1Data, converterVersion = "1.0.0") {
757
778
  validateInputSize(JSON.stringify(v1Data), "legacyhdf-to-hdf");
758
779
  const v2 = {
759
780
  baselines: (v1Data.profiles || []).map(convertProfile),
@@ -770,9 +791,13 @@ function convertV1ToV2(v1Data) {
770
791
  }
771
792
  v2.components = [component];
772
793
  }
773
- if (v1Data.generator) v2.generator = v1Data.generator;
774
- v2.tool = { name: "Heimdall Data Format v1" };
775
- if (v1Data.timestamp) v2.timestamp = v1Data.timestamp;
794
+ v2.generator = v1Data.generator ?? {
795
+ name: "legacyhdf-to-hdf",
796
+ version: converterVersion
797
+ };
798
+ v2.tool = toolIdentity(v1Data.version);
799
+ const timestamp = documentTimestamp(v1Data);
800
+ if (timestamp) v2.timestamp = timestamp;
776
801
  const knownV1Fields = /* @__PURE__ */ new Set([
777
802
  "version",
778
803
  "platform",
@@ -801,6 +826,63 @@ function isHDFV1(data) {
801
826
  return typeof obj.version === "string" && Array.isArray(obj.profiles) && typeof obj.platform === "object" && obj.platform !== null;
802
827
  }
803
828
  //#endregion
829
+ //#region shared/typescript/cvss.ts
830
+ /**
831
+ * Returns the schema CVSS Version enum for a vector-string prefix (CVSS:2.0/,
832
+ * CVSS:3.0/, CVSS:4.0/). An absent or unrecognized prefix (including CVSS:3.1/)
833
+ * defaults to 3.1 — the version modern scanners emit most often.
834
+ */
835
+ function cvssVersionFromVector(vector) {
836
+ if (!vector) return Version.The31;
837
+ if (vector.startsWith("CVSS:2.0/")) return Version.The20;
838
+ if (vector.startsWith("CVSS:3.0/")) return Version.The30;
839
+ if (vector.startsWith("CVSS:4.0/")) return Version.The40;
840
+ return Version.The31;
841
+ }
842
+ /**
843
+ * Maps a bare CVSS version number ("2.0"/"3.0"/"3.1"/"4.0") — as emitted in a
844
+ * structured version field rather than a vector prefix — to the schema Version
845
+ * enum. Unrecognized values default to 3.1.
846
+ */
847
+ function cvssVersionFromString(v) {
848
+ switch (v) {
849
+ case "2.0": return Version.The20;
850
+ case "3.0": return Version.The30;
851
+ case "4.0": return Version.The40;
852
+ default: return Version.The31;
853
+ }
854
+ }
855
+ /**
856
+ * Maps a CVSS base/computed score to the schema CVSSSeverity enum via the shared
857
+ * band thresholds in cvssScoreToSeverity. Scores below the low threshold (and
858
+ * non-finite inputs, per cvssScoreToSeverity) map to the "none" band.
859
+ */
860
+ function cvssSeverityFromScore(score) {
861
+ switch (cvssScoreToSeverity(score)) {
862
+ case "critical": return CVSSSeverity.Critical;
863
+ case "high": return CVSSSeverity.High;
864
+ case "medium": return CVSSSeverity.Medium;
865
+ case "low": return CVSSSeverity.Low;
866
+ default: return CVSSSeverity.None;
867
+ }
868
+ }
869
+ /**
870
+ * Assembles a Cvss from the base-metric fields a converter provides. version is
871
+ * always set. baseScore (and its derived baseSeverity) is set only when a finite
872
+ * number is supplied; baseVector and source are set only when non-empty. Consumers
873
+ * needing threat/environmental/computed enrichment set those fields on the result.
874
+ */
875
+ function buildCvss$2(input) {
876
+ const cv = { version: input.version };
877
+ if (typeof input.baseScore === "number" && Number.isFinite(input.baseScore)) {
878
+ cv.baseScore = input.baseScore;
879
+ cv.baseSeverity = cvssSeverityFromScore(input.baseScore);
880
+ }
881
+ if (input.baseVector) cv.baseVector = input.baseVector;
882
+ if (input.source) cv.source = input.source;
883
+ return cv;
884
+ }
885
+ //#endregion
804
886
  //#region converters/asff-to-hdf/typescript/converter.ts
805
887
  /**
806
888
  * AWS Security Finding Format (ASFF) to HDF converter.
@@ -865,14 +947,17 @@ function severityLabelToImpact(label) {
865
947
  /**
866
948
  * findingImpact derives a 0.0–1.0 impact. Suppressed findings are forced to 0.
867
949
  * Security Hub's INFORMATIONAL is up-graded to MEDIUM (Security Hub over-marks
868
- * findings INFORMATIONAL without context).
950
+ * findings INFORMATIONAL without context). FindingProviderFields.Severity — the
951
+ * provider's own rating — is preferred over the top-level Severity, which
952
+ * Security Hub may overwrite on ingest.
869
953
  */
870
954
  function findingImpact(f) {
871
955
  if (f.Workflow?.Status === "SUPPRESSED") return 0;
872
- let label = f.Severity?.Label;
956
+ const sev = f.FindingProviderFields?.Severity?.Label ? f.FindingProviderFields.Severity : f.Severity;
957
+ let label = sev?.Label;
873
958
  if (isSecurityHub(f) && (label ?? "").toUpperCase() === "INFORMATIONAL") label = "MEDIUM";
874
959
  if (label) return severityLabelToImpact(label);
875
- if (typeof f.Severity?.Normalized === "number") return f.Severity.Normalized / 100;
960
+ if (typeof sev?.Normalized === "number") return sev.Normalized / 100;
876
961
  return 0;
877
962
  }
878
963
  function productArnParts(arn) {
@@ -1037,6 +1122,37 @@ function vulnerabilitySummary(f) {
1037
1122
  return parts.join("; ");
1038
1123
  }).join("\n");
1039
1124
  }
1125
+ /**
1126
+ * Assembles requirement.cvss[] from a finding's ASFF Vulnerabilities[]. Each Cvss
1127
+ * entry carrying a BaseScore becomes one structured Cvss via the shared builder.
1128
+ * ASFF's Cvss shape has no vector string, so only version, score, and source map;
1129
+ * an entry without a BaseScore contributes nothing.
1130
+ */
1131
+ function vulnCvss(f) {
1132
+ const out = [];
1133
+ for (const v of f.Vulnerabilities ?? []) for (const c of v.Cvss ?? []) {
1134
+ if (typeof c.BaseScore !== "number") continue;
1135
+ out.push(buildCvss$2({
1136
+ version: cvssVersionFromString(c.Version),
1137
+ baseScore: c.BaseScore,
1138
+ source: c.Source
1139
+ }));
1140
+ }
1141
+ return out;
1142
+ }
1143
+ /**
1144
+ * Collects the CVE ids a finding's Vulnerabilities[] carry (dedup, first-seen
1145
+ * order) for tags.cve. ASFF stores the CVE in Vulnerabilities[].Id.
1146
+ */
1147
+ function vulnCves(f) {
1148
+ const out = [];
1149
+ const seen = /* @__PURE__ */ new Set();
1150
+ for (const v of f.Vulnerabilities ?? []) if (v.Id && !seen.has(v.Id)) {
1151
+ seen.add(v.Id);
1152
+ out.push(v.Id);
1153
+ }
1154
+ return out;
1155
+ }
1040
1156
  function trivyMessage(f) {
1041
1157
  const o = f.Resources?.[0]?.Details?.Other;
1042
1158
  if (!o) return "";
@@ -1076,10 +1192,15 @@ function buildRequirement$17(id, group) {
1076
1192
  });
1077
1193
  const nist = nistTags$1(group);
1078
1194
  const tags = nist.length > 0 ? buildNistCciTags(nist, nistToCci(nist)) : {};
1195
+ const cves = vulnCves(primary);
1196
+ if (cves.length > 0) tags.cve = cves;
1079
1197
  const results = group.map(buildResult$2);
1080
1198
  const req = createRequirement(id, primary.Title ?? "", descriptions, impact, results, { tags });
1081
1199
  const controlType = deriveControlTypeFromTags(nist);
1082
1200
  if (controlType !== void 0) req.controlType = controlType;
1201
+ const cvss = vulnCvss(primary);
1202
+ if (cvss.length > 0) req.cvss = cvss;
1203
+ req.code = JSON.stringify(primary, null, 2);
1083
1204
  const refs = [];
1084
1205
  if (primary.SourceUrl) refs.push({ url: primary.SourceUrl });
1085
1206
  const seen = /* @__PURE__ */ new Set();
@@ -1135,10 +1256,7 @@ async function convertAsffToHdf(input, converterVersion = "1.0.0") {
1135
1256
  name: "asff-to-hdf",
1136
1257
  version: converterVersion
1137
1258
  },
1138
- tool: {
1139
- name: "AWS Security Finding Format",
1140
- format: "JSON"
1141
- },
1259
+ tool: { name: "AWS Security Finding Format" },
1142
1260
  timestamp: /* @__PURE__ */ new Date()
1143
1261
  };
1144
1262
  if (accounts.length > 0) {
@@ -1240,6 +1358,8 @@ function convertResultGroup(ruleId, rule, sarifResults) {
1240
1358
  const controlType = deriveControlTypeFromTags(nistControls);
1241
1359
  if (controlType !== void 0) req.controlType = controlType;
1242
1360
  req.verificationMethod = VerificationMethodEnum.Automated;
1361
+ const snippet = (firstResult.locations ?? []).map((loc) => loc.physicalLocation?.region?.snippet?.text).find((text) => Boolean(text));
1362
+ if (snippet) req.code = snippet;
1243
1363
  const seenKeys = /* @__PURE__ */ new Set();
1244
1364
  const packages = [];
1245
1365
  for (const sr of sarifResults) {
@@ -1494,7 +1614,6 @@ async function convertJunitToHdf(input, converterVersion = "1.0.0") {
1494
1614
  generatorName: "junit-to-hdf",
1495
1615
  converterVersion,
1496
1616
  toolName: "JUnit XML",
1497
- toolFormat: "XML",
1498
1617
  baselines: [createMinimalBaseline(name, requirements, { resultsChecksum: await inputChecksum(input) })],
1499
1618
  components: [{
1500
1619
  type: TargetType.Application,
@@ -1546,7 +1665,7 @@ function testCaseToRequirement(tc, scanTime) {
1546
1665
  const { status, message } = resolveStatus(tc);
1547
1666
  const result = {
1548
1667
  status,
1549
- codeDesc: buildCodeDesc$9(tc),
1668
+ codeDesc: buildCodeDesc$10(tc),
1550
1669
  startTime: scanTime
1551
1670
  };
1552
1671
  if (message !== void 0) result.message = message;
@@ -1603,7 +1722,7 @@ function buildFailureMessage(message, typeName, body) {
1603
1722
  if (body) result += "\n" + decodeXmlEntities$5(body);
1604
1723
  return result;
1605
1724
  }
1606
- function buildCodeDesc$9(tc) {
1725
+ function buildCodeDesc$10(tc) {
1607
1726
  if (tc.classname) return `${decodeXmlEntities$5(tc.classname)} :: ${decodeXmlEntities$5(tc.name)}`;
1608
1727
  return decodeXmlEntities$5(tc.name);
1609
1728
  }
@@ -1615,6 +1734,25 @@ function noFindingsTarget(baselineName, suites) {
1615
1734
  //#endregion
1616
1735
  //#region converters/xccdf-results-to-hdf/typescript/converter.ts
1617
1736
  /**
1737
+ * Map XCCDF's severity vocabulary (unknown|info|low|medium|high) onto HDF's
1738
+ * (critical|high|medium|low|informational). Casting the raw attribute straight to
1739
+ * Severity emitted schema-invalid documents, since XCCDF "unknown" and "info" are
1740
+ * not HDF severities.
1741
+ *
1742
+ * "unknown" states that severity was not determined. HDF cannot express that, so
1743
+ * emit no severity at all rather than assert "informational", which would silently
1744
+ * downgrade a rule whose severity is merely unstated.
1745
+ */
1746
+ function xccdfSeverityToHdf(severity) {
1747
+ switch (severity.toLowerCase()) {
1748
+ case "high": return Severity.High;
1749
+ case "medium": return Severity.Medium;
1750
+ case "low": return Severity.Low;
1751
+ case "info": return Severity.Informational;
1752
+ default: return;
1753
+ }
1754
+ }
1755
+ /**
1618
1756
  * Walk the Group tree depth-first, collecting every rule at any depth. Rules may
1619
1757
  * sit directly on a Group that also has nested Groups.
1620
1758
  */
@@ -1859,6 +1997,12 @@ function ruleResultToRequirement(rr, ruleIndex, scanTime) {
1859
1997
  label: "default",
1860
1998
  data: stripHTML(extractVulnDiscussion(extractText(ruleDef?.description)))
1861
1999
  }];
2000
+ const check = selectCheck(ruleDef?.check);
2001
+ const checkContent = extractCheckContent(check);
2002
+ if (checkContent) descriptions.push({
2003
+ label: "check",
2004
+ data: stripHTML(checkContent)
2005
+ });
1862
2006
  const fixtext = extractFixtext(ruleDef?.fixtext);
1863
2007
  if (fixtext) descriptions.push({
1864
2008
  label: "fix",
@@ -1875,6 +2019,10 @@ function ruleResultToRequirement(rr, ruleIndex, scanTime) {
1875
2019
  const tags = buildCciNistTags(extractCCIs([...rr.ident ?? [], ...ruleDef?.ident ?? []]));
1876
2020
  const nistTags = tags["nist"];
1877
2021
  const req = createRequirement(id, title, descriptions, impact, [result], { tags });
2022
+ const code = buildCheckCode(check);
2023
+ if (code) req.code = code;
2024
+ const hdfSeverity = xccdfSeverityToHdf(severity);
2025
+ if (hdfSeverity) req.severity = hdfSeverity;
1878
2026
  const controlType = deriveControlTypeFromTags(nistTags);
1879
2027
  if (controlType !== void 0) req.controlType = controlType;
1880
2028
  return req;
@@ -1923,6 +2071,35 @@ function decodeXmlEntities$4(s) {
1923
2071
  return name !== void 0 ? NAMED_ENTITIES$4[name] ?? match : match;
1924
2072
  });
1925
2073
  }
2074
+ /**
2075
+ * Extract plain text from a field that may be a string or {#text: string}.
2076
+ */
2077
+ /**
2078
+ * A rule can carry several <check> elements (SSG emits SCE, OVAL and OCIL for the
2079
+ * same rule). Go decodes <check> into one struct field, so encoding/xml overwrites
2080
+ * it and the last element wins; match that or the two languages disagree.
2081
+ */
2082
+ function checkPriority(system) {
2083
+ const s = system ?? "";
2084
+ if (s.includes("oval.mitre.org")) return 0;
2085
+ if (s.includes("open-scap.org") || s.includes("/SCE")) return 1;
2086
+ if (s.includes("ocil")) return 2;
2087
+ return 3;
2088
+ }
2089
+ function selectCheck(check) {
2090
+ if (check === void 0) return void 0;
2091
+ const checks = Array.isArray(check) ? check : [check];
2092
+ let best;
2093
+ let bestPri = Infinity;
2094
+ for (const c of checks) {
2095
+ const p = checkPriority(c.system);
2096
+ if (p < bestPri) {
2097
+ best = c;
2098
+ bestPri = p;
2099
+ }
2100
+ }
2101
+ return best;
2102
+ }
1926
2103
  function extractText(field) {
1927
2104
  if (field === void 0 || field === null) return "";
1928
2105
  if (typeof field === "string") return decodeXmlEntities$4(field);
@@ -1945,6 +2122,67 @@ function extractFixtext(field) {
1945
2122
  return decodeXmlEntities$4(field["#text"] ?? "");
1946
2123
  }
1947
2124
  /**
2125
+ * Extract check-content text from a check element.
2126
+ */
2127
+ function extractCheckContent(check) {
2128
+ if (!check) return "";
2129
+ const cc = check["check-content"];
2130
+ if (!cc) return "";
2131
+ if (typeof cc === "string") return decodeXmlEntities$4(cc);
2132
+ if (Array.isArray(cc)) {
2133
+ const first = cc[0];
2134
+ if (typeof first === "string") return decodeXmlEntities$4(first);
2135
+ if (first && typeof first === "object" && "#text" in first) return decodeXmlEntities$4(first["#text"] ?? "");
2136
+ return "";
2137
+ }
2138
+ return decodeXmlEntities$4(cc["#text"] ?? "");
2139
+ }
2140
+ /**
2141
+ * Extract the OVAL/SCE check-content-ref (name + href) from a check element.
2142
+ * check-content-ref is not forced into an array, so handle both single and
2143
+ * (rare) multi cases, taking the first.
2144
+ */
2145
+ function extractCheckContentRef(check) {
2146
+ if (!check) return {
2147
+ name: "",
2148
+ href: ""
2149
+ };
2150
+ let ref = check["check-content-ref"];
2151
+ if (Array.isArray(ref)) ref = ref[0];
2152
+ if (!ref || typeof ref !== "object") return {
2153
+ name: "",
2154
+ href: ""
2155
+ };
2156
+ return {
2157
+ name: ref.name ?? "",
2158
+ href: ref.href ?? ""
2159
+ };
2160
+ }
2161
+ /**
2162
+ * Render a rule's selected <check> as the indented-JSON blob carried in
2163
+ * requirement.code — the automated-check logic (system + OVAL/SCE definition
2164
+ * reference + any inline content). Returns '' when the check is empty so the
2165
+ * caller leaves code unset rather than fabricating one. Key order and the
2166
+ * escape-free JSON.stringify mirror the Go buildCheckCode for byte parity.
2167
+ */
2168
+ function buildCheckCode(check) {
2169
+ if (!check) return "";
2170
+ const system = check.system ?? "";
2171
+ const ref = extractCheckContentRef(check);
2172
+ const content = extractCheckContent(check).trim();
2173
+ if (!system && !ref.name && !ref.href && !content) return "";
2174
+ const code = {};
2175
+ if (system) code.system = system;
2176
+ if (ref.name || ref.href) {
2177
+ const refObj = {};
2178
+ if (ref.name) refObj.name = ref.name;
2179
+ if (ref.href) refObj.href = ref.href;
2180
+ code.checkContentRef = refObj;
2181
+ }
2182
+ if (content) code.checkContent = content;
2183
+ return JSON.stringify(code, null, 2);
2184
+ }
2185
+ /**
1948
2186
  * Extract the VulnDiscussion text from an XCCDF description that contains
1949
2187
  * embedded XML like `<VulnDiscussion>...</VulnDiscussion>`. If no
1950
2188
  * VulnDiscussion tag is found, returns the original text.
@@ -2759,6 +2997,19 @@ function synthesizePurl(ecosystem, name, version) {
2759
2997
  if (ecosystem === Ecosystem.Generic) return void 0;
2760
2998
  return `pkg:${ecosystem}/${name}@${version}`;
2761
2999
  }
3000
+ /**
3001
+ * Assembles the structured cvss[] entry for a Snyk vulnerability from its
3002
+ * cvssScore (base score) and CVSSv3 (base vector, carrying a CVSS:3.1/ prefix).
3003
+ * Returns [] when the source carries neither so the field is omitted.
3004
+ */
3005
+ function buildSnykCvss(vuln) {
3006
+ if (!vuln.cvssScore && !vuln.CVSSv3) return [];
3007
+ return [buildCvss$2({
3008
+ version: cvssVersionFromVector(vuln.CVSSv3),
3009
+ baseScore: vuln.cvssScore,
3010
+ baseVector: vuln.CVSSv3
3011
+ })];
3012
+ }
2762
3013
  function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
2763
3014
  const rep = vulns[0];
2764
3015
  const cweIDs = rep.identifiers.CWE ?? [];
@@ -2767,8 +3018,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
2767
3018
  nist,
2768
3019
  cci: nistToCci(nist)
2769
3020
  };
2770
- if (cweIDs.length > 0) tags["cweid"] = cweIDs;
2771
- if (rep.identifiers.CVE && rep.identifiers.CVE.length > 0) tags["cveid"] = rep.identifiers.CVE;
3021
+ if (rep.identifiers.CVE && rep.identifiers.CVE.length > 0) tags["cve"] = rep.identifiers.CVE;
2772
3022
  if (rep.identifiers.GHSA && rep.identifiers.GHSA.length > 0) tags["ghsaid"] = rep.identifiers.GHSA;
2773
3023
  const descriptions = [{
2774
3024
  label: "default",
@@ -2786,6 +3036,9 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
2786
3036
  const controlType = deriveControlTypeFromTags(nist);
2787
3037
  if (controlType !== void 0) req.controlType = controlType;
2788
3038
  req.verificationMethod = VerificationMethodEnum.Automated;
3039
+ const cvss = buildSnykCvss(rep);
3040
+ if (cvss.length > 0) req.cvss = cvss;
3041
+ if (cweIDs.length > 0) req.cwe = cweIDs;
2789
3042
  const name = rep.packageName ?? rep.moduleName;
2790
3043
  const version = rep.version;
2791
3044
  if (name && version) {
@@ -2861,7 +3114,6 @@ async function convertSnykToHdf(input, converterVersion = "1.0.0") {
2861
3114
  generatorName: "snyk-to-hdf",
2862
3115
  converterVersion,
2863
3116
  toolName: "Snyk",
2864
- toolFormat: "JSON",
2865
3117
  baselines,
2866
3118
  components: [{
2867
3119
  name: targetName,
@@ -2920,7 +3172,7 @@ function getReferences(vuln, relatedVulns) {
2920
3172
  }
2921
3173
  return Array.from(refs);
2922
3174
  }
2923
- function buildCodeDesc$8(match) {
3175
+ function buildCodeDesc$9(match) {
2924
3176
  const parts = [];
2925
3177
  parts.push(`Package: ${match.artifact.name}@${match.artifact.version}`);
2926
3178
  if (match.artifact.type) parts.push(`Type: ${match.artifact.type}`);
@@ -2934,35 +3186,16 @@ function buildCodeDesc$8(match) {
2934
3186
  }
2935
3187
  return parts.join(" | ");
2936
3188
  }
2937
- function cvssVersionToSchema(v) {
2938
- switch (v) {
2939
- case "2.0": return Version.The20;
2940
- case "3.0": return Version.The30;
2941
- case "4.0": return Version.The40;
2942
- default: return Version.The31;
2943
- }
2944
- }
2945
- function cvssBandSeverity(score) {
2946
- switch (cvssScoreToSeverity(score)) {
2947
- case "critical": return CVSSSeverity.Critical;
2948
- case "high": return CVSSSeverity.High;
2949
- case "medium": return CVSSSeverity.Medium;
2950
- case "low": return CVSSSeverity.Low;
2951
- default: return CVSSSeverity.None;
2952
- }
2953
- }
2954
- function buildCvssEntries$1(vuln) {
3189
+ function buildCvssEntries$5(vuln) {
2955
3190
  if (!vuln.cvss || vuln.cvss.length === 0) return;
2956
3191
  const entries = [];
2957
3192
  for (const c of vuln.cvss) {
2958
- const entry = { version: cvssVersionToSchema(c.version) };
2959
- if (c.vector) entry.baseVector = c.vector;
2960
- const score = c.metrics?.baseScore;
2961
- if (typeof score === "number" && Number.isFinite(score)) {
2962
- entry.baseScore = score;
2963
- entry.baseSeverity = cvssBandSeverity(score);
2964
- }
2965
- if (vuln.id) entry.source = vuln.id;
3193
+ const entry = buildCvss$2({
3194
+ version: cvssVersionFromString(c.version),
3195
+ baseScore: c.metrics?.baseScore,
3196
+ baseVector: c.vector,
3197
+ source: vuln.id
3198
+ });
2966
3199
  if (entry.baseVector === void 0 && entry.baseScore === void 0) continue;
2967
3200
  entries.push(entry);
2968
3201
  }
@@ -2996,10 +3229,10 @@ function buildAffectedPackages(match) {
2996
3229
  if (fix && fix.state === "fixed" && fix.versions && fix.versions.length > 0 && fix.versions[0]) pkg.fixedInVersion = fix.versions[0];
2997
3230
  return [pkg];
2998
3231
  }
2999
- const CWE_ID_PATTERN = /^CWE-[1-9]\d*$/;
3232
+ const CWE_ID_PATTERN$1 = /^CWE-[1-9]\d*$/;
3000
3233
  function extractCwe(raw) {
3001
3234
  if (!raw || raw.length === 0) return void 0;
3002
- const out = raw.filter((c) => CWE_ID_PATTERN.test(c));
3235
+ const out = raw.filter((c) => CWE_ID_PATTERN$1.test(c));
3003
3236
  return out.length === 0 ? void 0 : out;
3004
3237
  }
3005
3238
  function buildEpss$2(entries) {
@@ -3041,7 +3274,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
3041
3274
  const message = messageParts.join(" ");
3042
3275
  const result = {
3043
3276
  status,
3044
- codeDesc: buildCodeDesc$8(match),
3277
+ codeDesc: buildCodeDesc$9(match),
3045
3278
  message,
3046
3279
  startTime
3047
3280
  };
@@ -3050,6 +3283,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
3050
3283
  id: isIgnored ? `Grype-Ignored-Match/${cveId}` : `Grype/${cveId}`,
3051
3284
  title: `Grype found a vulnerability to ${cveId} in ${targetName}`,
3052
3285
  impact,
3286
+ code: JSON.stringify(match, null, 2),
3053
3287
  results: [result],
3054
3288
  tags,
3055
3289
  descriptions: [
@@ -3071,7 +3305,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
3071
3305
  };
3072
3306
  const controlType = deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
3073
3307
  if (controlType !== void 0) requirement.controlType = controlType;
3074
- const cvss = buildCvssEntries$1(vuln);
3308
+ const cvss = buildCvssEntries$5(vuln);
3075
3309
  if (cvss) requirement.cvss = cvss;
3076
3310
  requirement.affectedPackages = buildAffectedPackages(match);
3077
3311
  const cwe = extractCwe(vuln.cwe);
@@ -3179,26 +3413,15 @@ function findingId(f) {
3179
3413
  function cveList(f) {
3180
3414
  return (f.vulnerability_ids ?? []).map((v) => v.vulnerability_id).filter(Boolean);
3181
3415
  }
3182
- function cvssBand(score) {
3183
- switch (cvssScoreToSeverity(score)) {
3184
- case "critical": return CVSSSeverity.Critical;
3185
- case "high": return CVSSSeverity.High;
3186
- case "medium": return CVSSSeverity.Medium;
3187
- case "low": return CVSSSeverity.Low;
3188
- default: return CVSSSeverity.None;
3189
- }
3190
- }
3191
3416
  function buildCvss$1(f) {
3192
3417
  const out = [];
3193
3418
  const add = (version, vector, score) => {
3194
3419
  if ((score === void 0 || score === null) && !vector) return;
3195
- const entry = { version };
3196
- if (score !== void 0 && score !== null) {
3197
- entry.baseScore = score;
3198
- entry.baseSeverity = cvssBand(score);
3199
- }
3200
- if (vector) entry.baseVector = vector;
3201
- out.push(entry);
3420
+ out.push(buildCvss$2({
3421
+ version,
3422
+ baseScore: score,
3423
+ baseVector: vector
3424
+ }));
3202
3425
  };
3203
3426
  add(Version.The31, f.cvssv3, f.cvssv3_score);
3204
3427
  add(Version.The40, f.cvssv4, f.cvssv4_score);
@@ -3241,6 +3464,8 @@ function codeDesc(f) {
3241
3464
  function convertFinding(f) {
3242
3465
  const nist = nistTags(f);
3243
3466
  const tags = buildNistCciTags(nist, nistToCci(nist), triageTags(f));
3467
+ const cves = cveList(f);
3468
+ if (cves.length > 0) tags.cve = cves;
3244
3469
  const result = {
3245
3470
  status: deriveStatus(f),
3246
3471
  codeDesc: codeDesc(f),
@@ -3260,6 +3485,7 @@ function convertFinding(f) {
3260
3485
  const epss = buildEpss$1(f);
3261
3486
  if (epss) req.epss = epss;
3262
3487
  if (f.cwe && f.cwe > 0) req.cwe = [`CWE-${f.cwe}`];
3488
+ req.code = JSON.stringify(f, null, 2);
3263
3489
  const firstRisk = f.accepted_risks?.[0];
3264
3490
  if (f.risk_accepted && firstRisk) {
3265
3491
  req.statusOverrides = [buildWaiverOverride(firstRisk)];
@@ -3468,7 +3694,7 @@ function convertReportItemToRequirement(item, host) {
3468
3694
  if (controlType !== void 0) req.controlType = controlType;
3469
3695
  if (verificationMethod !== void 0) req.verificationMethod = verificationMethod;
3470
3696
  if (!isCompliance) {
3471
- const cvssEntries = buildCvssEntries(item);
3697
+ const cvssEntries = buildCvssEntries$4(item);
3472
3698
  if (cvssEntries.length > 0) req.cvss = cvssEntries;
3473
3699
  const cweIDs = buildCweIDs(item);
3474
3700
  if (cweIDs.length > 0) req.cwe = cweIDs;
@@ -3482,7 +3708,7 @@ function convertReportItemToRequirement(item, host) {
3482
3708
  * the schema models cvss as a multi-entry array (Nessus emits one entry per
3483
3709
  * item; multi-vendor convergence may yield more).
3484
3710
  */
3485
- function buildCvssEntries(item) {
3711
+ function buildCvssEntries$4(item) {
3486
3712
  const source = (item.cvss_score_source || "").trim();
3487
3713
  if (!source || !CVE_SOURCE_RE.test(source)) return [];
3488
3714
  const hasV3 = !!(item.cvss3_vector || item.cvss3_base_score);
@@ -3513,15 +3739,13 @@ function buildCvssEntries(item) {
3513
3739
  if (baseVector) entry.baseVector = baseVector;
3514
3740
  if (baseScore !== void 0) {
3515
3741
  entry.baseScore = baseScore;
3516
- const baseSeverity = mapCvssSeverity(baseScore);
3517
- if (baseSeverity !== void 0) entry.baseSeverity = baseSeverity;
3742
+ entry.baseSeverity = cvssSeverityFromScore(baseScore);
3518
3743
  }
3519
3744
  if (threatVector !== void 0 && threatVector !== "") entry.threatVector = threatVector;
3520
3745
  if (threatScore !== void 0) {
3521
3746
  entry.threatScore = threatScore;
3522
3747
  entry.computedScore = threatScore;
3523
- const computedSeverity = mapCvssSeverity(threatScore);
3524
- if (computedSeverity !== void 0) entry.computedSeverity = computedSeverity;
3748
+ entry.computedSeverity = cvssSeverityFromScore(threatScore);
3525
3749
  }
3526
3750
  return [entry];
3527
3751
  }
@@ -3552,16 +3776,6 @@ function parseFloatOrUndef(s) {
3552
3776
  const f = Number.parseFloat(s);
3553
3777
  return Number.isFinite(f) ? f : void 0;
3554
3778
  }
3555
- function mapCvssSeverity(score) {
3556
- switch (cvssScoreToSeverity(score)) {
3557
- case "critical": return CVSSSeverity.Critical;
3558
- case "high": return CVSSSeverity.High;
3559
- case "medium": return CVSSSeverity.Medium;
3560
- case "low": return CVSSSeverity.Low;
3561
- case "none": return CVSSSeverity.None;
3562
- default: return;
3563
- }
3564
- }
3565
3779
  /**
3566
3780
  * Extract CWE IDs from a ReportItem's <cwe> elements. Nessus emits bare
3567
3781
  * numeric IDs (e.g. <cwe>200</cwe>); occasionally pipe-separated or prefixed
@@ -4048,7 +4262,7 @@ function buildCheckText(rule) {
4048
4262
  }
4049
4263
  return parts.join("<br/>");
4050
4264
  }
4051
- function buildCodeDesc$7(q) {
4265
+ function buildCodeDesc$8(q) {
4052
4266
  return `config_rule_name: ${q.ConfigRuleName}, resource_type: ${q.ResourceType}, resource_id: ${q.ResourceId}`;
4053
4267
  }
4054
4268
  function buildResultMessage(codeDesc, annotation, status) {
@@ -4065,7 +4279,7 @@ function computeRunTime(invoked, recorded) {
4065
4279
  function buildResult(r) {
4066
4280
  const q = r.EvaluationResultIdentifier.EvaluationResultQualifier;
4067
4281
  const status = mapComplianceStatus(r.ComplianceType);
4068
- const codeDesc = buildCodeDesc$7(q);
4282
+ const codeDesc = buildCodeDesc$8(q);
4069
4283
  return createResult(status, buildResultMessage(codeDesc, r.Annotation, status), {
4070
4284
  codeDesc,
4071
4285
  startTime: (r.ConfigRuleInvokedTime ? parseTimestamp(r.ConfigRuleInvokedTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z"),
@@ -4193,12 +4407,32 @@ function checkToResult(check, scanTime) {
4193
4407
  });
4194
4408
  }
4195
4409
  /**
4410
+ * Renders checkov's code_block ([[lineno, "source"], ...]) into a readable,
4411
+ * line-numbered source snippet for the Heimdall CODE tab. Returns undefined when
4412
+ * the code_block is absent or not an array so requirement.code is omitted.
4413
+ */
4414
+ function renderCodeBlock(codeBlock) {
4415
+ if (!Array.isArray(codeBlock)) return void 0;
4416
+ const lines = [];
4417
+ for (const entry of codeBlock) {
4418
+ if (!Array.isArray(entry) || entry.length < 2) continue;
4419
+ const lineno = entry[0];
4420
+ const src = typeof entry[1] === "string" ? entry[1] : "";
4421
+ const trimmed = src.endsWith("\n") ? src.slice(0, -1) : src;
4422
+ lines.push(`${lineno} ${trimmed}`);
4423
+ }
4424
+ return lines.length ? lines.join("\n") : void 0;
4425
+ }
4426
+ /**
4196
4427
  * Converts a group of checks sharing a check_id into one EvaluatedRequirement.
4197
4428
  */
4198
- function buildRequirement$14(checkId, checks, scanTime) {
4429
+ function buildRequirement$14(checkId, group, scanTime) {
4430
+ const checks = group.map((c) => c.check);
4199
4431
  const rep = checks[0];
4200
4432
  const impact = getImpact$8(rep.severity);
4201
4433
  const tags = { nist: [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS] };
4434
+ const checkTypes = [...new Set(group.map((c) => c.checkType).filter((t) => t !== ""))].sort();
4435
+ if (checkTypes.length > 0) tags["check_type"] = checkTypes;
4202
4436
  const descriptions = [{
4203
4437
  label: "default",
4204
4438
  data: rep.check_name
@@ -4210,6 +4444,8 @@ function buildRequirement$14(checkId, checks, scanTime) {
4210
4444
  const results = checks.map((check) => checkToResult(check, scanTime));
4211
4445
  const req = createRequirement(checkId, rep.check_name, descriptions, impact, results, { tags });
4212
4446
  req.verificationMethod = VerificationMethodEnum.Automated;
4447
+ const code = renderCodeBlock(rep.code_block);
4448
+ if (code !== void 0) req.code = code;
4213
4449
  const controlType = deriveControlTypeFromTags([...DEFAULT_STATIC_ANALYSIS_NIST_TAGS]);
4214
4450
  if (controlType !== void 0) req.controlType = controlType;
4215
4451
  return req;
@@ -4246,18 +4482,23 @@ async function convertCheckovToHdf(input, converterVersion = "1.0.0") {
4246
4482
  for (const report of reports) {
4247
4483
  checkTypes.push(report.check_type);
4248
4484
  if (!version && report.summary.checkov_version) version = report.summary.checkov_version;
4249
- allChecks.push(...report.results.passed_checks);
4250
- allChecks.push(...report.results.failed_checks);
4251
- allChecks.push(...report.results.skipped_checks);
4485
+ for (const check of [
4486
+ ...report.results.passed_checks,
4487
+ ...report.results.failed_checks,
4488
+ ...report.results.skipped_checks
4489
+ ]) allChecks.push({
4490
+ check,
4491
+ checkType: report.check_type
4492
+ });
4252
4493
  }
4253
4494
  const { items: limitedChecks, truncated } = limitArray(allChecks);
4254
4495
  /* v8 ignore next -- truncation only triggers with >100K items */
4255
4496
  if (truncated) console.warn(`WARNING: Input truncated at ${limitedChecks.length} check items (original: ${allChecks.length})`);
4256
4497
  const groups = /* @__PURE__ */ new Map();
4257
- for (const check of limitedChecks) {
4258
- const existing = groups.get(check.check_id);
4259
- if (existing) existing.push(check);
4260
- else groups.set(check.check_id, [check]);
4498
+ for (const entry of limitedChecks) {
4499
+ const existing = groups.get(entry.check.check_id);
4500
+ if (existing) existing.push(entry);
4501
+ else groups.set(entry.check.check_id, [entry]);
4261
4502
  }
4262
4503
  const requirements = [];
4263
4504
  for (const [checkId, checks] of groups) requirements.push(buildRequirement$14(checkId, checks, scanTime));
@@ -4272,7 +4513,6 @@ async function convertCheckovToHdf(input, converterVersion = "1.0.0") {
4272
4513
  converterVersion,
4273
4514
  toolName: "Checkov",
4274
4515
  toolVersion: version,
4275
- toolFormat: format,
4276
4516
  baselines: [baseline],
4277
4517
  timestamp: scanTime
4278
4518
  });
@@ -4288,6 +4528,13 @@ const IMPACT_MAPPING$6 = {
4288
4528
  LOW: .3
4289
4529
  };
4290
4530
  /**
4531
+ * Returns the first-class CWE identifiers for an issue in "CWE-N" form,
4532
+ * or an empty array when the issue carries no CWE.
4533
+ */
4534
+ function cweIds(issue) {
4535
+ return issue.cwe.id ? [`CWE-${issue.cwe.id}`] : [];
4536
+ }
4537
+ /**
4291
4538
  * Returns true if the issue is suppressed (via nosec flag or suppressions list).
4292
4539
  */
4293
4540
  function isSuppressed(issue) {
@@ -4324,13 +4571,7 @@ function buildRequirement$13(ruleId, issues, scanTime) {
4324
4571
  const rep = issues[0];
4325
4572
  const impact = IMPACT_MAPPING$6[rep.severity.toUpperCase()] ?? .5;
4326
4573
  const nist = mapCWEToNIST([rep.cwe.id], DEFAULT_REMEDIATION_NIST_TAGS$1);
4327
- const tags = {
4328
- nist,
4329
- cwe: {
4330
- id: rep.cwe.id,
4331
- url: rep.cwe.url
4332
- }
4333
- };
4574
+ const tags = { nist };
4334
4575
  const results = issues.map((issue) => issueToResult(issue, scanTime));
4335
4576
  const descriptions = [{
4336
4577
  label: "default",
@@ -4340,6 +4581,9 @@ function buildRequirement$13(ruleId, issues, scanTime) {
4340
4581
  data: `CWE-${rep.cwe.id}: ${rep.cwe.url}`
4341
4582
  }];
4342
4583
  const req = createRequirement(ruleId, rep.details, descriptions, impact, results, { tags });
4584
+ const cwe = cweIds(rep);
4585
+ if (cwe.length > 0) req.cwe = cwe;
4586
+ req.code = rep.code;
4343
4587
  req.verificationMethod = VerificationMethodEnum.Automated;
4344
4588
  const controlType = deriveControlTypeFromTags(nist);
4345
4589
  if (controlType !== void 0) req.controlType = controlType;
@@ -4392,7 +4636,7 @@ function buildNistTags$2(niktoId) {
4392
4636
  if (control) return [control];
4393
4637
  return [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS];
4394
4638
  }
4395
- function buildCodeDesc$6(vuln) {
4639
+ function buildCodeDesc$7(vuln) {
4396
4640
  const parts = [];
4397
4641
  if (vuln.url) parts.push(`URL: ${vuln.url}`);
4398
4642
  if (vuln.method) parts.push(`Method: ${vuln.method}`);
@@ -4403,7 +4647,7 @@ function convertVulnToRequirement(vuln) {
4403
4647
  const cciTags = nistToCci(nistTags);
4404
4648
  const result = {
4405
4649
  status: ResultStatus.Failed,
4406
- codeDesc: buildCodeDesc$6(vuln),
4650
+ codeDesc: buildCodeDesc$7(vuln),
4407
4651
  startTime: /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
4408
4652
  };
4409
4653
  const extras = {};
@@ -4446,7 +4690,7 @@ async function convertNiktoToHdf(input, converterVersion = "unknown") {
4446
4690
  const req = convertVulnToRequirement(primary);
4447
4691
  if (vulns.length > 1) for (let i = 1; i < vulns.length; i++) req.results.push({
4448
4692
  status: ResultStatus.Failed,
4449
- codeDesc: buildCodeDesc$6(vulns[i]),
4693
+ codeDesc: buildCodeDesc$7(vulns[i]),
4450
4694
  startTime: /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
4451
4695
  });
4452
4696
  requirements.push(req);
@@ -4460,7 +4704,6 @@ async function convertNiktoToHdf(input, converterVersion = "unknown") {
4460
4704
  generatorName: "nikto-to-hdf",
4461
4705
  converterVersion,
4462
4706
  toolName: "Nikto",
4463
- toolFormat: "JSON",
4464
4707
  baselines: [createMinimalBaseline(targetName, requirements, {
4465
4708
  resultsChecksum,
4466
4709
  title: `Nikto Target: ${targetName}`,
@@ -4493,7 +4736,13 @@ function buildNistTags$1(cweid) {
4493
4736
  }
4494
4737
  return [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS];
4495
4738
  }
4496
- function buildCodeDesc$5(instance) {
4739
+ function buildCwe(cweid) {
4740
+ if (!cweid || cweid === "0") return;
4741
+ const cweNum = parseInt(cweid, 10);
4742
+ if (isNaN(cweNum)) return;
4743
+ return [`CWE-${cweNum}`];
4744
+ }
4745
+ function buildCodeDesc$6(instance) {
4497
4746
  const parts = [];
4498
4747
  if (instance.uri) parts.push(`URI: ${instance.uri}`);
4499
4748
  if (instance.method) parts.push(`Method: ${instance.method}`);
@@ -4501,6 +4750,21 @@ function buildCodeDesc$5(instance) {
4501
4750
  if (instance.evidence) parts.push(`Evidence: ${instance.evidence}`);
4502
4751
  return parts.join(" | ");
4503
4752
  }
4753
+ function representativeInstance(instances) {
4754
+ if (!instances || instances.length === 0) return;
4755
+ return instances.find((inst) => inst.attack) ?? instances[0];
4756
+ }
4757
+ function buildRequirementCode$1(alert) {
4758
+ const inst = representativeInstance(alert.instances);
4759
+ if (!inst) return;
4760
+ const parts = [];
4761
+ const requestLine = `${inst.method ?? ""} ${inst.uri ?? ""}`.trim();
4762
+ if (requestLine) parts.push(requestLine);
4763
+ if (inst.param) parts.push(`Param: ${inst.param}`);
4764
+ if (inst.attack) parts.push(`Attack: ${inst.attack}`);
4765
+ if (parts.length === 0) return;
4766
+ return parts.join("\n");
4767
+ }
4504
4768
  function buildCheckDescription(alert) {
4505
4769
  const parts = [];
4506
4770
  if (alert.solution) {
@@ -4513,33 +4777,11 @@ function buildCheckDescription(alert) {
4513
4777
  }
4514
4778
  return parts.join("\n");
4515
4779
  }
4516
- function selectSite(sites) {
4517
- if (sites.length === 0) return void 0;
4518
- let best = sites[0];
4519
- for (let i = 1; i < sites.length; i++) {
4520
- const site = sites[i];
4521
- if ((site.alerts?.length ?? 0) > (best.alerts?.length ?? 0)) best = site;
4522
- }
4523
- return best;
4524
- }
4525
- const ZAP_RFC1123_LIKE = /^[A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2}$/;
4526
- function parseZapTimestamp(s) {
4527
- const trimmed = s.trim();
4528
- if (ZAP_RFC1123_LIKE.test(trimmed)) {
4529
- const d = /* @__PURE__ */ new Date(`${trimmed} GMT`);
4530
- if (!isNaN(d.getTime())) return d;
4531
- }
4532
- return parseTimestamp(s) ?? void 0;
4780
+ function siteLabel(site, index) {
4781
+ return site["@host"] || site["@name"] || `site ${index + 1}`;
4533
4782
  }
4534
- async function convertZapToHdf(input, converterVersion = "1.0.0") {
4535
- validateInputSize(input, "zap");
4536
- registerAllFingerprints();
4537
- const detected = detectConverter(input);
4538
- if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
4539
- const resultsChecksum = await inputChecksum(input);
4540
- const zapData = parseJSON(input);
4541
- const site = selectSite(Array.isArray(zapData.site) ? zapData.site : []);
4542
- const alerts = site?.alerts ?? [];
4783
+ function buildSiteRequirements(site) {
4784
+ const alerts = site.alerts ?? [];
4543
4785
  const pluginIdCount = /* @__PURE__ */ new Map();
4544
4786
  const { items: limitedAlerts, truncated } = limitArray(alerts);
4545
4787
  /* v8 ignore next -- truncation only triggers with >100K items */
@@ -4552,7 +4794,6 @@ async function convertZapToHdf(input, converterVersion = "1.0.0") {
4552
4794
  const nistTags = buildNistTags$1(alert.cweid ?? "");
4553
4795
  const cciTags = nistToCci(nistTags);
4554
4796
  const extras = {};
4555
- if (alert.cweid) extras.cweid = alert.cweid;
4556
4797
  if (alert.wascid) extras.wascid = alert.wascid;
4557
4798
  if (alert.riskdesc) extras.riskdesc = alert.riskdesc;
4558
4799
  if (alert.confidence) extras.confidence = alert.confidence;
@@ -4564,7 +4805,7 @@ async function convertZapToHdf(input, converterVersion = "1.0.0") {
4564
4805
  for (const instance of limitedInstances) {
4565
4806
  const result = {
4566
4807
  status: ResultStatus.Failed,
4567
- codeDesc: buildCodeDesc$5(instance),
4808
+ codeDesc: buildCodeDesc$6(instance),
4568
4809
  startTime: /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
4569
4810
  };
4570
4811
  if (instance.attack) result.message = instance.attack;
@@ -4591,40 +4832,76 @@ async function convertZapToHdf(input, converterVersion = "1.0.0") {
4591
4832
  descriptions,
4592
4833
  verificationMethod: VerificationMethodEnum.Automated
4593
4834
  };
4835
+ const cwe = buildCwe(alert.cweid);
4836
+ if (cwe !== void 0) req.cwe = cwe;
4594
4837
  const controlType = deriveControlTypeFromTags(nistTags);
4595
4838
  if (controlType !== void 0) req.controlType = controlType;
4839
+ const code = buildRequirementCode$1(alert);
4840
+ if (code !== void 0) req.code = code;
4596
4841
  requirements.push(req);
4597
4842
  }
4598
- const targetName = site?.["@host"] ?? "Unknown Host";
4599
- const siteName = site?.["@name"] ?? "";
4600
- const baselineName = site && (site["@name"] || site["@host"]) ? `OWASP ZAP Scan of ${site["@name"] ?? targetName}` : "OWASP ZAP Scan";
4601
- if (requirements.length === 0) {
4602
- let target = siteName || targetName;
4603
- if (!target || target === "Unknown Host") target = "the target site";
4604
- requirements.push(buildNoFindingsRequirement("zap-no-findings", `OWASP ZAP scanned ${target} and reported zero findings.`, /* @__PURE__ */ new Date()));
4843
+ return requirements;
4844
+ }
4845
+ const ZAP_RFC1123_LIKE = /^[A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2}$/;
4846
+ function parseZapTimestamp(s) {
4847
+ const trimmed = s.trim();
4848
+ if (ZAP_RFC1123_LIKE.test(trimmed)) {
4849
+ const d = /* @__PURE__ */ new Date(`${trimmed} GMT`);
4850
+ if (!isNaN(d.getTime())) return d;
4605
4851
  }
4606
- const baseline = createMinimalBaseline("OWASP ZAP Scan", requirements, {
4607
- resultsChecksum,
4608
- title: baselineName,
4609
- summary: `ZAP Version ${zapData["@version"] ?? "unknown"}`
4610
- });
4611
- const tool = {
4612
- name: "OWASP ZAP",
4613
- format: "JSON"
4614
- };
4615
- if (zapData["@version"]) tool.version = zapData["@version"];
4852
+ return parseTimestamp(s) ?? void 0;
4853
+ }
4854
+ async function convertZapToHdf(input, converterVersion = "1.0.0") {
4855
+ validateInputSize(input, "zap");
4856
+ registerAllFingerprints();
4857
+ const detected = detectConverter(input);
4858
+ if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
4859
+ const resultsChecksum = await inputChecksum(input);
4860
+ const zapData = parseJSON(input);
4861
+ const sites = Array.isArray(zapData.site) ? zapData.site : [];
4862
+ const multiSite = sites.length > 1;
4863
+ const summary = `ZAP Version ${zapData["@version"] ?? "unknown"}`;
4864
+ const baselines = [];
4616
4865
  const components = [];
4617
- if (site?.["@name"]) components.push({
4618
- name: targetName,
4619
- type: TargetType.Application,
4620
- url: site["@name"]
4621
- });
4622
- else if (targetName !== "Unknown Host") components.push({
4623
- name: targetName,
4624
- type: TargetType.Application
4866
+ sites.forEach((site, i) => {
4867
+ const targetName = site["@host"] ?? "Unknown Host";
4868
+ const siteName = site["@name"] ?? "";
4869
+ const requirements = buildSiteRequirements(site);
4870
+ if (requirements.length === 0) {
4871
+ let target = siteName || targetName;
4872
+ if (!target || target === "Unknown Host") target = "the target site";
4873
+ requirements.push(buildNoFindingsRequirement("zap-no-findings", `OWASP ZAP scanned ${target} and reported zero findings.`, /* @__PURE__ */ new Date()));
4874
+ }
4875
+ const baselineTitle = site["@name"] || site["@host"] ? `OWASP ZAP Scan of ${site["@name"] ?? targetName}` : "OWASP ZAP Scan";
4876
+ const baseline = createMinimalBaseline(multiSite ? `OWASP ZAP Scan: ${siteLabel(site, i)}` : "OWASP ZAP Scan", requirements, {
4877
+ resultsChecksum,
4878
+ title: baselineTitle,
4879
+ summary
4880
+ });
4881
+ if (site["@host"]) baseline.labels = { component: site["@host"] };
4882
+ baselines.push(baseline);
4883
+ if (site["@name"]) components.push({
4884
+ name: targetName,
4885
+ type: TargetType.Application,
4886
+ url: site["@name"]
4887
+ });
4888
+ else if (targetName !== "Unknown Host") components.push({
4889
+ name: targetName,
4890
+ type: TargetType.Application
4891
+ });
4625
4892
  });
4893
+ if (baselines.length === 0) {
4894
+ const requirements = [buildNoFindingsRequirement("zap-no-findings", "OWASP ZAP scanned the target site and reported zero findings.", /* @__PURE__ */ new Date())];
4895
+ baselines.push(createMinimalBaseline("OWASP ZAP Scan", requirements, {
4896
+ resultsChecksum,
4897
+ title: "OWASP ZAP Scan",
4898
+ summary
4899
+ }));
4900
+ }
4901
+ const tool = { name: "OWASP ZAP" };
4902
+ if (zapData["@version"]) tool.version = zapData["@version"];
4626
4903
  const hdf = {
4627
- baselines: [baseline],
4904
+ baselines,
4628
4905
  components,
4629
4906
  generator: {
4630
4907
  name: "zap-to-hdf",
@@ -5242,23 +5519,18 @@ function stringSlice(v) {
5242
5519
  if (Array.isArray(v)) return v.filter((e) => typeof e === "string");
5243
5520
  return [];
5244
5521
  }
5245
- /** Most-significant status across a requirement's results[] (lossless). */
5522
+ /**
5523
+ * Most-significant status across a requirement's results[] (lossless), using
5524
+ * the canonical worst-wins ordering from @mitre/hdf-utilities.
5525
+ */
5246
5526
  function worstOfResults(req) {
5247
5527
  const results = asArr(req.results) ?? [];
5248
- const precedence = [
5249
- "failed",
5250
- "error",
5251
- "passed",
5252
- "notReviewed",
5253
- "notApplicable"
5254
- ];
5255
- const present = /* @__PURE__ */ new Set();
5528
+ const statuses = [];
5256
5529
  for (const rRaw of results) {
5257
5530
  const r = asMap(rRaw);
5258
- if (r) present.add(getStr(r, "status"));
5531
+ if (r) statuses.push(getStr(r, "status"));
5259
5532
  }
5260
- for (const s of precedence) if (present.has(s)) return s;
5261
- return "notReviewed";
5533
+ return worstStatus(statuses);
5262
5534
  }
5263
5535
  /**
5264
5536
  * Whether an HDF Result_Status is a failing verdict. Only 'failed' fails;
@@ -5514,10 +5786,34 @@ function maxImpact(ratings) {
5514
5786
  return max;
5515
5787
  }
5516
5788
  /**
5517
- * Formats the ratings as a human-readable tag string.
5789
+ * Assembles structured requirement.cvss[] entries from the CycloneDX ratings. A
5790
+ * rating contributes an entry only when it carries a CVSS method
5791
+ * (CVSSv2/v3/v31/v4) and at least a score or a vector — ratings that only state a
5792
+ * qualitative severity (method "other") carry no CVSS metrics and are left out,
5793
+ * their severity already reflected in the requirement impact.
5518
5794
  */
5519
- function formatRatingsTag(ratings) {
5520
- return ratings.map((r) => `${r.source?.name ?? "Unknown"} - ${r.severity ?? "unrated"}`).join(", ");
5795
+ function cvssVersionFromMethod(method, vector) {
5796
+ if (vector !== void 0 && vector.startsWith("CVSS:")) return cvssVersionFromVector(vector);
5797
+ switch (method) {
5798
+ case "CVSSv2": return cvssVersionFromString("2.0");
5799
+ case "CVSSv4": return cvssVersionFromString("4.0");
5800
+ default: return cvssVersionFromVector(vector);
5801
+ }
5802
+ }
5803
+ function buildCvssEntries$3(ratings) {
5804
+ const entries = [];
5805
+ for (const r of ratings) {
5806
+ const hasCvssMethod = r.method !== void 0 && CVSS_METHODS.has(r.method);
5807
+ const hasMetric = r.score !== void 0 && r.score !== null || r.vector !== void 0 && r.vector !== "";
5808
+ if (!hasCvssMethod || !hasMetric) continue;
5809
+ entries.push(buildCvss$2({
5810
+ version: cvssVersionFromMethod(r.method, r.vector),
5811
+ baseScore: r.score,
5812
+ baseVector: r.vector,
5813
+ source: r.source?.name
5814
+ }));
5815
+ }
5816
+ return entries;
5521
5817
  }
5522
5818
  /**
5523
5819
  * Formats a component reference as a code_desc string.
@@ -5585,8 +5881,8 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
5585
5881
  nist,
5586
5882
  cci: nistToCci(nist)
5587
5883
  };
5588
- if (cwes.length > 0) tags["cweid"] = cwes.map((c) => `CWE-${c}`);
5589
- if (ratings.length > 0) tags["ratings"] = formatRatingsTag(ratings);
5884
+ const cweIds = cwes.map((c) => `CWE-${c}`);
5885
+ const cvssEntries = buildCvssEntries$3(ratings);
5590
5886
  const descriptions = [];
5591
5887
  const defaultParts = [];
5592
5888
  if (vuln.description) defaultParts.push(`Description: ${vuln.description}`);
@@ -5614,6 +5910,8 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
5614
5910
  const results = affects.length > 0 ? affects.map((affect) => toResult(formatCodeDesc$4(componentLookup, affect.ref))) : [toResult(`Vulnerability ${vuln.id}`)];
5615
5911
  const title = vuln.source?.name ? `${vuln.id} (${vuln.source.name})` : vuln.id;
5616
5912
  const req = createRequirement(vuln.id, title, descriptions, impact, results, { tags });
5913
+ if (cvssEntries.length > 0) req.cvss = cvssEntries;
5914
+ if (cweIds.length > 0) req.cwe = cweIds;
5617
5915
  const controlType = deriveControlTypeFromTags(nist);
5618
5916
  if (controlType !== void 0) req.controlType = controlType;
5619
5917
  requirements.push(req);
@@ -5637,7 +5935,6 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
5637
5935
  generatorName: "cyclonedx-to-hdf",
5638
5936
  converterVersion,
5639
5937
  toolName: "CycloneDX",
5640
- toolFormat: "JSON",
5641
5938
  baselines: [baseline],
5642
5939
  components: [{
5643
5940
  ...targetComponent,
@@ -6748,7 +7045,7 @@ function collectIdentifierTags(identifiers) {
6748
7045
  }
6749
7046
  return result;
6750
7047
  }
6751
- function buildCodeDesc$4(scanType, location) {
7048
+ function buildCodeDesc$5(scanType, location) {
6752
7049
  if (!location) return "";
6753
7050
  switch (scanType) {
6754
7051
  case "sast":
@@ -6826,7 +7123,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
6826
7123
  });
6827
7124
  const result = {
6828
7125
  status: ResultStatus.Failed,
6829
- codeDesc: buildCodeDesc$4(scanType, vuln.location),
7126
+ codeDesc: buildCodeDesc$5(scanType, vuln.location),
6830
7127
  startTime: startTime ? parseTimestamp(startTime) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z") : /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
6831
7128
  };
6832
7129
  const impact = gitlabSeverityToImpact(vuln.severity ?? "Unknown");
@@ -6834,6 +7131,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
6834
7131
  id: vuln.id,
6835
7132
  title: vuln.name ?? vuln.id,
6836
7133
  impact,
7134
+ code: JSON.stringify(vuln, null, 2),
6837
7135
  results: [result],
6838
7136
  tags,
6839
7137
  descriptions,
@@ -6853,10 +7151,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
6853
7151
  title: `GitLab ${label} Security Scan`,
6854
7152
  summary: `Scanner: ${scannerName}${scannerVersion ? ` v${scannerVersion}` : ""}`
6855
7153
  });
6856
- const tool = {
6857
- name: scannerName,
6858
- format: "JSON"
6859
- };
7154
+ const tool = { name: scannerName };
6860
7155
  if (scannerVersion) tool.version = scannerVersion;
6861
7156
  const components = [];
6862
7157
  const targetType = scanTypeToTargetType(scanType);
@@ -6889,6 +7184,20 @@ const TRUFFLEHOG_CCI = [
6889
7184
  "CCI-002367"
6890
7185
  ];
6891
7186
  /**
7187
+ * A verified secret is a confirmed-live credential (TruffleHog reached the
7188
+ * provider and the credential authenticated) and rates high; an unverified
7189
+ * candidate rates medium.
7190
+ */
7191
+ const IMPACT_VERIFIED = .7;
7192
+ const IMPACT_UNVERIFIED = .5;
7193
+ /**
7194
+ * Rate a requirement by its strongest signal: any verified finding in the group
7195
+ * elevates the whole requirement to the verified impact.
7196
+ */
7197
+ function groupImpact(findings) {
7198
+ return findings.some((f) => f.Verified) ? IMPACT_VERIFIED : IMPACT_UNVERIFIED;
7199
+ }
7200
+ /**
6892
7201
  * Parse TruffleHog input as JSON array, single object, or NDJSON.
6893
7202
  */
6894
7203
  function parseFindings(input) {
@@ -6948,7 +7257,7 @@ function buildMessage(f) {
6948
7257
  /**
6949
7258
  * Build the Result.CodeDesc as JSON of SourceMetadata.
6950
7259
  */
6951
- function buildCodeDesc$3(f) {
7260
+ function buildCodeDesc$4(f) {
6952
7261
  return JSON.stringify(f.SourceMetadata ?? {});
6953
7262
  }
6954
7263
  /**
@@ -6991,12 +7300,12 @@ function buildRequirement$12(reqID, findings) {
6991
7300
  data: rep.DetectorDescription ?? `${rep.DetectorName} secret detected by ${rep.DecoderName} decoder`
6992
7301
  }];
6993
7302
  const results = findings.map((f) => createResult(ResultStatus.Failed, buildMessage(f), {
6994
- codeDesc: buildCodeDesc$3(f),
7303
+ codeDesc: buildCodeDesc$4(f),
6995
7304
  startTime: getTimestamp(f)
6996
7305
  }));
6997
7306
  const sourceFile = getSourceFile(rep);
6998
7307
  const sourceLine = getSourceLine(rep);
6999
- const req = createRequirement(reqID, title, descriptions, .5, results, {
7308
+ const req = createRequirement(reqID, title, descriptions, groupImpact(findings), results, {
7000
7309
  tags,
7001
7310
  sourceLocation: sourceFile ? {
7002
7311
  ref: sourceFile,
@@ -7036,10 +7345,7 @@ async function convertTrufflehogToHdf(input, converterVersion = "1.0.0") {
7036
7345
  name: "trufflehog-to-hdf",
7037
7346
  version: converterVersion
7038
7347
  },
7039
- tool: {
7040
- name: "TruffleHog",
7041
- format: "JSON"
7042
- },
7348
+ tool: { name: "TruffleHog" },
7043
7349
  timestamp: /* @__PURE__ */ new Date()
7044
7350
  };
7045
7351
  const repoURL = findGitRepoURL(limitedFindings);
@@ -7173,7 +7479,6 @@ async function convertHipcheckToHdf(input, converterVersion = "1.0.0") {
7173
7479
  generatorName: "hipcheck-to-hdf",
7174
7480
  converterVersion,
7175
7481
  toolName: "Hipcheck",
7176
- toolFormat: "JSON",
7177
7482
  toolVersion: report.hipcheck_version,
7178
7483
  baselines: [baseline],
7179
7484
  components: ident ? [{
@@ -7246,10 +7551,7 @@ async function convertBurpsuiteToHdf(input, converterVersion = "1.0.0") {
7246
7551
  resultsChecksum,
7247
7552
  title: `BurpSuite Scan: ${targetName}`
7248
7553
  });
7249
- const tool = {
7250
- name: "BurpSuite",
7251
- format: "XML"
7252
- };
7554
+ const tool = { name: "BurpSuite" };
7253
7555
  if (burpVersion) tool.version = burpVersion;
7254
7556
  const hdf = {
7255
7557
  baselines: [baseline],
@@ -7418,6 +7720,17 @@ function parseDate(dateStr) {
7418
7720
  return parseTimestamp(month ? `${month[3]}-${MONTH_ABBR[month[1]] ?? "00"}-${month[2].padStart(2, "0")} ${month[4]}` : trimmed) ?? ZERO;
7419
7721
  }
7420
7722
  /**
7723
+ * Renders a finding's parsed row (column→value map) as the indented JSON blob
7724
+ * carried in requirement.code. DBProtect ships no literal check source, so the
7725
+ * row itself is the richest available representation. Keys are sorted so the
7726
+ * bytes match the Go twin, whose encoding/json emits map keys in sorted order.
7727
+ */
7728
+ function marshalFindingCode(f) {
7729
+ const sorted = {};
7730
+ for (const key of Object.keys(f).sort()) sorted[key] = f[key];
7731
+ return JSON.stringify(sorted, null, 2);
7732
+ }
7733
+ /**
7421
7734
  * Builds a single EvaluatedRequirement from a group of findings sharing a Check ID.
7422
7735
  */
7423
7736
  function buildRequirement$10(checkID, findings, hasStatus) {
@@ -7436,6 +7749,7 @@ function buildRequirement$10(checkID, findings, hasStatus) {
7436
7749
  });
7437
7750
  const req = createRequirement(checkID, rep["Check"] ?? "", descriptions, getImpact$6(rep["Risk DV"] ?? ""), results, { tags });
7438
7751
  req.verificationMethod = VerificationMethodEnum.Automated;
7752
+ req.code = marshalFindingCode(rep);
7439
7753
  const controlType = deriveControlTypeFromTags([...nist]);
7440
7754
  if (controlType !== void 0) req.controlType = controlType;
7441
7755
  return req;
@@ -7487,7 +7801,6 @@ async function convertDbprotectToHdf(input, converterVersion = "1.0.0") {
7487
7801
  generatorName: "dbprotect-to-hdf",
7488
7802
  converterVersion,
7489
7803
  toolName: "DBProtect",
7490
- toolFormat: "XML",
7491
7804
  baselines: [baseline],
7492
7805
  components: [{
7493
7806
  name: targetName,
@@ -7531,30 +7844,6 @@ function buildSummary(result) {
7531
7844
  return `Package Vulnerability Summary: ${result.vulnerabilityDistribution ? String(result.vulnerabilityDistribution.total) : "N/A"} Application Compliance Issue Total: ${result.complianceDistribution ? String(result.complianceDistribution.total) : "N/A"}`;
7532
7845
  }
7533
7846
  /**
7534
- * Detects CVSS schema version enum from the vector prefix. Defaults to 3.1
7535
- * since modern Twistlock exclusively emits 3.x output.
7536
- */
7537
- function cvssVersionFromVector(vector) {
7538
- if (!vector) return Version.The31;
7539
- if (vector.startsWith("CVSS:2.0/")) return Version.The20;
7540
- if (vector.startsWith("CVSS:3.0/")) return Version.The30;
7541
- if (vector.startsWith("CVSS:4.0/")) return Version.The40;
7542
- return Version.The31;
7543
- }
7544
- /**
7545
- * Maps cvssScoreToSeverity('low'|'medium'|...) into the CVSSSeverity enum.
7546
- */
7547
- function cvssSeverityFromScore(score) {
7548
- switch (cvssScoreToSeverity(score)) {
7549
- case "none": return CVSSSeverity.None;
7550
- case "low": return CVSSSeverity.Low;
7551
- case "medium": return CVSSSeverity.Medium;
7552
- case "high": return CVSSSeverity.High;
7553
- case "critical": return CVSSSeverity.Critical;
7554
- default: return;
7555
- }
7556
- }
7557
- /**
7558
7847
  * Builds a Cvss entry from a Twistlock vulnerability. Returns undefined only
7559
7848
  * when neither a score nor a vector is available. When the vendor emits a
7560
7849
  * score but no vector (common in Twistlock/Prisma Cloud output), the Cvss
@@ -7565,16 +7854,12 @@ function buildCvss(vuln) {
7565
7854
  const hasScore = typeof vuln.cvss === "number" && Number.isFinite(vuln.cvss);
7566
7855
  const hasVector = !!vuln.vector;
7567
7856
  if (!hasScore && !hasVector) return void 0;
7568
- const cv = { version: cvssVersionFromVector(vuln.vector) };
7569
- if (hasScore) {
7570
- cv.baseScore = vuln.cvss;
7571
- const sev = cvssSeverityFromScore(vuln.cvss);
7572
- if (sev !== void 0) cv.baseSeverity = sev;
7573
- }
7574
- if (hasVector) cv.baseVector = vuln.vector;
7575
- const source = vuln.cve ?? vuln.id;
7576
- if (source) cv.source = source;
7577
- return cv;
7857
+ return buildCvss$2({
7858
+ version: cvssVersionFromVector(vuln.vector),
7859
+ baseScore: hasScore ? vuln.cvss : void 0,
7860
+ baseVector: vuln.vector,
7861
+ source: vuln.cve ?? vuln.id
7862
+ });
7578
7863
  }
7579
7864
  const CWE_REGEX = /cwe[-_]?(\d+)/gi;
7580
7865
  /**
@@ -7764,7 +8049,6 @@ async function convertTwistlockToHdf(input, converterVersion = "1.0.0") {
7764
8049
  generatorName: "twistlock-to-hdf",
7765
8050
  converterVersion,
7766
8051
  toolName: "Twistlock",
7767
- toolFormat: "JSON",
7768
8052
  baselines,
7769
8053
  components: [component],
7770
8054
  timestamp: /* @__PURE__ */ new Date()
@@ -7805,16 +8089,32 @@ function getCweIDs(cwes) {
7805
8089
  return cwes.map((cwe) => `CWE-${cwe.cweId}`);
7806
8090
  }
7807
8091
  /**
8092
+ * Collects CVE identifiers from vulnerability.aliases[].cveId, deduped in
8093
+ * first-seen order. The finding.matrix (requirement id) is a UUID composite, so
8094
+ * the CVE has no other home; it goes to tags.cve (interim, pending an
8095
+ * identifiers[] schema field).
8096
+ */
8097
+ function getCVEs(vuln) {
8098
+ const seen = /* @__PURE__ */ new Set();
8099
+ const out = [];
8100
+ for (const alias of vuln.aliases ?? []) if (alias.cveId && !seen.has(alias.cveId)) {
8101
+ seen.add(alias.cveId);
8102
+ out.push(alias.cveId);
8103
+ }
8104
+ return out;
8105
+ }
8106
+ /**
7808
8107
  * Builds a single EvaluatedRequirement from a Dependency-Track finding.
7809
8108
  */
7810
8109
  function buildRequirement$8(finding, timestamp) {
7811
8110
  const cweIDs = getCweIDs(finding.vulnerability.cwes);
8111
+ const cveIDs = getCVEs(finding.vulnerability);
7812
8112
  const nist = mapCWEToNIST(cweIDs, DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
7813
8113
  const tags = {
7814
8114
  nist,
7815
8115
  cci: nistToCci(nist)
7816
8116
  };
7817
- if (cweIDs.length > 0) tags["cweIds"] = cweIDs;
8117
+ if (cveIDs.length > 0) tags["cve"] = cveIDs;
7818
8118
  const descriptions = [{
7819
8119
  label: "default",
7820
8120
  data: finding.vulnerability.description ?? ""
@@ -7834,8 +8134,10 @@ function buildRequirement$8(finding, timestamp) {
7834
8134
  })];
7835
8135
  const req = createRequirement(finding.matrix, getTitle$1(finding), descriptions, getImpact$5(finding.vulnerability.severity), results, { tags });
7836
8136
  req.verificationMethod = VerificationMethodEnum.Automated;
8137
+ req.code = JSON.stringify(finding, null, 2);
7837
8138
  const controlType = deriveControlTypeFromTags(nist);
7838
8139
  if (controlType !== void 0) req.controlType = controlType;
8140
+ if (cweIDs.length > 0) req.cwe = cweIDs;
7839
8141
  const pkg = buildAffectedPackageFromComponent(finding.component);
7840
8142
  if (pkg) req.affectedPackages = [pkg];
7841
8143
  return req;
@@ -7887,7 +8189,7 @@ async function convertDeptrackToHdf(input, converterVersion = "1.0.0") {
7887
8189
  generatorName: "deptrack-to-hdf",
7888
8190
  converterVersion,
7889
8191
  toolName: "Dependency-Track",
7890
- toolFormat: "JSON",
8192
+ toolFormat: "FPF",
7891
8193
  baselines: [baseline],
7892
8194
  components: [{
7893
8195
  name: targetName,
@@ -7906,12 +8208,79 @@ async function hashID(summary) {
7906
8208
  return (await sha256(summary)).substring(0, 32);
7907
8209
  }
7908
8210
  /**
7909
- * Extract CWE identifiers from the first CVE entry.
8211
+ * Collect the distinct CWE identifiers across every CVE entry, preserving
8212
+ * first-seen order. These feed both requirement.cwe[] and the CWE→NIST mapping.
7910
8213
  */
7911
8214
  function extractCWEs$1(entry) {
7912
- const cves = entry.component_versions?.more_details?.cves;
7913
- if (!cves || cves.length === 0) return [];
7914
- return cves[0]?.cwe ?? [];
8215
+ const out = [];
8216
+ const seen = /* @__PURE__ */ new Set();
8217
+ for (const cve of entry.component_versions?.more_details?.cves ?? []) for (const cwe of cve.cwe ?? []) if (cwe && !seen.has(cwe)) {
8218
+ seen.add(cwe);
8219
+ out.push(cwe);
8220
+ }
8221
+ return out;
8222
+ }
8223
+ /**
8224
+ * Collect the distinct CVE identifiers across every CVE entry, preserving
8225
+ * first-seen order. The requirement id is a summary hash (never the CVE), so
8226
+ * tags.cve is where the CVE list lives.
8227
+ */
8228
+ function extractCVEs$1(entry) {
8229
+ const out = [];
8230
+ const seen = /* @__PURE__ */ new Set();
8231
+ for (const cve of entry.component_versions?.more_details?.cves ?? []) if (cve.cve && !seen.has(cve.cve)) {
8232
+ seen.add(cve.cve);
8233
+ out.push(cve.cve);
8234
+ }
8235
+ return out;
8236
+ }
8237
+ /**
8238
+ * Split a JFrog cvss_v2/cvss_v3 field ("<score>/<vector>", e.g.
8239
+ * "9.8/CVSS:3.1/AV:N/...") on the FIRST "/" into the numeric base score and the
8240
+ * remaining vector. A field with no "/" is a bare score with no vector; an
8241
+ * unparseable score yields undefined (the vector may still stand alone).
8242
+ */
8243
+ function parseCvssField(field) {
8244
+ if (!field) return { vector: "" };
8245
+ const idx = field.indexOf("/");
8246
+ let scorePart = field;
8247
+ let vector = "";
8248
+ if (idx >= 0) {
8249
+ scorePart = field.slice(0, idx);
8250
+ vector = field.slice(idx + 1);
8251
+ }
8252
+ const parsed = Number.parseFloat(scorePart.trim());
8253
+ return {
8254
+ score: Number.isFinite(parsed) ? parsed : void 0,
8255
+ vector
8256
+ };
8257
+ }
8258
+ /**
8259
+ * Assemble the structured requirement.cvss[] for a group's representative
8260
+ * entry. Each CVE contributes its v3 metric (version from the vector prefix)
8261
+ * then its v2 metric (always CVSS 2.0 — the cvss_v2 vector prefix is
8262
+ * inconsistent, so the field name is the authority). A field with neither a
8263
+ * score nor a vector is skipped.
8264
+ */
8265
+ function buildCvssEntries$2(cves) {
8266
+ const out = [];
8267
+ for (const cve of cves ?? []) {
8268
+ const v3 = parseCvssField(cve.cvss_v3);
8269
+ if (v3.score !== void 0 || v3.vector !== "") out.push(buildCvss$2({
8270
+ version: cvssVersionFromVector(v3.vector),
8271
+ baseScore: v3.score,
8272
+ baseVector: v3.vector,
8273
+ source: cve.cve
8274
+ }));
8275
+ const v2 = parseCvssField(cve.cvss_v2);
8276
+ if (v2.score !== void 0 || v2.vector !== "") out.push(buildCvss$2({
8277
+ version: cvssVersionFromString("2.0"),
8278
+ baseScore: v2.score,
8279
+ baseVector: v2.vector,
8280
+ source: cve.cve
8281
+ }));
8282
+ }
8283
+ return out;
7915
8284
  }
7916
8285
  /**
7917
8286
  * Build description from more_details, including CVE data.
@@ -7949,17 +8318,57 @@ function formatCodeDesc$1(entry) {
7949
8318
  return parts.join("\n").replace(/,/g, ", ");
7950
8319
  }
7951
8320
  /**
8321
+ * Render an Xray entry as the indented JSON blob carried in the requirement's
8322
+ * code field (the ionchannel pattern), so Heimdall's CODE tab shows the raw
8323
+ * finding. The object is reconstructed with a fixed key order and full field set
8324
+ * matching the Go twin's struct marshal, keeping Go and TS byte-identical
8325
+ * (verified by the shared snapshot test).
8326
+ */
8327
+ function marshalEntryCode(entry) {
8328
+ const cv = entry.component_versions;
8329
+ const md = cv?.more_details;
8330
+ const cves = md?.cves;
8331
+ const codeObject = {
8332
+ id: entry.id ?? "",
8333
+ severity: entry.severity ?? "",
8334
+ summary: entry.summary ?? "",
8335
+ issue_type: entry.issue_type ?? "",
8336
+ provider: entry.provider ?? "",
8337
+ component: entry.component ?? "",
8338
+ source_id: entry.source_id ?? "",
8339
+ source_comp_id: entry.source_comp_id ?? "",
8340
+ component_versions: {
8341
+ id: cv?.id ?? "",
8342
+ vulnerable_versions: cv?.vulnerable_versions ?? null,
8343
+ fixed_versions: cv?.fixed_versions ?? null,
8344
+ more_details: {
8345
+ cves: cves ? cves.map((c) => ({
8346
+ cve: c.cve ?? "",
8347
+ cwe: c.cwe ?? null,
8348
+ cvss_v2: c.cvss_v2 ?? "",
8349
+ cvss_v3: c.cvss_v3 ?? ""
8350
+ })) : null,
8351
+ description: md?.description ?? "",
8352
+ provider: md?.provider ?? ""
8353
+ }
8354
+ },
8355
+ edited: entry.edited ?? ""
8356
+ };
8357
+ return JSON.stringify(codeObject, null, 2);
8358
+ }
8359
+ /**
7952
8360
  * Builds a single EvaluatedRequirement from a group of entries sharing an ID.
7953
8361
  */
7954
8362
  function buildRequirement$7(entryID, entries, scanTime) {
7955
8363
  const rep = entries[0];
7956
8364
  const cweIDs = extractCWEs$1(rep);
8365
+ const cveIDs = extractCVEs$1(rep);
7957
8366
  const nist = mapCWEToNIST(cweIDs, DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
7958
8367
  const tags = {
7959
8368
  nist,
7960
8369
  cci: nistToCci(nist)
7961
8370
  };
7962
- if (cweIDs.length > 0) tags["cweid"] = cweIDs;
8371
+ if (cveIDs.length > 0) tags["cve"] = cveIDs;
7963
8372
  const descriptions = [{
7964
8373
  label: "default",
7965
8374
  data: formatDescription(rep)
@@ -7970,9 +8379,13 @@ function buildRequirement$7(entryID, entries, scanTime) {
7970
8379
  startTime: scanTime
7971
8380
  }));
7972
8381
  const req = createRequirement(entryID, rep.summary, descriptions, severityToImpact$1(rep.severity), results, { tags });
8382
+ req.code = marshalEntryCode(rep);
7973
8383
  const controlType = deriveControlTypeFromTags(nist);
7974
8384
  if (controlType !== void 0) req.controlType = controlType;
7975
8385
  req.verificationMethod = VerificationMethodEnum.Automated;
8386
+ if (cweIDs.length > 0) req.cwe = cweIDs;
8387
+ const cvss = buildCvssEntries$2(rep.component_versions?.more_details?.cves);
8388
+ if (cvss.length > 0) req.cvss = cvss;
7976
8389
  const pkg = buildAffectedPackageFromEntry(rep);
7977
8390
  if (pkg) req.affectedPackages = [pkg];
7978
8391
  return req;
@@ -8051,7 +8464,6 @@ async function convertJfrogXrayToHdf(input, converterVersion = "1.0.0") {
8051
8464
  generatorName: "jfrog-xray-to-hdf",
8052
8465
  converterVersion,
8053
8466
  toolName: "JFrog Xray",
8054
- toolFormat: "JSON",
8055
8467
  baselines: [createMinimalBaseline("JFrog Xray Scan", requirements, { resultsChecksum })],
8056
8468
  components: [{
8057
8469
  name: "JFrog Xray Scan",
@@ -8070,6 +8482,43 @@ function extractCWEs(description) {
8070
8482
  return extractCWEIDs(description).map((id) => `CWE-${id}`);
8071
8483
  }
8072
8484
  /**
8485
+ * Collects the distinct CVE identifiers a vulnerability carries in its cves[]
8486
+ * field, preserving first-seen order. The requirement ID is a
8487
+ * name/package/version composite (not the CVE), so tags.cve is where the CVE
8488
+ * list lives.
8489
+ */
8490
+ function extractCVEs(vuln) {
8491
+ const out = [];
8492
+ const seen = /* @__PURE__ */ new Set();
8493
+ for (const cve of vuln.cves ?? []) if (cve && !seen.has(cve)) {
8494
+ seen.add(cve);
8495
+ out.push(cve);
8496
+ }
8497
+ return out;
8498
+ }
8499
+ /**
8500
+ * Assembles the structured requirement.cvss[] from the scoring a vulnerability
8501
+ * carries. NeuVector emits a CVSS v3 vector (version-prefixed) and, separately,
8502
+ * a legacy v2 vector (prefix-less). The v3 metric is preferred; the v2 metric
8503
+ * is the fallback. A vulnerability with neither vector contributes no entry —
8504
+ * its score still drives impact.
8505
+ */
8506
+ function buildCvssEntries$1(vuln) {
8507
+ if (vuln.vectors_v3) return [buildCvss$2({
8508
+ version: cvssVersionFromVector(vuln.vectors_v3),
8509
+ baseScore: vuln.score_v3 > 0 ? vuln.score_v3 : void 0,
8510
+ baseVector: vuln.vectors_v3,
8511
+ source: "NeuVector"
8512
+ })];
8513
+ if (vuln.vectors) return [buildCvss$2({
8514
+ version: cvssVersionFromString("2.0"),
8515
+ baseScore: vuln.score > 0 ? vuln.score : void 0,
8516
+ baseVector: vuln.vectors,
8517
+ source: "NeuVector"
8518
+ })];
8519
+ return [];
8520
+ }
8521
+ /**
8073
8522
  * Computes the HDF impact from NeuVector CVSS scores.
8074
8523
  * Prefers CVSS v3 score; falls back to CVSS v2 if v3 is 0.
8075
8524
  * Impact is normalized to 0.0-1.0 by dividing by 10.
@@ -8098,29 +8547,66 @@ function vulnMessage(vuln) {
8098
8547
  if (!vuln.fixed_version) return `Vulnerable package ${vuln.package_name} is at version ${vuln.package_version}. No fixed version available.`;
8099
8548
  return `Vulnerable package ${vuln.package_name} is at version ${vuln.package_version}. Update to fixed version ${vuln.fixed_version}.`;
8100
8549
  }
8550
+ const WHITESPACE_RUN = /[ \t\n\r]+/g;
8551
+ const CODE_DESC_SNIPPET_RUNES = 100;
8552
+ /**
8553
+ * Returns the CVSS score used in code_desc: CVSS v3 preferred, else v2.
8554
+ */
8555
+ function cvssScore(vuln) {
8556
+ return vuln.score_v3 > 0 ? vuln.score_v3 : vuln.score;
8557
+ }
8558
+ /**
8559
+ * Collapses the description to a single line and truncates it to
8560
+ * CODE_DESC_SNIPPET_RUNES code points (Array.from matches Go's []rune).
8561
+ */
8562
+ function descSnippet(description) {
8563
+ const collapsed = description.replace(WHITESPACE_RUN, " ").trim();
8564
+ const runes = Array.from(collapsed);
8565
+ return runes.length > CODE_DESC_SNIPPET_RUNES ? runes.slice(0, CODE_DESC_SNIPPET_RUNES).join("") + "…" : collapsed;
8566
+ }
8567
+ /**
8568
+ * Builds the pipe-joined result code_desc from the fields the vuln carries:
8569
+ * package@version | name | CVSS score | description snippet. Only parts the
8570
+ * source actually provides are included.
8571
+ */
8572
+ function buildCodeDesc$3(vuln) {
8573
+ const parts = [];
8574
+ if (vuln.package_name) parts.push(vuln.package_version ? `${vuln.package_name}@${vuln.package_version}` : vuln.package_name);
8575
+ if (vuln.name) parts.push(vuln.name);
8576
+ const score = cvssScore(vuln);
8577
+ if (score > 0) parts.push(`CVSS ${score}`);
8578
+ const snippet = descSnippet(vuln.description);
8579
+ if (snippet) parts.push(snippet);
8580
+ return parts.join(" | ");
8581
+ }
8101
8582
  /**
8102
8583
  * Builds a single EvaluatedRequirement from a NeuVector vulnerability.
8103
8584
  */
8104
8585
  function buildRequirement$6(vuln, scanTime) {
8105
8586
  const cweIDs = extractCWEs(vuln.description);
8587
+ const cveIDs = extractCVEs(vuln);
8106
8588
  const nist = mapCWEToNIST(cweIDs, DEFAULT_REMEDIATION_NIST_TAGS);
8107
8589
  const tags = {
8108
8590
  nist,
8109
8591
  cci: nistToCci(nist)
8110
8592
  };
8111
- if (cweIDs.length > 0) tags["cwe"] = cweIDs;
8593
+ if (cveIDs.length > 0) tags["cve"] = cveIDs;
8112
8594
  const descriptions = [{
8113
8595
  label: "default",
8114
8596
  data: vuln.description
8115
8597
  }];
8116
8598
  const results = [createResult(ResultStatus.Failed, vulnMessage(vuln), {
8117
- codeDesc: "",
8599
+ codeDesc: buildCodeDesc$3(vuln),
8118
8600
  startTime: scanTime
8119
8601
  })];
8120
8602
  const req = createRequirement(vulnID(vuln), vulnTitle(vuln), descriptions, getImpact$4(vuln), results, { tags });
8603
+ req.code = JSON.stringify(vuln, null, 2);
8121
8604
  const controlType = deriveControlTypeFromTags(nist);
8122
8605
  if (controlType !== void 0) req.controlType = controlType;
8123
8606
  req.verificationMethod = VerificationMethodEnum.Automated;
8607
+ if (cweIDs.length > 0) req.cwe = cweIDs;
8608
+ const cvss = buildCvssEntries$1(vuln);
8609
+ if (cvss.length > 0) req.cvss = cvss;
8124
8610
  const cpe23 = (vuln.cpes ?? []).find((c) => /^cpe:2\.3:[aho]:/.test(c));
8125
8611
  const pkg = buildAffectedPackage$1({
8126
8612
  name: vuln.package_name,
@@ -8182,7 +8668,6 @@ async function convertNeuvectorToHdf(input, converterVersion = "1.0.0") {
8182
8668
  generatorName: "neuvector-to-hdf",
8183
8669
  converterVersion,
8184
8670
  toolName: "NeuVector",
8185
- toolFormat: "JSON",
8186
8671
  baselines: [createMinimalBaseline("NeuVector Scan", requirements, {
8187
8672
  resultsChecksum,
8188
8673
  title: imageTitle(scan.report)
@@ -8201,7 +8686,9 @@ async function convertNeuvectorToHdf(input, converterVersion = "1.0.0") {
8201
8686
  //#endregion
8202
8687
  //#region converters/fortify-to-hdf/typescript/converter.ts
8203
8688
  const NIST_REFERENCE_NAME = "Standards Mapping - NIST Special Publication 800-53 Revision 4";
8689
+ const CWE_REFERENCE_NAME = "Standards Mapping - Common Weakness Enumeration";
8204
8690
  const NIST_PATTERN = /[a-zA-Z]{2}-\d+/g;
8691
+ const CWE_ID_PATTERN = /\d+/g;
8205
8692
  const NAMED_ENTITIES$1 = {
8206
8693
  lt: "<",
8207
8694
  gt: ">",
@@ -8241,6 +8728,18 @@ function extractNISTFromReferences(refs) {
8241
8728
  }
8242
8729
  return [];
8243
8730
  }
8731
+ function extractCWEFromReferences(refs) {
8732
+ for (const ref of refs) if (ref.Author === CWE_REFERENCE_NAME) {
8733
+ const matches = ref.Title?.match(CWE_ID_PATTERN);
8734
+ if (matches && matches.length > 0) return matches.map((m) => `CWE-${m}`);
8735
+ }
8736
+ return [];
8737
+ }
8738
+ function mergeCweNist(nist, cweIDs) {
8739
+ const merged = [...nist];
8740
+ for (const ctrl of mapCWEToNIST(cweIDs, [])) if (!merged.includes(ctrl)) merged.push(ctrl);
8741
+ return merged;
8742
+ }
8244
8743
  function formatSnippet(snippet) {
8245
8744
  const text = (snippet.Text ?? "").trim();
8246
8745
  return `Path: ${snippet.File ?? ""}\nStartLine: ${snippet.StartLine ?? ""}, EndLine: ${snippet.EndLine ?? ""}\nCode:\n${text}`;
@@ -8263,8 +8762,26 @@ function buildCodeDesc$2(vuln, snippetMap) {
8263
8762
  if (parts.length === 0) return `ClassID: ${vuln.ClassInfo?.ClassID ?? ""}, InstanceID: ${vuln.InstanceInfo?.InstanceID ?? ""}`;
8264
8763
  return parts.join("\n");
8265
8764
  }
8765
+ function buildRequirementCode(vulns, snippetMap) {
8766
+ if (vulns.length === 0) return void 0;
8767
+ const parts = [];
8768
+ const entries = ensureArray(vulns[0].AnalysisInfo?.Unified?.Trace?.Primary?.Entry);
8769
+ for (const entry of entries) {
8770
+ if (!entry.Node) continue;
8771
+ const snippetID = entry.Node.SourceLocation?.snippet;
8772
+ if (!snippetID) continue;
8773
+ const snippet = snippetMap.get(snippetID);
8774
+ if (!snippet) continue;
8775
+ const text = (snippet.Text ?? "").trim();
8776
+ if (text) parts.push(text);
8777
+ }
8778
+ if (parts.length === 0) return void 0;
8779
+ return parts.join("\n");
8780
+ }
8266
8781
  function buildRequirement$5(desc, vulns, snippetMap, startTimeStr) {
8267
- let nistTags = extractNISTFromReferences(ensureArray(desc.References?.Reference));
8782
+ const refs = ensureArray(desc.References?.Reference);
8783
+ const cweIDs = extractCWEFromReferences(refs);
8784
+ let nistTags = mergeCweNist(extractNISTFromReferences(refs), cweIDs);
8268
8785
  if (nistTags.length === 0) nistTags = [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1];
8269
8786
  const cciTags = nistToCci(nistTags);
8270
8787
  const tags = buildNistCciTags(nistTags, cciTags);
@@ -8280,7 +8797,7 @@ function buildRequirement$5(desc, vulns, snippetMap, startTimeStr) {
8280
8797
  data: stripFvdlMarkup(desc.Recommendations)
8281
8798
  });
8282
8799
  let impact = 0;
8283
- if (vulns.length > 0) impact = parseFloat(vulns[0].ClassInfo?.DefaultSeverity ?? "0") / 5;
8800
+ if (vulns.length > 0) impact = parseFloat(vulns[0].InstanceInfo?.InstanceSeverity ?? "0") / 5;
8284
8801
  const { items: limitedVulns, truncated } = limitArray(vulns);
8285
8802
  /* v8 ignore next -- truncation only triggers with >100K items */
8286
8803
  if (truncated) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${vulns.length})`);
@@ -8298,8 +8815,11 @@ function buildRequirement$5(desc, vulns, snippetMap, startTimeStr) {
8298
8815
  results,
8299
8816
  verificationMethod: VerificationMethodEnum.Automated
8300
8817
  };
8818
+ if (cweIDs.length > 0) req.cwe = cweIDs;
8301
8819
  const controlType = deriveControlTypeFromTags(nistTags);
8302
8820
  if (controlType !== void 0) req.controlType = controlType;
8821
+ const code = buildRequirementCode(vulns, snippetMap);
8822
+ if (code !== void 0) req.code = code;
8303
8823
  return req;
8304
8824
  }
8305
8825
  /**
@@ -8456,6 +8976,8 @@ function buildRequirement$4(rec, scanTime) {
8456
8976
  const controlType = deriveControlTypeFromTags(nist);
8457
8977
  if (controlType !== void 0) req.controlType = controlType;
8458
8978
  req.verificationMethod = VerificationMethodEnum.Automated;
8979
+ const cvss = buildCvssEntries(rec);
8980
+ if (cvss) req.cvss = cvss;
8459
8981
  if (rec["CVE ID"]) {
8460
8982
  const pkg = buildAffectedPackageFromRecord(rec);
8461
8983
  if (pkg) req.affectedPackages = [pkg];
@@ -8463,6 +8985,32 @@ function buildRequirement$4(rec, scanTime) {
8463
8985
  return req;
8464
8986
  }
8465
8987
  /**
8988
+ * Maps the CSV CVSS column to a structured cvss[] entry. Prisma emits a bare
8989
+ * base score with no vector, so version defaults to 3.1 via the shared helper.
8990
+ * Config rows carry "0.00" (or blank); only a positive score yields an entry,
8991
+ * and the source is the associated CVE when present.
8992
+ */
8993
+ function buildCvssEntries(rec) {
8994
+ const score = parseCvssScore(rec.CVSS);
8995
+ if (score === void 0) return void 0;
8996
+ return [buildCvss$2({
8997
+ version: cvssVersionFromString(void 0),
8998
+ baseScore: score,
8999
+ source: rec["CVE ID"] || void 0
9000
+ })];
9001
+ }
9002
+ /**
9003
+ * Parses the CVSS column, returning undefined for blank, non-numeric, or
9004
+ * non-positive scores (the placeholder value on config rows).
9005
+ */
9006
+ function parseCvssScore(field) {
9007
+ const s = field?.trim();
9008
+ if (!s) return void 0;
9009
+ const v = Number.parseFloat(s);
9010
+ if (!Number.isFinite(v) || v <= 0) return void 0;
9011
+ return v;
9012
+ }
9013
+ /**
8466
9014
  * Distro slugs in Prisma look like `redhat-RHEL7`, `debian-buster`,
8467
9015
  * `alpine-3.14`, `ubuntu-20.04`. Only the leading vendor segment is
8468
9016
  * mapped — unknown vendors fall back to `generic` rather than guessing.
@@ -8555,7 +9103,6 @@ async function convertPrismaToHdf(input, converterVersion = "1.0.0") {
8555
9103
  generatorName: "prisma-to-hdf",
8556
9104
  converterVersion,
8557
9105
  toolName: "Prisma Cloud",
8558
- toolFormat: "CSV",
8559
9106
  baselines,
8560
9107
  components,
8561
9108
  timestamp: scanTime
@@ -8612,6 +9159,36 @@ function formatControlDesc(vuln) {
8612
9159
  return parts.join("\n");
8613
9160
  }
8614
9161
  /**
9162
+ * Returns the parsed Base-metric score from a CVSS block, or undefined when
9163
+ * there is no Base score or its value does not parse as a finite number.
9164
+ */
9165
+ function baseScoreFromBlock(block) {
9166
+ const scores = block?.score ?? [];
9167
+ for (const s of scores) if ((s.type ?? "").toLowerCase() === "base") {
9168
+ const v = parseFloat((s.value ?? "").trim());
9169
+ return Number.isFinite(v) ? v : void 0;
9170
+ }
9171
+ }
9172
+ /**
9173
+ * Assembles the structured cvss[] from the <cvss> (3.0) and <cvss31> (3.1)
9174
+ * classification blocks. Each block carrying a vector or a Base score becomes
9175
+ * one entry; the schema version derives from the vector prefix.
9176
+ */
9177
+ function buildNetsparkerCvss(classification) {
9178
+ const out = [];
9179
+ for (const block of [classification?.cvss, classification?.cvss31]) {
9180
+ const baseScore = baseScoreFromBlock(block);
9181
+ const vector = block?.vector ?? "";
9182
+ if (!vector && baseScore === void 0) continue;
9183
+ out.push(buildCvss$2({
9184
+ version: cvssVersionFromVector(vector),
9185
+ baseScore,
9186
+ baseVector: vector
9187
+ }));
9188
+ }
9189
+ return out;
9190
+ }
9191
+ /**
8615
9192
  * Performs dual NIST mapping from both CWE and OWASP IDs.
8616
9193
  * Returns a deduplicated sorted list of NIST controls, falling back to
8617
9194
  * DEFAULT_STATIC_ANALYSIS_NIST_TAGS if no mappings are found.
@@ -8683,6 +9260,10 @@ function buildRequirement$3(vuln, initiated) {
8683
9260
  const controlType = deriveControlTypeFromTags(nist);
8684
9261
  if (controlType !== void 0) req.controlType = controlType;
8685
9262
  req.verificationMethod = VerificationMethodEnum.Automated;
9263
+ const rawRequest = vuln["http-request"]?.content;
9264
+ if (rawRequest) req.code = rawRequest;
9265
+ const cvss = buildNetsparkerCvss(vuln.classification);
9266
+ if (cvss.length > 0) req.cvss = cvss;
8686
9267
  return req;
8687
9268
  }
8688
9269
  /**
@@ -8696,7 +9277,7 @@ async function convertNetsparkerToHdf(input, converterVersion = "1.0.0") {
8696
9277
  if (!input || input.trim().length === 0) throw new Error("netsparker: empty input");
8697
9278
  validateInputSize(input, "netsparker");
8698
9279
  const resultsChecksum = await inputChecksum(input);
8699
- const parsed = parseXmlWithArrays(input, ["vulnerability"]);
9280
+ const parsed = parseXmlWithArrays(input, ["vulnerability", "score"]);
8700
9281
  const isInvicti = !!parsed["invicti-enterprise"];
8701
9282
  const data = parsed["invicti-enterprise"] ?? parsed["netsparker-enterprise"] ?? {};
8702
9283
  if (!data.vulnerabilities && !data.target) throw new Error("netsparker: invalid XML — missing expected root element");
@@ -8717,7 +9298,6 @@ async function convertNetsparkerToHdf(input, converterVersion = "1.0.0") {
8717
9298
  generatorName: "netsparker-to-hdf",
8718
9299
  converterVersion,
8719
9300
  toolName,
8720
- toolFormat: "XML",
8721
9301
  baselines: [createMinimalBaseline("Netsparker Scan", requirements, {
8722
9302
  resultsChecksum,
8723
9303
  title: `${toolName} Enterprise Scan ID: ${target["scan-id"] ?? ""} URL: ${target.url ?? ""}`
@@ -8851,7 +9431,6 @@ async function convertScoutsuiteToHdf(input, converterVersion = "1.0.0") {
8851
9431
  generatorName: "scoutsuite-to-hdf",
8852
9432
  converterVersion,
8853
9433
  toolName: "ScoutSuite",
8854
- toolFormat: "JSON",
8855
9434
  toolVersion: report.last_run.version,
8856
9435
  baselines: [baseline],
8857
9436
  components: [{
@@ -9015,7 +9594,6 @@ async function convertConveyorToHdf(input, converterVersion = "1.0.0") {
9015
9594
  generatorName: "conveyor-to-hdf",
9016
9595
  converterVersion,
9017
9596
  toolName: "Conveyor",
9018
- toolFormat: "JSON",
9019
9597
  baselines,
9020
9598
  components: [{
9021
9599
  name: targetName,
@@ -9097,23 +9675,6 @@ function formatRecommendations(rec) {
9097
9675
  }
9098
9676
  return parts.join("\n");
9099
9677
  }
9100
- /** Format CWE data for the cweid tag. */
9101
- function formatCWEData(cwes) {
9102
- return cwes.map((c) => {
9103
- let entry = `CWE-${attr(c, "cweid")}: ${attr(c, "cwename")}`;
9104
- const categories = [
9105
- ["pcrirelated", attr(c, "pcirelated")],
9106
- ["owasp", attr(c, "owasp")],
9107
- ["sans", attr(c, "sans")],
9108
- ["certc", attr(c, "certc")],
9109
- ["certcpp", attr(c, "certcpp")],
9110
- ["certjava", attr(c, "certjava")],
9111
- ["owaspmobile", attr(c, "owaspmobile")]
9112
- ];
9113
- for (const [name, val] of categories) if (val) entry += `${name}: ${val}\n`;
9114
- return entry;
9115
- }).join("\n");
9116
- }
9117
9678
  /** Format CWE descriptions for the cweDescription tag. */
9118
9679
  function formatCWEDesc(cwes) {
9119
9680
  return cwes.map((c) => {
@@ -9174,6 +9735,54 @@ function formatSCACodeDesc(comp) {
9174
9735
  }
9175
9736
  return parts.join("\n");
9176
9737
  }
9738
+ /**
9739
+ * Synthesize a static flaw's source-context locus from its function prototype
9740
+ * and source-file position. Returns '' when the flaw carries neither a prototype
9741
+ * nor a source location (the NOT-IN-SOURCE case).
9742
+ */
9743
+ function synthesizeFlawCode(flaw) {
9744
+ let locus = attr(flaw, "sourcefilepath") + attr(flaw, "sourcefile");
9745
+ const line = attr(flaw, "line");
9746
+ if (locus && line) locus += `:${line}`;
9747
+ const proto = attr(flaw, "functionprototype");
9748
+ if (proto && locus) return `${proto} at ${locus}`;
9749
+ if (proto) return proto;
9750
+ return locus;
9751
+ }
9752
+ /**
9753
+ * Serialize a CVE and its affected components as indented JSON. Field order is
9754
+ * load-bearing: it must match the Go twin's struct declaration order so the two
9755
+ * `code` strings are byte-identical.
9756
+ */
9757
+ function buildSCACode(vuln, components) {
9758
+ const entry = {
9759
+ cve_id: attr(vuln, "cve_id"),
9760
+ cvss_score: attr(vuln, "cvss_score"),
9761
+ severity: attr(vuln, "severity"),
9762
+ cwe_id: attr(vuln, "cwe_id"),
9763
+ first_found_date: attr(vuln, "first_found_date"),
9764
+ cve_summary: attr(vuln, "cve_summary"),
9765
+ severity_desc: attr(vuln, "severity_desc"),
9766
+ components: components.map((comp) => {
9767
+ const filePathsElem = comp.file_paths;
9768
+ const fps = filePathsElem ? ensureArray(filePathsElem.file_path) : [];
9769
+ return {
9770
+ component_id: attr(comp, "component_id"),
9771
+ file_name: attr(comp, "file_name"),
9772
+ sha1: attr(comp, "sha1"),
9773
+ version: attr(comp, "version"),
9774
+ library: attr(comp, "library"),
9775
+ library_id: attr(comp, "library_id"),
9776
+ vendor: attr(comp, "vendor"),
9777
+ description: attr(comp, "description"),
9778
+ max_cvss_score: attr(comp, "max_cvss_score"),
9779
+ added_date: attr(comp, "added_date"),
9780
+ file_paths: fps.map((fp) => attr(fp, "value"))
9781
+ };
9782
+ })
9783
+ };
9784
+ return JSON.stringify(entry, null, 2);
9785
+ }
9177
9786
  /** Build CWE-based requirements from severity categories. */
9178
9787
  function buildCWERequirements(severities, firstBuildDate) {
9179
9788
  const requirements = [];
@@ -9187,14 +9796,14 @@ function buildCWERequirements(severities, firstBuildDate) {
9187
9796
  /** Build a single CWE-based requirement from a category. */
9188
9797
  function buildCWERequirement(cat, impact, firstBuildDate) {
9189
9798
  const cwes = ensureArray(cat.cwe);
9190
- const nist = mapCWEToNIST(cwes.map((c) => attr(c, "cweid")).filter(Boolean), DEFAULT_REMEDIATION_NIST_TAGS$1);
9799
+ const cweIDs = cwes.map((c) => attr(c, "cweid")).filter(Boolean);
9800
+ const nist = mapCWEToNIST(cweIDs, DEFAULT_REMEDIATION_NIST_TAGS$1);
9191
9801
  const cciTags = nistToCci(nist);
9192
9802
  const extras = {};
9193
- const cweData = formatCWEData(cwes);
9194
- if (cweData) extras.cweid = cweData;
9195
9803
  const cweDescStr = formatCWEDesc(cwes);
9196
9804
  if (cweDescStr) extras.cweDescription = cweDescStr;
9197
9805
  const tags = buildNistCciTags(nist, cciTags, extras);
9806
+ const cweList = cweIDs.map((id) => `CWE-${id}`);
9198
9807
  const descriptions = [{
9199
9808
  label: "default",
9200
9809
  data: formatDesc(cat.desc)
@@ -9216,6 +9825,10 @@ function buildCWERequirement(cat, impact, firstBuildDate) {
9216
9825
  const staticflaws = c.staticflaws;
9217
9826
  return ensureArray(staticflaws?.flaw).map((flaw) => attr(flaw, "sourcefile")).filter(Boolean);
9218
9827
  }).join("\n");
9828
+ const codeLines = cwes.flatMap((c) => {
9829
+ const staticflaws = c.staticflaws;
9830
+ return ensureArray(staticflaws?.flaw).map((flaw) => synthesizeFlawCode(flaw)).filter(Boolean);
9831
+ });
9219
9832
  const req = createRequirement(attr(cat, "categoryid"), attr(cat, "categoryname"), descriptions, impact, results, {
9220
9833
  tags,
9221
9834
  sourceLocation: sourceRef ? { ref: sourceRef } : void 0
@@ -9223,6 +9836,8 @@ function buildCWERequirement(cat, impact, firstBuildDate) {
9223
9836
  const controlType = deriveControlTypeFromTags(nist);
9224
9837
  if (controlType !== void 0) req.controlType = controlType;
9225
9838
  req.verificationMethod = VerificationMethodEnum.Automated;
9839
+ if (cweList.length > 0) req.cwe = cweList;
9840
+ if (codeLines.length > 0) req.code = codeLines.join("\n");
9226
9841
  return req;
9227
9842
  }
9228
9843
  /** Build CVE-based requirements from SCA components. */
@@ -9257,6 +9872,30 @@ function buildCVERequirements(sca, firstBuildDate) {
9257
9872
  return buildCVERequirement(entry.vuln, entry.components, firstBuildDate);
9258
9873
  });
9259
9874
  }
9875
+ /**
9876
+ * Assemble the structured CVSS entry for an SCA CVE. Veracode reports a bare
9877
+ * numeric base score (no vector, no version), so the version defaults to 3.1 via
9878
+ * the shared helper. When the vulnerability itself carries no cvss_score, the
9879
+ * first affected component's max_cvss_score is used as a fallback. A missing or
9880
+ * non-numeric score yields no entry.
9881
+ */
9882
+ function buildVeracodeCvss(vuln, components) {
9883
+ let scoreStr = attr(vuln, "cvss_score");
9884
+ if (!scoreStr) for (const comp of components) {
9885
+ const max = attr(comp, "max_cvss_score");
9886
+ if (max) {
9887
+ scoreStr = max;
9888
+ break;
9889
+ }
9890
+ }
9891
+ if (!scoreStr) return [];
9892
+ const score = Number(scoreStr);
9893
+ if (!Number.isFinite(score)) return [];
9894
+ return [buildCvss$2({
9895
+ version: cvssVersionFromString(void 0),
9896
+ baseScore: score
9897
+ })];
9898
+ }
9260
9899
  /** Build a single CVE-based requirement. */
9261
9900
  function buildCVERequirement(vuln, components, firstBuildDate) {
9262
9901
  const impact = veracodeSeverityToImpact(attr(vuln, "severity"));
@@ -9265,9 +9904,7 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
9265
9904
  if (cweID) nist = mapCWEToNIST([cweID.replace(/^CWE-/, "")], DEFAULT_REMEDIATION_NIST_TAGS$1);
9266
9905
  else nist = DEFAULT_REMEDIATION_NIST_TAGS$1;
9267
9906
  const cciTags = nistToCci(nist);
9268
- const extras = {};
9269
- if (cweID) extras.cwe = cweID;
9270
- const tags = buildNistCciTags(nist, cciTags, extras);
9907
+ const tags = buildNistCciTags(nist, cciTags, {});
9271
9908
  const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
9272
9909
  const results = components.map((comp) => createResult(ResultStatus.Failed, void 0, {
9273
9910
  codeDesc: formatSCACodeDesc(comp),
@@ -9291,6 +9928,10 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
9291
9928
  const controlType = deriveControlTypeFromTags(nist);
9292
9929
  if (controlType !== void 0) req.controlType = controlType;
9293
9930
  req.verificationMethod = VerificationMethodEnum.Automated;
9931
+ const cvss = buildVeracodeCvss(vuln, components);
9932
+ if (cvss.length > 0) req.cvss = cvss;
9933
+ if (cweID) req.cwe = [cweID];
9934
+ req.code = buildSCACode(vuln, components);
9294
9935
  return req;
9295
9936
  }
9296
9937
  /**
@@ -9336,7 +9977,6 @@ async function convertVeracodeToHdf(input, converterVersion = "1.0.0") {
9336
9977
  generatorName: "veracode-to-hdf",
9337
9978
  converterVersion,
9338
9979
  toolName: "Veracode",
9339
- toolFormat: "XML",
9340
9980
  baselines: [baseline],
9341
9981
  components: [{
9342
9982
  name: targetName,
@@ -9444,7 +10084,6 @@ async function convertMsftSecureScoreToHdf(input, converterVersion = "1.0.0") {
9444
10084
  generatorName: "msft-secure-score-to-hdf",
9445
10085
  converterVersion,
9446
10086
  toolName: "Microsoft Secure Score",
9447
- toolFormat: "JSON",
9448
10087
  baselines: combined.secureScore.value.map((ss) => {
9449
10088
  if (!tenantId) tenantId = ss.azureTenantId;
9450
10089
  const { items: limitedControlScores, truncated } = limitArray(ss.controlScores);
@@ -9680,7 +10319,6 @@ async function convertMsftDefenderCloudToHdf(input, converterVersion = "1.0.0")
9680
10319
  generatorName: "msft-defender-cloud-to-hdf",
9681
10320
  converterVersion,
9682
10321
  toolName: "Microsoft Defender for Cloud",
9683
- toolFormat: "JSON",
9684
10322
  baselines: [baseline],
9685
10323
  components,
9686
10324
  timestamp: scanTime
@@ -9956,8 +10594,6 @@ function buildRuleObj(req) {
9956
10594
  }
9957
10595
  const rationale = findDescription(req.descriptions, "rationale");
9958
10596
  if (rationale) rule.rationale = wrap(rationale);
9959
- const fixtext = findDescription(req.descriptions, "fix");
9960
- if (fixtext) rule.fixtext = wrap(fixtext);
9961
10597
  const idents = [];
9962
10598
  if (req.tags && Array.isArray(req.tags["cci"])) for (const cci of req.tags["cci"]) idents.push({
9963
10599
  [`${ATTR}system`]: "http://cyber.mil/cci",
@@ -9968,6 +10604,8 @@ function buildRuleObj(req) {
9968
10604
  "#text": n
9969
10605
  });
9970
10606
  if (idents.length > 0) rule.ident = idents;
10607
+ const fixtext = findDescription(req.descriptions, "fix");
10608
+ if (fixtext) rule.fixtext = wrap(fixtext);
9971
10609
  const checks = [];
9972
10610
  const checkContent = findDescription(req.descriptions, "check");
9973
10611
  if (checkContent) checks.push({
@@ -9998,12 +10636,19 @@ function buildTestResultObj(hdfData, baseline) {
9998
10636
  if (target.ipAddress) testResult["target-address"] = wrap(target.ipAddress);
9999
10637
  } else testResult.target = wrap("unknown");
10000
10638
  const ruleResults = [];
10639
+ let passed = 0;
10640
+ let scorable = 0;
10001
10641
  for (const req of baseline.requirements) {
10002
10642
  const ruleIdRef = sanitizeXccdfId("xccdf_hdf_rule_" + req.id + "_rule");
10003
10643
  for (const result of req.results) {
10644
+ const status = hdfStatusToXccdf(result.status);
10645
+ if (status === "pass") {
10646
+ passed++;
10647
+ scorable++;
10648
+ } else if (status === "fail") scorable++;
10004
10649
  const rr = {
10005
10650
  [`${ATTR}idref`]: ruleIdRef,
10006
- result: wrap(hdfStatusToXccdf(result.status))
10651
+ result: wrap(status)
10007
10652
  };
10008
10653
  rr[`${ATTR}time`] = typeof result.startTime === "string" ? result.startTime : result.startTime.toISOString();
10009
10654
  if (result.message) rr.message = wrap(result.message);
@@ -10015,6 +10660,12 @@ function buildTestResultObj(hdfData, baseline) {
10015
10660
  }
10016
10661
  }
10017
10662
  testResult["rule-result"] = ruleResults;
10663
+ const score = scorable > 0 ? passed / scorable * 100 : 0;
10664
+ testResult.score = {
10665
+ [`${ATTR}system`]: "urn:xccdf:scoring:default",
10666
+ [`${ATTR}maximum`]: "100.000000",
10667
+ "#text": score.toFixed(6)
10668
+ };
10018
10669
  return testResult;
10019
10670
  }
10020
10671
  //#endregion
@@ -10207,23 +10858,40 @@ async function convertHdfToOscalSar(input) {
10207
10858
  return JSON.stringify(doc, null, 2);
10208
10859
  }
10209
10860
  /**
10861
+ * Human-readable assessment-tool label from the HDF tool/generator identity,
10862
+ * falling back when neither is present.
10863
+ */
10864
+ function toolPartyName(hdfResults) {
10865
+ const tool = hdfResults.tool;
10866
+ if (tool?.name) return tool.version ? `${tool.name} ${tool.version}` : tool.name;
10867
+ const gen = hdfResults.generator;
10868
+ if (gen?.name) return gen.version ? `${gen.name} ${gen.version}` : gen.name;
10869
+ return "HDF Assessment Tool";
10870
+ }
10871
+ /**
10210
10872
  * Constructs the full OSCAL assessment-results document from HDF results.
10211
10873
  */
10212
10874
  function buildOSCALDocument(hdfResults) {
10213
10875
  let timestamp = formatTimestampSeconds(/* @__PURE__ */ new Date());
10214
10876
  const documentTime = hdfTime(hdfResults.timestamp);
10215
10877
  if (documentTime) timestamp = formatTimestampSeconds(documentTime);
10878
+ const toolActorUuid = crypto.randomUUID();
10216
10879
  const metadata = {
10217
10880
  title: "HDF Assessment Results Export",
10218
10881
  "last-modified": timestamp,
10219
10882
  version: "1.0.0",
10220
- "oscal-version": OSCAL_VERSION
10883
+ "oscal-version": OSCAL_VERSION,
10884
+ parties: [{
10885
+ uuid: toolActorUuid,
10886
+ type: "organization",
10887
+ name: toolPartyName(hdfResults)
10888
+ }]
10221
10889
  };
10222
10890
  let importAP;
10223
10891
  if (hdfResults.planRef && hdfResults.planRef !== "") importAP = { href: hdfResults.planRef };
10224
10892
  else importAP = { href: "#" };
10225
10893
  const results = [];
10226
- for (const baseline of hdfResults.baselines) results.push(baselineToResult(baseline, timestamp));
10894
+ for (const baseline of hdfResults.baselines) results.push(baselineToResult(baseline, timestamp, toolActorUuid));
10227
10895
  return { "assessment-results": {
10228
10896
  uuid: crypto.randomUUID(),
10229
10897
  metadata,
@@ -10262,7 +10930,7 @@ function assessmentStart(baseline, fallback) {
10262
10930
  /**
10263
10931
  * Converts a single EvaluatedBaseline to an OSCAL Result.
10264
10932
  */
10265
- function baselineToResult(baseline, timestamp) {
10933
+ function baselineToResult(baseline, timestamp, toolActorUuid) {
10266
10934
  let title = baseline.name;
10267
10935
  if (baseline.title && baseline.title !== "") title = baseline.title;
10268
10936
  let description = "Converted from HDF results";
@@ -10270,17 +10938,26 @@ function baselineToResult(baseline, timestamp) {
10270
10938
  const findings = [];
10271
10939
  const observations = [];
10272
10940
  const risks = [];
10941
+ const includeControls = [];
10942
+ const seenControl = /* @__PURE__ */ new Set();
10273
10943
  for (const req of baseline.requirements) {
10274
- const { finding, observation, risk } = requirementToFindingSet(req, timestamp);
10944
+ const { finding, observation, risk } = requirementToFindingSet(req, timestamp, toolActorUuid);
10275
10945
  findings.push(finding);
10276
10946
  if (observation) observations.push(observation);
10277
10947
  if (risk) risks.push(risk);
10948
+ const cid = nistTagToControlId(req.id);
10949
+ if (cid !== "" && !seenControl.has(cid)) {
10950
+ seenControl.add(cid);
10951
+ includeControls.push({ "control-id": cid });
10952
+ }
10278
10953
  }
10954
+ const controlSelection = includeControls.length > 0 ? { "include-controls": includeControls } : {};
10279
10955
  return {
10280
10956
  uuid: crypto.randomUUID(),
10281
10957
  title,
10282
10958
  description,
10283
10959
  start: assessmentStart(baseline, timestamp),
10960
+ "reviewed-controls": { "control-selections": [controlSelection] },
10284
10961
  findings,
10285
10962
  observations,
10286
10963
  risks
@@ -10289,49 +10966,39 @@ function baselineToResult(baseline, timestamp) {
10289
10966
  /**
10290
10967
  * Converts an EvaluatedRequirement into a Finding, optional Observation, and optional Risk.
10291
10968
  */
10292
- function requirementToFindingSet(req, timestamp) {
10969
+ function requirementToFindingSet(req, timestamp, toolActorUuid) {
10293
10970
  const controlID = nistTagToControlId(req.id);
10294
- const { state, reason } = aggregateStatus(req.results);
10295
- const findingDesc = extractDefaultDescription(req.descriptions);
10971
+ const results = req.results ?? [];
10972
+ const descriptions = req.descriptions ?? [];
10973
+ const { state, reason } = aggregateStatus(results);
10974
+ const findingDesc = extractDefaultDescription(descriptions);
10296
10975
  const props = [];
10976
+ const addProp = (name, value) => {
10977
+ if (value !== "") props.push({
10978
+ name,
10979
+ value
10980
+ });
10981
+ };
10297
10982
  const pushTagValues = (key) => {
10298
10983
  const raw = req.tags?.[key];
10299
10984
  if (Array.isArray(raw)) {
10300
- for (const v of raw) if (typeof v === "string") props.push({
10301
- name: key,
10302
- value: v
10303
- });
10985
+ for (const v of raw) if (typeof v === "string") addProp(key, v);
10304
10986
  }
10305
10987
  };
10306
10988
  pushTagValues("nist");
10307
10989
  pushTagValues("cci");
10308
- if (req.code) props.push({
10309
- name: "code",
10310
- value: req.code
10311
- });
10990
+ if (req.code != null) addProp("code", req.code);
10312
10991
  for (const label of [
10313
10992
  "check",
10314
10993
  "fix",
10315
10994
  "rationale"
10316
10995
  ]) {
10317
- const d = req.descriptions.find((x) => x.label === label);
10318
- if (d) props.push({
10319
- name: label,
10320
- value: d.data
10321
- });
10996
+ const d = descriptions.find((x) => x.label === label);
10997
+ addProp(label, d ? d.data : "");
10322
10998
  }
10323
- if (req.controlType) props.push({
10324
- name: "control-type",
10325
- value: req.controlType
10326
- });
10327
- if (req.verificationMethod) props.push({
10328
- name: "verification-method",
10329
- value: req.verificationMethod
10330
- });
10331
- if (req.applicability) props.push({
10332
- name: "applicability",
10333
- value: req.applicability
10334
- });
10999
+ if (req.controlType) addProp("control-type", req.controlType);
11000
+ if (req.verificationMethod) addProp("verification-method", req.verificationMethod);
11001
+ if (req.applicability) addProp("applicability", req.applicability);
10335
11002
  const links = [];
10336
11003
  if (Array.isArray(req.refs)) for (const r of req.refs) {
10337
11004
  const o = r;
@@ -10360,19 +11027,19 @@ function requirementToFindingSet(req, timestamp) {
10360
11027
  const finding = {
10361
11028
  uuid: crypto.randomUUID(),
10362
11029
  title,
10363
- description: findingDesc || "",
11030
+ description: findingDesc || title,
10364
11031
  props: props.length > 0 ? props : void 0,
10365
11032
  links: links.length > 0 ? links : void 0,
10366
11033
  target
10367
11034
  };
10368
11035
  let observation;
10369
- if (req.results.length > 0) {
11036
+ if (results.length > 0) {
10370
11037
  const obsUUID = crypto.randomUUID();
10371
11038
  observation = {
10372
11039
  uuid: obsUUID,
10373
- description: buildObservationDescription(req.results),
11040
+ description: buildObservationDescription(results),
10374
11041
  methods: ["TEST"],
10375
- collected: formatAssessmentTime(earliestResultTime(req.results), timestamp)
11042
+ collected: formatAssessmentTime(earliestResultTime(results), timestamp)
10376
11043
  };
10377
11044
  finding["related-observations"] = [{ "observation-uuid": obsUUID }];
10378
11045
  }
@@ -10386,11 +11053,17 @@ function requirementToFindingSet(req, timestamp) {
10386
11053
  description: `Impact: ${req.impact.toFixed(1)} (${severity})`,
10387
11054
  statement: `Impact: ${req.impact.toFixed(1)} (${severity})`,
10388
11055
  status: riskStatusFromState(state),
10389
- characterizations: [{ facets: [{
10390
- name: "impact",
10391
- system: "https://fedramp.gov",
10392
- value: severity
10393
- }] }]
11056
+ characterizations: [{
11057
+ origin: { actors: [{
11058
+ type: "party",
11059
+ "actor-uuid": toolActorUuid
11060
+ }] },
11061
+ facets: [{
11062
+ name: "impact",
11063
+ system: "https://fedramp.gov",
11064
+ value: severity
11065
+ }]
11066
+ }]
10394
11067
  };
10395
11068
  finding["related-risks"] = [{ "risk-uuid": riskUUID }];
10396
11069
  }
@@ -10690,15 +11363,56 @@ function buildTags(dep) {
10690
11363
  if (dep.parentDependencies.length > 0) extras.parentDependencies = dep.parentDependencies;
10691
11364
  return buildNistCciTags(nist, cciTags, extras);
10692
11365
  }
11366
+ function titleCaseFirst(s) {
11367
+ return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
11368
+ }
11369
+ function buildScanRequirement(scan) {
11370
+ const title = scan.description || `${titleCaseFirst(scan.name)} scan`;
11371
+ const descriptions = [createDescription("default", scan.summary || `${titleCaseFirst(scan.name)} scan summary`)];
11372
+ const results = [{
11373
+ status: ResultStatus.NotReviewed,
11374
+ codeDesc: `${scan.name} scan summary`,
11375
+ startTime: /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
11376
+ }];
11377
+ const req = createRequirement(`scan-${scan.name}`, title, descriptions, 0, results, { tags: {
11378
+ name: scan.name,
11379
+ type: scan.results?.type ?? ""
11380
+ } });
11381
+ req.code = JSON.stringify(scan.results?.data ?? {}, null, 2);
11382
+ req.verificationMethod = VerificationMethodEnum.Automated;
11383
+ return req;
11384
+ }
11385
+ function verdictDescription(a) {
11386
+ let desc = `Ion Channel analysis verdict: ${a.passed ? "PASSED" : "FAILED"}`;
11387
+ if (a.risk) desc += ` (risk: ${a.risk})`;
11388
+ if (a.ruleset_name) {
11389
+ desc += `. Ruleset: ${a.ruleset_name}`;
11390
+ if (a.ruleset_id) desc += ` (${a.ruleset_id})`;
11391
+ }
11392
+ return `${desc}.`;
11393
+ }
11394
+ function verdictLabels(a) {
11395
+ const labels = { passed: String(a.passed) };
11396
+ if (a.risk) labels.risk = a.risk;
11397
+ if (a.ruleset_name) labels.ruleset_name = a.ruleset_name;
11398
+ if (a.ruleset_id) labels.ruleset_id = a.ruleset_id;
11399
+ return labels;
11400
+ }
10693
11401
  async function convertIonchannelToHdf(input, converterVersion = "1.0.0") {
10694
11402
  if (!input?.trim()) throw new Error("Empty input");
10695
11403
  validateInputSize(input, "ionchannel");
10696
11404
  const analysis = parseJSON(input);
10697
11405
  if (!Array.isArray(analysis.scan_summaries)) throw new Error(`Ion Channel scan_summaries invalid summary data (expecting array, got ${typeof analysis.scan_summaries})`);
10698
11406
  let allDeps = [];
10699
- for (const scan of analysis.scan_summaries) if (scan.name === "dependency") {
10700
- allDeps = scan.results?.data?.dependencies ?? [];
10701
- break;
11407
+ const nonDepScans = [];
11408
+ let foundDep = false;
11409
+ for (const scan of analysis.scan_summaries) {
11410
+ if (scan.name === "dependency" && !foundDep) {
11411
+ allDeps = scan.results?.data?.dependencies ?? [];
11412
+ foundDep = true;
11413
+ continue;
11414
+ }
11415
+ nonDepScans.push(scan);
10702
11416
  }
10703
11417
  const requirements = buildDependencyGraph(allDeps).map((dep) => {
10704
11418
  const depId = `dependency-${dep.org}/${dep.name}`;
@@ -10739,12 +11453,27 @@ async function convertIonchannelToHdf(input, converterVersion = "1.0.0") {
10739
11453
  status: "loaded"
10740
11454
  });
10741
11455
  baseline.maintainer = "saf@groups.mitre.org";
11456
+ baseline.description = verdictDescription(analysis);
11457
+ baseline.labels = verdictLabels(analysis);
11458
+ const baselines = [baseline];
11459
+ for (const scan of nonDepScans) {
11460
+ const scanBaseline = createMinimalBaseline(`Ion Channel ${scan.name} Scan`, [buildScanRequirement(scan)], {
11461
+ title: `Ion Channel Analysis of ${analysis.source}`,
11462
+ summary: scan.summary,
11463
+ integrity: {
11464
+ algorithm: resultsChecksum.algorithm,
11465
+ checksum: resultsChecksum.value
11466
+ },
11467
+ status: "loaded"
11468
+ });
11469
+ scanBaseline.maintainer = "saf@groups.mitre.org";
11470
+ baselines.push(scanBaseline);
11471
+ }
10742
11472
  return buildHdfResults({
10743
11473
  generatorName: "ionchannel-to-hdf",
10744
11474
  converterVersion,
10745
11475
  toolName: "Ion Channel",
10746
- toolFormat: "JSON",
10747
- baselines: [baseline],
11476
+ baselines,
10748
11477
  timestamp: /* @__PURE__ */ new Date()
10749
11478
  });
10750
11479
  }
@@ -11717,7 +12446,8 @@ function buildDocument(amendments, converterVersion) {
11717
12446
  ...notes.length > 0 && { notes },
11718
12447
  publisher: {
11719
12448
  category: "other",
11720
- name: publisherName
12449
+ name: publisherName,
12450
+ namespace: "https://saf.mitre.org"
11721
12451
  },
11722
12452
  tracking: {
11723
12453
  id: trackingId,
@@ -12027,7 +12757,11 @@ function buildMetadata(a, docTime, converterVersion) {
12027
12757
  }
12028
12758
  async function buildSerialNumber(input, a) {
12029
12759
  if (a.amendmentId) return `urn:uuid:${a.amendmentId}`;
12030
- return `urn:uuid:${(await sha256(input)).slice(0, 32)}`;
12760
+ const c = (await sha256(input)).slice(0, 32).split("");
12761
+ c[12] = "5";
12762
+ c[16] = (parseInt(c[16], 16) & 3 | 8).toString(16);
12763
+ const h = c.join("");
12764
+ return `urn:uuid:${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
12031
12765
  }
12032
12766
  //#endregion
12033
12767
  //#region converters/oscal-to-hdf/typescript/detect.ts
@@ -13050,6 +13784,225 @@ function substituteParams(text, overrides) {
13050
13784
  return result;
13051
13785
  }
13052
13786
  //#endregion
13053
- export { convertAsffToHdf, convertAwsConfigToHdf, convertBurpsuiteToHdf, convertCheckovToHdf, convertCklToHdf, convertCklbToHdf, convertConveyorToHdf, convertCsafVexToHdf, convertCyclonedxToHdf, convertCyclonedxVexToHdf, convertDbprotectToHdf, convertDefectDojoToHdf, convertDeptrackToHdf, convertFortifyToHdf, convertGitlabToHdf, convertGosecToHdf, convertGrypeToHdf, convertHdfToAsff, convertHdfToCkl, convertHdfToCklb, convertHdfToCsafVex, convertHdfToCsv, convertHdfToCyclonedxVex, convertHdfToEcs, convertHdfToOcsf, convertHdfToOpenVex, convertHdfToOscalPoam, convertHdfToOscalSar, convertHdfToSplunk, convertHdfToXccdf, convertHdfToXml, convertHipcheckToHdf, convertIonchannelToHdf, convertJfrogXrayToHdf, convertJunitToHdf, convertMsftDefenderCloudToHdf, convertMsftDefenderDevopsToHdf, convertMsftDefenderEndpointToHdf, convertMsftSecureScoreToHdf, convertNessusToHdf, convertNetsparkerToHdf, convertNeuvectorToHdf, convertNiktoToHdf, convertOpenVexToHdf, convertOscalCatalogToHdf, convertOscalComponentToHdf, convertOscalPoamToHdf, convertOscalProfileToHdf, convertOscalSapToHdf, convertOscalSarToHdf, convertOscalSspToHdf, convertPrismaToHdf, convertSarifToHdf, convertScoutsuiteToHdf, convertSnykToHdf, convertSonarqubeToHdf, convertSplunkToHdf, convertTrufflehogToHdf, convertTwistlockToHdf, convertV1ToV2, convertVeracodeToHdf, convertXccdfResultsToHdf, convertZapToHdf, detectOscalDocumentType, isHDFV1 };
13787
+ //#region shared/typescript/stix.ts
13788
+ /** Parse and validate a STIX 2.1 bundle. Objects are kept raw for lossless carriage. */
13789
+ function parseStixBundle(input) {
13790
+ if (!input) throw new Error("stix: empty input");
13791
+ let raw;
13792
+ try {
13793
+ raw = JSON.parse(input);
13794
+ } catch (e) {
13795
+ throw new Error(`stix: parsing bundle: ${e.message}`);
13796
+ }
13797
+ const obj = raw ?? {};
13798
+ if (obj.type !== "bundle") throw new Error(`stix: not a STIX bundle (type=${JSON.stringify(obj.type)})`);
13799
+ if (!Array.isArray(obj.objects)) throw new Error("stix: bundle has no objects[]");
13800
+ return {
13801
+ type: "bundle",
13802
+ id: typeof obj.id === "string" ? obj.id : void 0,
13803
+ objects: obj.objects.filter((o) => typeof o === "object" && o !== null && !Array.isArray(o))
13804
+ };
13805
+ }
13806
+ /** Report whether the input looks like a STIX 2.1 bundle, without throwing. */
13807
+ function detectStixBundle(input) {
13808
+ try {
13809
+ const o = JSON.parse(input);
13810
+ return o?.type === "bundle" && Array.isArray(o.objects);
13811
+ } catch {
13812
+ return false;
13813
+ }
13814
+ }
13815
+ /** A STIX object's id ('' if absent). */
13816
+ function stixObjectId(obj) {
13817
+ return typeof obj.id === "string" ? obj.id : "";
13818
+ }
13819
+ /**
13820
+ * The CVE ids a STIX object cites via external_references (source_name "cve").
13821
+ * A STIX vulnerability records its CVE this way, not in a native field
13822
+ * (STIX 2.1 §4.19).
13823
+ */
13824
+ function stixObjectCVEs(obj) {
13825
+ const refs = obj.external_references;
13826
+ if (!Array.isArray(refs)) return [];
13827
+ const cves = [];
13828
+ for (const r of refs) {
13829
+ const ref = r;
13830
+ if (typeof ref.source_name === "string" && ref.source_name.toLowerCase() === "cve" && typeof ref.external_id === "string" && ref.external_id) cves.push(ref.external_id);
13831
+ }
13832
+ return cves;
13833
+ }
13834
+ //#endregion
13835
+ //#region shared/typescript/enrichStix.ts
13836
+ const DEFAULT_REVIEW_HORIZON_MS = 2160 * 60 * 60 * 1e3;
13837
+ /** Format a Date as canonical trimmed-UTC RFC3339 (seconds precision, matching Go's time.RFC3339). */
13838
+ function toRfc3339(d) {
13839
+ return d.toISOString().replace(/\.\d{3}Z$/, "Z");
13840
+ }
13841
+ /**
13842
+ * Overlay a STIX 2.1 bundle onto an existing HDF results document, attaching
13843
+ * each STIX object as an inert externalReferences[] entry: a CVE-bearing object
13844
+ * attaches to the finding whose requirementId is that CVE (fanning out to every
13845
+ * match), and everything else — non-CVE objects and CVEs with no matching
13846
+ * finding — attaches to the results root. Each entry carries the raw STIX object
13847
+ * losslessly in `document`.
13848
+ *
13849
+ * Informational only: authors no overrides and fabricates no status/impact (the
13850
+ * E:A recompute is a separate, opt-in step). The results document is preserved
13851
+ * verbatim except for the appended references — manipulated structurally so
13852
+ * every pre-existing field, including timestamp strings, round-trips unchanged.
13853
+ * TypeScript peer of shared/go/enrich_stix.go (kept at parity).
13854
+ */
13855
+ function enrichStix(resultsInput, bundleInput, opts) {
13856
+ validateInputSize(resultsInput, "enrich-stix", opts?.maxSize);
13857
+ validateInputSize(bundleInput, "enrich-stix", opts?.maxSize);
13858
+ if (!resultsInput) throw new Error("enrich-stix: empty results input");
13859
+ const bundle = parseStixBundle(bundleInput);
13860
+ let parsed;
13861
+ try {
13862
+ parsed = JSON.parse(resultsInput);
13863
+ } catch (e) {
13864
+ throw new Error(`enrich-stix: parsing results: ${e.message}`);
13865
+ }
13866
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("enrich-stix: results input is not a JSON object");
13867
+ const doc = parsed;
13868
+ const reqById = indexRequirementsById(doc);
13869
+ for (const obj of bundle.objects) {
13870
+ let matched = false;
13871
+ for (const cve of stixObjectCVEs(obj)) for (const req of reqById.get(cve) ?? []) {
13872
+ appendExternalReference(req, buildStixRef(obj, "investigate"));
13873
+ matched = true;
13874
+ }
13875
+ if (!matched) appendExternalReference(doc, buildStixRef(obj, "reference"));
13876
+ }
13877
+ if (opts?.recomputeCvss) recomputeExploitation(bundle, reqById, opts);
13878
+ return JSON.stringify(doc, null, 2);
13879
+ }
13880
+ /**
13881
+ * Author an inline riskAdjustment on each CVE-matched finding that has a 3.1 base
13882
+ * vector and whose CVE carries a structural exploitation signal in the bundle.
13883
+ * Applies CVSS Exploit Maturity E:H (the 3.1 analog of 4.0 E:A) and recomputes
13884
+ * the Threat score. Skips (no fabrication) when a base vector is absent or non-3.1.
13885
+ * TypeScript peer of recomputeExploitation in enrich_stix.go.
13886
+ */
13887
+ function recomputeExploitation(bundle, reqById, opts) {
13888
+ const appliedAt = opts.asOf ?? /* @__PURE__ */ new Date();
13889
+ const expiresAt = new Date(appliedAt.getTime() + (opts.reviewHorizonMs ?? DEFAULT_REVIEW_HORIZON_MS));
13890
+ for (const [cve, src] of exploitedCves(bundle)) for (const req of reqById.get(cve) ?? []) {
13891
+ const entry = findBaseVectorEntry(req, cve);
13892
+ if (!entry) continue;
13893
+ const baseVector = entry.baseVector;
13894
+ let score;
13895
+ try {
13896
+ score = computeCvssScore(`${baseVector}/E:H`);
13897
+ } catch {
13898
+ continue;
13899
+ }
13900
+ const ov = buildRiskAdjustment(cve, baseVector, score, src, appliedAt, expiresAt);
13901
+ req.statusOverrides = [...Array.isArray(req.statusOverrides) ? req.statusOverrides : [], ov];
13902
+ }
13903
+ }
13904
+ /**
13905
+ * Map each CVE with a structural exploitation signal in the bundle to the STIX
13906
+ * object providing it: a sighting (sighting_of_ref), a targets/exploits
13907
+ * relationship (target_ref), or an indicator/report (object_refs).
13908
+ */
13909
+ function exploitedCves(bundle) {
13910
+ const vulnCves = /* @__PURE__ */ new Map();
13911
+ for (const o of bundle.objects) if (o.type === "vulnerability") {
13912
+ const id = stixObjectId(o);
13913
+ vulnCves.set(id, [...vulnCves.get(id) ?? [], ...stixObjectCVEs(o)]);
13914
+ }
13915
+ const exploited = /* @__PURE__ */ new Map();
13916
+ const mark = (vulnId, src) => {
13917
+ for (const cve of vulnCves.get(vulnId) ?? []) if (!exploited.has(cve)) exploited.set(cve, src);
13918
+ };
13919
+ for (const o of bundle.objects) if (o.type === "sighting" && typeof o.sighting_of_ref === "string") mark(o.sighting_of_ref, o);
13920
+ else if (o.type === "relationship" && (o.relationship_type === "targets" || o.relationship_type === "exploits") && typeof o.target_ref === "string") mark(o.target_ref, o);
13921
+ else if ((o.type === "indicator" || o.type === "report") && Array.isArray(o.object_refs)) {
13922
+ for (const r of o.object_refs) if (typeof r === "string") mark(r, o);
13923
+ }
13924
+ return exploited;
13925
+ }
13926
+ /** The finding's cvss[] entry carrying a base vector for the CVE (or a version-agnostic entry). */
13927
+ function findBaseVectorEntry(req, cve) {
13928
+ const arr = req.cvss;
13929
+ if (!Array.isArray(arr)) return void 0;
13930
+ for (const e of arr) {
13931
+ const m = e;
13932
+ const bv = m.baseVector;
13933
+ if (typeof bv === "string" && bv) {
13934
+ const id = m.id;
13935
+ if (id === void 0 || id === cve) return m;
13936
+ }
13937
+ }
13938
+ }
13939
+ /** Build the inline riskAdjustment recording the E:H-recomputed Threat score + STIX source. */
13940
+ function buildRiskAdjustment(cve, baseVector, score, src, appliedAt, expiresAt) {
13941
+ return {
13942
+ type: "riskAdjustment",
13943
+ reason: `${cve} actively exploited per STIX threat intelligence (${stixObjectId(src)}); CVSS Threat recomputed with Exploit Maturity E:H.`,
13944
+ impact: { value: roundImpact(score.temporalScore / 10) },
13945
+ appliedBy: {
13946
+ type: "other",
13947
+ identifier: "hdf-enrich"
13948
+ },
13949
+ appliedAt: toRfc3339(appliedAt),
13950
+ expiresAt: toRfc3339(expiresAt),
13951
+ cvss: {
13952
+ version: "3.1",
13953
+ id: cve,
13954
+ baseVector,
13955
+ baseScore: score.baseScore,
13956
+ threatVector: "E:H",
13957
+ threatScore: score.temporalScore,
13958
+ computedScore: score.temporalScore
13959
+ },
13960
+ externalReferences: [buildStixRef(src, "evidence")]
13961
+ };
13962
+ }
13963
+ /** Map each finding id to the requirement objects carrying it (fan-out across baselines). */
13964
+ function indexRequirementsById(doc) {
13965
+ const index = /* @__PURE__ */ new Map();
13966
+ const baselines = Array.isArray(doc.baselines) ? doc.baselines : [];
13967
+ for (const b of baselines) {
13968
+ const reqs = b?.requirements;
13969
+ if (!Array.isArray(reqs)) continue;
13970
+ for (const r of reqs) {
13971
+ const req = r;
13972
+ if (typeof req.id === "string" && req.id) {
13973
+ const list = index.get(req.id) ?? [];
13974
+ list.push(req);
13975
+ index.set(req.id, list);
13976
+ }
13977
+ }
13978
+ }
13979
+ return index;
13980
+ }
13981
+ /** Build an External_Reference envelope carrying the raw STIX object in `document`. `rel` is an open token. */
13982
+ function buildStixRef(obj, rel) {
13983
+ const ref = {
13984
+ sourceName: "stix",
13985
+ kind: "threat-intel",
13986
+ rel,
13987
+ document: obj
13988
+ };
13989
+ const id = stixObjectId(obj);
13990
+ if (id) ref.externalId = id;
13991
+ else ref.description = stixFallbackDescription(obj);
13992
+ return ref;
13993
+ }
13994
+ /** Human-readable description for an id-less STIX object: its name, else a
13995
+ * type-derived label, else a generic one — so its reference satisfies anyOf. */
13996
+ function stixFallbackDescription(obj) {
13997
+ if (typeof obj.name === "string" && obj.name) return obj.name;
13998
+ if (typeof obj.type === "string" && obj.type) return `STIX ${obj.type} object`;
13999
+ return "STIX object";
14000
+ }
14001
+ /** Append a reference to a container's externalReferences[], creating it if absent. */
14002
+ function appendExternalReference(container, ref) {
14003
+ container.externalReferences = [...Array.isArray(container.externalReferences) ? container.externalReferences : [], ref];
14004
+ }
14005
+ //#endregion
14006
+ export { convertAsffToHdf, convertAwsConfigToHdf, convertBurpsuiteToHdf, convertCheckovToHdf, convertCklToHdf, convertCklbToHdf, convertConveyorToHdf, convertCsafVexToHdf, convertCyclonedxToHdf, convertCyclonedxVexToHdf, convertDbprotectToHdf, convertDefectDojoToHdf, convertDeptrackToHdf, convertFortifyToHdf, convertGitlabToHdf, convertGosecToHdf, convertGrypeToHdf, convertHdfToAsff, convertHdfToCkl, convertHdfToCklb, convertHdfToCsafVex, convertHdfToCsv, convertHdfToCyclonedxVex, convertHdfToEcs, convertHdfToOcsf, convertHdfToOpenVex, convertHdfToOscalPoam, convertHdfToOscalSar, convertHdfToSplunk, convertHdfToXccdf, convertHdfToXml, convertHipcheckToHdf, convertIonchannelToHdf, convertJfrogXrayToHdf, convertJunitToHdf, convertMsftDefenderCloudToHdf, convertMsftDefenderDevopsToHdf, convertMsftDefenderEndpointToHdf, convertMsftSecureScoreToHdf, convertNessusToHdf, convertNetsparkerToHdf, convertNeuvectorToHdf, convertNiktoToHdf, convertOpenVexToHdf, convertOscalCatalogToHdf, convertOscalComponentToHdf, convertOscalPoamToHdf, convertOscalProfileToHdf, convertOscalSapToHdf, convertOscalSarToHdf, convertOscalSspToHdf, convertPrismaToHdf, convertSarifToHdf, convertScoutsuiteToHdf, convertSnykToHdf, convertSonarqubeToHdf, convertSplunkToHdf, convertTrufflehogToHdf, convertTwistlockToHdf, convertV1ToV2, convertVeracodeToHdf, convertXccdfResultsToHdf, convertZapToHdf, detectOscalDocumentType, detectStixBundle, enrichStix, isHDFV1, parseStixBundle };
13054
14007
 
13055
14008
  //# sourceMappingURL=index.js.map