@mitre/hdf-converters 3.4.0 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +1 -1
- package/dist/index.d.ts +12 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1713 -1133
- package/dist/index.js.map +1 -1
- package/dist/{register-all-DxZN6IJ4.js → register-all-WRR-vAZf.js} +33 -1
- package/dist/register-all-WRR-vAZf.js.map +1 -0
- package/package.json +16 -7
- package/dist/register-all-DxZN6IJ4.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { n as detectConverter, t as registerAllFingerprints } from "./register-all-
|
|
1
|
+
import { n as detectConverter, t as registerAllFingerprints } from "./register-all-WRR-vAZf.js";
|
|
2
2
|
import { flattenOverlays } from "@mitre/hdf-parsers";
|
|
3
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
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";
|
|
5
|
-
import { DEFAULT_COMPONENT_MANAGEMENT_NIST_TAGS, DEFAULT_REMEDIATION_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS as DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1, awsConfigMappedRevisions, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getCCINistMappings, getCurrentNistRevision, getCweNistControl, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistToCci } from "@mitre/hdf-mappings";
|
|
5
|
+
import { DEFAULT_COMPONENT_MANAGEMENT_NIST_TAGS, DEFAULT_REMEDIATION_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS as DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1, awsConfigMappedRevisions, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getAwsConfigNistControlsBySubstring, getCCINistMappings, getCurrentNistRevision, getCweNistControl, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistToCci } from "@mitre/hdf-mappings";
|
|
6
6
|
//#region shared/typescript/converterutil.ts
|
|
7
7
|
/**
|
|
8
8
|
* Shared utilities for TypeScript HDF converters.
|
|
@@ -696,6 +696,33 @@ function convertSupports(supports) {
|
|
|
696
696
|
return out.length ? out : void 0;
|
|
697
697
|
}
|
|
698
698
|
/**
|
|
699
|
+
* Convert v1.0 attributes to v2.0 Input objects. V1 attributes are
|
|
700
|
+
* {name, options: {...}}; InSpec nests value/type/required/sensitive/description
|
|
701
|
+
* under `options`. Mirrors convertAttributes in the Go converter (omit fields
|
|
702
|
+
* absent from options so both languages produce identical output).
|
|
703
|
+
*/
|
|
704
|
+
function convertAttributes(attrs) {
|
|
705
|
+
const inputs = [];
|
|
706
|
+
for (const attr of attrs) {
|
|
707
|
+
if (attr === null || typeof attr !== "object") continue;
|
|
708
|
+
const a = attr;
|
|
709
|
+
const name = typeof a.name === "string" ? a.name : "";
|
|
710
|
+
if (!name) continue;
|
|
711
|
+
const input = { name };
|
|
712
|
+
const options = a.options;
|
|
713
|
+
if (options !== null && typeof options === "object") {
|
|
714
|
+
const o = options;
|
|
715
|
+
if ("value" in o) input.value = o.value;
|
|
716
|
+
if (typeof o.description === "string") input.description = o.description;
|
|
717
|
+
if (typeof o.sensitive === "boolean") input.sensitive = o.sensitive;
|
|
718
|
+
if (typeof o.required === "boolean") input.required = o.required;
|
|
719
|
+
if (typeof o.type === "string") input.type = o.type;
|
|
720
|
+
}
|
|
721
|
+
inputs.push(input);
|
|
722
|
+
}
|
|
723
|
+
return inputs;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
699
726
|
* Convert v1.0 profile to v2.0 baseline.
|
|
700
727
|
* Transforms field names and nested structures.
|
|
701
728
|
*/
|
|
@@ -712,7 +739,7 @@ function convertProfile(v1Profile) {
|
|
|
712
739
|
const supports = convertSupports(v1Profile.supports);
|
|
713
740
|
if (supports) v2Baseline.supports = supports;
|
|
714
741
|
}
|
|
715
|
-
if (v1Profile.attributes?.length) v2Baseline.inputs = v1Profile.attributes;
|
|
742
|
+
if (v1Profile.attributes?.length) v2Baseline.inputs = convertAttributes(v1Profile.attributes);
|
|
716
743
|
if (v1Profile.status !== void 0) v2Baseline.status = v1Profile.status;
|
|
717
744
|
if (v1Profile.sha256) v2Baseline.integrity = {
|
|
718
745
|
algorithm: "sha256",
|
|
@@ -798,6 +825,352 @@ function isHDFV1(data) {
|
|
|
798
825
|
return typeof obj.version === "string" && Array.isArray(obj.profiles) && typeof obj.platform === "object" && obj.platform !== null;
|
|
799
826
|
}
|
|
800
827
|
//#endregion
|
|
828
|
+
//#region converters/asff-to-hdf/typescript/converter.ts
|
|
829
|
+
/**
|
|
830
|
+
* AWS Security Finding Format (ASFF) to HDF converter.
|
|
831
|
+
*
|
|
832
|
+
* A single ASFF envelope can carry findings from many products, and Security Hub
|
|
833
|
+
* findings span multiple compliance standards (CIS, AFSBP, PCI, ...). Because
|
|
834
|
+
* hdf-results supports multiple baselines in one document, each product — and
|
|
835
|
+
* each Security Hub standard — becomes its own baseline entry, mirroring the
|
|
836
|
+
* per-file split the predecessor SAF CLI produced.
|
|
837
|
+
*/
|
|
838
|
+
function productOf(f) {
|
|
839
|
+
const [company, prod] = productArnParts(f.ProductArn);
|
|
840
|
+
if (prod === "securityhub") return "securityHub";
|
|
841
|
+
if (company === "prowler" && prod === "prowler") return "prowler";
|
|
842
|
+
if (company === "aquasecurity" && prod === "aquasecurity") return "trivy";
|
|
843
|
+
return "default";
|
|
844
|
+
}
|
|
845
|
+
/** The CVE id Trivy stashes under Resources[0].Details.Other. */
|
|
846
|
+
function trivyCVE(f) {
|
|
847
|
+
return f.Resources?.[0]?.Details?.Other?.["CVE ID"] ?? "";
|
|
848
|
+
}
|
|
849
|
+
/** Accepts the `{ "Findings": [...] }` envelope, a bare array, a single finding, or NDJSON (Prowler). */
|
|
850
|
+
function parseFindings$1(input) {
|
|
851
|
+
try {
|
|
852
|
+
const parsed = parseJSON(input);
|
|
853
|
+
if (parsed !== null && typeof parsed === "object" && "Findings" in parsed) {
|
|
854
|
+
const findings = parsed.Findings;
|
|
855
|
+
return Array.isArray(findings) ? findings : [];
|
|
856
|
+
}
|
|
857
|
+
if (Array.isArray(parsed)) return parsed;
|
|
858
|
+
if (parsed !== null && typeof parsed === "object") return [parsed];
|
|
859
|
+
} catch {}
|
|
860
|
+
const out = input.trim().split("\n").filter((l) => l.trim().length > 0).map((l) => parseJSON(l.trim())).filter((x) => x !== null && typeof x === "object" && !Array.isArray(x));
|
|
861
|
+
if (out.length > 0) return out;
|
|
862
|
+
throw new Error("invalid ASFF JSON");
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* mapComplianceStatus maps ASFF Compliance.Status to an HDF result status.
|
|
866
|
+
* hdf-results has no "skipped": WARNING and NOT_AVAILABLE (no clean pass/fail
|
|
867
|
+
* verdict) map to notReviewed; an absent status defaults to failed.
|
|
868
|
+
*/
|
|
869
|
+
function mapComplianceStatus$1(status) {
|
|
870
|
+
switch (status) {
|
|
871
|
+
case "PASSED": return ResultStatus.Passed;
|
|
872
|
+
case "FAILED": return ResultStatus.Failed;
|
|
873
|
+
case "WARNING":
|
|
874
|
+
case "NOT_AVAILABLE": return ResultStatus.NotReviewed;
|
|
875
|
+
case void 0:
|
|
876
|
+
case "": return ResultStatus.Failed;
|
|
877
|
+
default: return ResultStatus.Error;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function severityLabelToImpact(label) {
|
|
881
|
+
switch ((label ?? "").toUpperCase()) {
|
|
882
|
+
case "CRITICAL": return .9;
|
|
883
|
+
case "HIGH": return .7;
|
|
884
|
+
case "MEDIUM": return .5;
|
|
885
|
+
case "LOW": return .3;
|
|
886
|
+
default: return 0;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* findingImpact derives a 0.0–1.0 impact. Suppressed findings are forced to 0.
|
|
891
|
+
* Security Hub's INFORMATIONAL is up-graded to MEDIUM (Security Hub over-marks
|
|
892
|
+
* findings INFORMATIONAL without context).
|
|
893
|
+
*/
|
|
894
|
+
function findingImpact(f) {
|
|
895
|
+
if (f.Workflow?.Status === "SUPPRESSED") return 0;
|
|
896
|
+
let label = f.Severity?.Label;
|
|
897
|
+
if (isSecurityHub(f) && (label ?? "").toUpperCase() === "INFORMATIONAL") label = "MEDIUM";
|
|
898
|
+
if (label) return severityLabelToImpact(label);
|
|
899
|
+
if (typeof f.Severity?.Normalized === "number") return f.Severity.Normalized / 100;
|
|
900
|
+
return 0;
|
|
901
|
+
}
|
|
902
|
+
function productArnParts(arn) {
|
|
903
|
+
if (!arn) return ["", ""];
|
|
904
|
+
const segs = (arn.split(":").pop() ?? "").split("/");
|
|
905
|
+
if (segs.length >= 3) return [segs[1], segs[2]];
|
|
906
|
+
return ["", ""];
|
|
907
|
+
}
|
|
908
|
+
function isSecurityHub(f) {
|
|
909
|
+
return productOf(f) === "securityHub";
|
|
910
|
+
}
|
|
911
|
+
const normalizeStd = (s) => s.toLowerCase().replace(/-/g, " ");
|
|
912
|
+
function titleCaseWords(s) {
|
|
913
|
+
return s.split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* securityHubStandardName derives "CIS AWS Foundations Benchmark v1.2.0" from a
|
|
917
|
+
* finding's StandardsControlArn, preferring the nicer Types[0] casing when it
|
|
918
|
+
* matches the ARN's standard slug (mirrors the SAF CLI grouping key).
|
|
919
|
+
*/
|
|
920
|
+
function securityHubStandardName(f) {
|
|
921
|
+
const arn = f.ProductFields?.StandardsControlArn ?? "";
|
|
922
|
+
if (!arn) return "";
|
|
923
|
+
const segs = arn.split("/");
|
|
924
|
+
if (segs.length < 4) return "";
|
|
925
|
+
const slug = segs[segs.length - 4];
|
|
926
|
+
const version = segs[segs.length - 2];
|
|
927
|
+
let typesLast = "";
|
|
928
|
+
if (f.Types && f.Types.length > 0) {
|
|
929
|
+
const ts = f.Types[0].split("/");
|
|
930
|
+
typesLast = ts[ts.length - 1];
|
|
931
|
+
}
|
|
932
|
+
return `${typesLast && normalizeStd(typesLast) === normalizeStd(slug) ? typesLast.replace(/-/g, " ") : titleCaseWords(slug.replace(/-/g, " "))} v${version}`;
|
|
933
|
+
}
|
|
934
|
+
/** Level-1 grouping key, per product. */
|
|
935
|
+
function baselineName(f) {
|
|
936
|
+
switch (productOf(f)) {
|
|
937
|
+
case "securityHub": {
|
|
938
|
+
const name = securityHubStandardName(f);
|
|
939
|
+
if (name) return name;
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case "prowler":
|
|
943
|
+
if (f.ProductFields?.ProviderName) return f.ProductFields.ProviderName;
|
|
944
|
+
break;
|
|
945
|
+
case "trivy": return "Aqua Security - Trivy";
|
|
946
|
+
}
|
|
947
|
+
const [company, prod] = productArnParts(f.ProductArn);
|
|
948
|
+
if (!company && !prod) return "AWS Security Finding Format";
|
|
949
|
+
return `${company} - ${prod}`;
|
|
950
|
+
}
|
|
951
|
+
/** Level-2 grouping key within a baseline, per product. */
|
|
952
|
+
function controlId(f) {
|
|
953
|
+
switch (productOf(f)) {
|
|
954
|
+
case "securityHub":
|
|
955
|
+
if (f.ProductFields?.ControlId) return f.ProductFields.ControlId;
|
|
956
|
+
if (f.ProductFields?.RuleId) return f.ProductFields.RuleId;
|
|
957
|
+
break;
|
|
958
|
+
case "prowler": {
|
|
959
|
+
const i = (f.GeneratorId ?? "").indexOf("-");
|
|
960
|
+
if (i >= 0) return (f.GeneratorId ?? "").slice(i + 1);
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
case "trivy": return `${f.GeneratorId ?? ""}/${trivyCVE(f) || f.Id || ""}`;
|
|
964
|
+
}
|
|
965
|
+
if (f.Compliance?.Status && f.GeneratorId) {
|
|
966
|
+
const segs = f.GeneratorId.split("/");
|
|
967
|
+
if (segs[segs.length - 1]) return segs[segs.length - 1];
|
|
968
|
+
}
|
|
969
|
+
return f.Id ?? "";
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* nistTags derives NIST controls for a finding group: AWS Config rule → NIST via
|
|
973
|
+
* the shared awsconfig mapping, falling back to the static analysis default
|
|
974
|
+
* bundle (matching the SAF CLI) when no config rule applies.
|
|
975
|
+
*/
|
|
976
|
+
function nistTags(group) {
|
|
977
|
+
if (group.length > 0 && productOf(group[0]) === "trivy" && trivyCVE(group[0])) return DEFAULT_REMEDIATION_NIST_TAGS$1;
|
|
978
|
+
const out = [];
|
|
979
|
+
const seen = /* @__PURE__ */ new Set();
|
|
980
|
+
for (const f of group) for (const tag of configRuleNist(f)) if (!seen.has(tag)) {
|
|
981
|
+
seen.add(tag);
|
|
982
|
+
out.push(tag);
|
|
983
|
+
}
|
|
984
|
+
return out.length > 0 ? out : DEFAULT_STATIC_ANALYSIS_NIST_TAGS;
|
|
985
|
+
}
|
|
986
|
+
function configRuleNist(f) {
|
|
987
|
+
if (f.ProductFields?.["RelatedAWSResources:0/type"] !== "AWS::Config::ConfigRule") return [];
|
|
988
|
+
const name = f.ProductFields?.["RelatedAWSResources:0/name"];
|
|
989
|
+
if (!name) return [];
|
|
990
|
+
return getAwsConfigNistControlsBySubstring(name);
|
|
991
|
+
}
|
|
992
|
+
function remediationText(f) {
|
|
993
|
+
const parts = [];
|
|
994
|
+
const rec = f.Remediation?.Recommendation;
|
|
995
|
+
if (rec?.Text) parts.push(rec.Text);
|
|
996
|
+
if (rec?.Url) parts.push(rec.Url);
|
|
997
|
+
return parts.join("\n");
|
|
998
|
+
}
|
|
999
|
+
function resourceCodeDesc(f) {
|
|
1000
|
+
return `Resources: [${(f.Resources ?? []).map((r) => {
|
|
1001
|
+
let seg = `Type: ${r.Type ?? ""}, Id: ${r.Id ?? ""}`;
|
|
1002
|
+
if (r.Partition) seg += `, Partition: ${r.Partition}`;
|
|
1003
|
+
if (r.Region) seg += `, Region: ${r.Region}`;
|
|
1004
|
+
return seg;
|
|
1005
|
+
}).join("; ")}]`;
|
|
1006
|
+
}
|
|
1007
|
+
function statusReason(f) {
|
|
1008
|
+
const lines = [];
|
|
1009
|
+
for (const r of f.Compliance?.StatusReasons ?? []) {
|
|
1010
|
+
if (r.ReasonCode) lines.push(`ReasonCode: ${r.ReasonCode}`);
|
|
1011
|
+
if (r.Description) lines.push(`Description: ${r.Description}`);
|
|
1012
|
+
}
|
|
1013
|
+
return lines.join("\n");
|
|
1014
|
+
}
|
|
1015
|
+
function buildResult$2(f) {
|
|
1016
|
+
const start = parseTimestamp(f.LastObservedAt ?? f.UpdatedAt ?? "") ?? /* @__PURE__ */ new Date();
|
|
1017
|
+
let status = mapComplianceStatus$1(f.Compliance?.Status);
|
|
1018
|
+
let codeDesc = resourceCodeDesc(f);
|
|
1019
|
+
let message = statusReason(f);
|
|
1020
|
+
switch (productOf(f)) {
|
|
1021
|
+
case "prowler":
|
|
1022
|
+
codeDesc = f.Description ?? "";
|
|
1023
|
+
break;
|
|
1024
|
+
case "trivy": {
|
|
1025
|
+
status = ResultStatus.Failed;
|
|
1026
|
+
const m = trivyMessage(f);
|
|
1027
|
+
if (m) message = m;
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const vuln = vulnerabilitySummary(f);
|
|
1032
|
+
if (vuln) message = message ? `${message}\n${vuln}` : vuln;
|
|
1033
|
+
return createResult(status, message || void 0, {
|
|
1034
|
+
codeDesc,
|
|
1035
|
+
startTime: start
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Renders a finding's ASFF Vulnerabilities[] as a compact text block: CVE id,
|
|
1040
|
+
* CVSS base score, EPSS, exploit/fix availability, and affected packages.
|
|
1041
|
+
* Generic — applies to any producer that emits the field.
|
|
1042
|
+
*/
|
|
1043
|
+
function vulnerabilitySummary(f) {
|
|
1044
|
+
return (f.Vulnerabilities ?? []).map((v) => {
|
|
1045
|
+
const parts = [v.Id ?? ""];
|
|
1046
|
+
const cvss = v.Cvss?.[0];
|
|
1047
|
+
if (cvss && typeof cvss.BaseScore === "number") parts.push(`CVSS ${cvss.Version ?? ""} ${cvss.BaseScore.toFixed(1)}`);
|
|
1048
|
+
if (typeof v.EpssScore === "number") parts.push(`EPSS ${v.EpssScore.toFixed(4)}`);
|
|
1049
|
+
if (v.ExploitAvailable) parts.push(`exploit ${v.ExploitAvailable.toLowerCase()}`);
|
|
1050
|
+
if (v.FixAvailable) parts.push(`fix ${v.FixAvailable.toLowerCase()}`);
|
|
1051
|
+
for (const p of v.VulnerablePackages ?? []) {
|
|
1052
|
+
let pkg = `${p.Name ?? ""}@${p.Version ?? ""}`;
|
|
1053
|
+
if (p.FixedInVersion) pkg += ` (fixed in ${p.FixedInVersion})`;
|
|
1054
|
+
parts.push(pkg);
|
|
1055
|
+
}
|
|
1056
|
+
return parts.join("; ");
|
|
1057
|
+
}).join("\n");
|
|
1058
|
+
}
|
|
1059
|
+
function trivyMessage(f) {
|
|
1060
|
+
const o = f.Resources?.[0]?.Details?.Other;
|
|
1061
|
+
if (!o) return "";
|
|
1062
|
+
if (o["CVE ID"]) {
|
|
1063
|
+
const patched = o["Patched Package"];
|
|
1064
|
+
const patchMsg = patched ? `The package has been patched since version(s): ${patched}.` : "There is no patched version of the package.";
|
|
1065
|
+
return `For package ${o["PkgName"] ?? ""}, the current version that is installed is ${o["Installed Package"] ?? ""}. ${patchMsg}`;
|
|
1066
|
+
}
|
|
1067
|
+
if (o["Message"]) {
|
|
1068
|
+
const loc = trivyLocation(o);
|
|
1069
|
+
return loc ? `${o["Message"]} (${loc})` : o["Message"];
|
|
1070
|
+
}
|
|
1071
|
+
if (o["Filename"]) return `Secret detected in ${o["Filename"]}.`;
|
|
1072
|
+
return "";
|
|
1073
|
+
}
|
|
1074
|
+
function trivyLocation(o) {
|
|
1075
|
+
const file = o["Filename"] ?? "";
|
|
1076
|
+
if (!file) return "";
|
|
1077
|
+
const sl = o["StartLine"];
|
|
1078
|
+
if (!sl || sl === "0") return file;
|
|
1079
|
+
let loc = `${file}:${sl}`;
|
|
1080
|
+
const el = o["EndLine"];
|
|
1081
|
+
if (el && el !== "0" && el !== sl) loc += `-${el}`;
|
|
1082
|
+
return loc;
|
|
1083
|
+
}
|
|
1084
|
+
function buildRequirement$17(id, group) {
|
|
1085
|
+
const primary = group[0];
|
|
1086
|
+
const impact = Math.max(...group.map(findingImpact));
|
|
1087
|
+
const descriptions = [{
|
|
1088
|
+
label: "default",
|
|
1089
|
+
data: productOf(primary) === "prowler" ? " " : primary.Description ?? primary.Title ?? ""
|
|
1090
|
+
}];
|
|
1091
|
+
const fix = remediationText(primary);
|
|
1092
|
+
if (fix) descriptions.push({
|
|
1093
|
+
label: "fix",
|
|
1094
|
+
data: fix
|
|
1095
|
+
});
|
|
1096
|
+
const nist = nistTags(group);
|
|
1097
|
+
const tags = nist.length > 0 ? buildNistCciTags(nist, nistToCci(nist)) : {};
|
|
1098
|
+
const results = group.map(buildResult$2);
|
|
1099
|
+
const req = createRequirement(id, primary.Title ?? "", descriptions, impact, results, { tags });
|
|
1100
|
+
const controlType = deriveControlTypeFromTags(nist);
|
|
1101
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
1102
|
+
const refs = [];
|
|
1103
|
+
if (primary.SourceUrl) refs.push({ url: primary.SourceUrl });
|
|
1104
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1105
|
+
for (const v of primary.Vulnerabilities ?? []) for (const u of v.ReferenceUrls ?? []) if (u && !seen.has(u)) {
|
|
1106
|
+
seen.add(u);
|
|
1107
|
+
refs.push({ url: u });
|
|
1108
|
+
}
|
|
1109
|
+
if (refs.length > 0) req.refs = refs;
|
|
1110
|
+
return req;
|
|
1111
|
+
}
|
|
1112
|
+
function buildBaseline$1(name, findings, resultsChecksum) {
|
|
1113
|
+
const order = [];
|
|
1114
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1115
|
+
for (const f of findings) {
|
|
1116
|
+
const id = controlId(f);
|
|
1117
|
+
const existing = groups.get(id);
|
|
1118
|
+
if (existing) existing.push(f);
|
|
1119
|
+
else {
|
|
1120
|
+
order.push(id);
|
|
1121
|
+
groups.set(id, [f]);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return createMinimalBaseline(name, order.map((id) => buildRequirement$17(id, groups.get(id))), { resultsChecksum });
|
|
1125
|
+
}
|
|
1126
|
+
/** Converts an ASFF document to HDF Results JSON. */
|
|
1127
|
+
async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
1128
|
+
if (!input || input.trim().length === 0) throw new Error("asff: empty input");
|
|
1129
|
+
validateInputSize(input, "asff");
|
|
1130
|
+
const findings = parseFindings$1(input);
|
|
1131
|
+
const resultsChecksum = await inputChecksum(input);
|
|
1132
|
+
const order = [];
|
|
1133
|
+
const byBaseline = /* @__PURE__ */ new Map();
|
|
1134
|
+
const accounts = [];
|
|
1135
|
+
const seenAccount = /* @__PURE__ */ new Set();
|
|
1136
|
+
for (const f of findings) {
|
|
1137
|
+
const name = baselineName(f);
|
|
1138
|
+
const existing = byBaseline.get(name);
|
|
1139
|
+
if (existing) existing.push(f);
|
|
1140
|
+
else {
|
|
1141
|
+
order.push(name);
|
|
1142
|
+
byBaseline.set(name, [f]);
|
|
1143
|
+
}
|
|
1144
|
+
if (f.AwsAccountId && !seenAccount.has(f.AwsAccountId)) {
|
|
1145
|
+
seenAccount.add(f.AwsAccountId);
|
|
1146
|
+
accounts.push(f.AwsAccountId);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
let baselines = order.map((name) => buildBaseline$1(name, byBaseline.get(name), resultsChecksum));
|
|
1150
|
+
if (baselines.length === 0) baselines = [createMinimalBaseline("AWS Security Finding Format", [buildNoFindingsRequirement("asff-no-findings", "AWS Security Finding Format input contained zero findings.", /* @__PURE__ */ new Date())], { resultsChecksum })];
|
|
1151
|
+
const hdf = {
|
|
1152
|
+
baselines,
|
|
1153
|
+
generator: {
|
|
1154
|
+
name: "asff-to-hdf",
|
|
1155
|
+
version: converterVersion
|
|
1156
|
+
},
|
|
1157
|
+
tool: {
|
|
1158
|
+
name: "AWS Security Finding Format",
|
|
1159
|
+
format: "JSON"
|
|
1160
|
+
},
|
|
1161
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1162
|
+
};
|
|
1163
|
+
if (accounts.length > 0) {
|
|
1164
|
+
const refs = baselines.map((b) => b.name);
|
|
1165
|
+
hdf.components = accounts.map((acct) => ({
|
|
1166
|
+
name: acct,
|
|
1167
|
+
type: TargetType.CloudAccount,
|
|
1168
|
+
baselineRefs: refs
|
|
1169
|
+
}));
|
|
1170
|
+
}
|
|
1171
|
+
return serializeHdf(hdf);
|
|
1172
|
+
}
|
|
1173
|
+
//#endregion
|
|
801
1174
|
//#region converters/sarif-to-hdf/typescript/converter.ts
|
|
802
1175
|
const IMPACT_MAPPING$9 = {
|
|
803
1176
|
error: .7,
|
|
@@ -1949,6 +2322,9 @@ function parseCklb(input) {
|
|
|
1949
2322
|
return {
|
|
1950
2323
|
format: "cklb",
|
|
1951
2324
|
cklbVersion: nz(doc.cklb_version),
|
|
2325
|
+
active: doc.active ?? false,
|
|
2326
|
+
hasPath: doc.has_path ?? false,
|
|
2327
|
+
mode: doc.mode ?? 0,
|
|
1952
2328
|
asset,
|
|
1953
2329
|
stigs
|
|
1954
2330
|
};
|
|
@@ -1989,8 +2365,9 @@ function serializeCklb(cl) {
|
|
|
1989
2365
|
const doc = {
|
|
1990
2366
|
title: cklbTitle(cl),
|
|
1991
2367
|
cklb_version: cl.cklbVersion || "1.0",
|
|
1992
|
-
active: false,
|
|
1993
|
-
|
|
2368
|
+
active: cl.active ?? false,
|
|
2369
|
+
...cl.mode ? { mode: cl.mode } : {},
|
|
2370
|
+
has_path: cl.hasPath ?? false,
|
|
1994
2371
|
target_data: {
|
|
1995
2372
|
target_type: cl.asset.assetType || "Computing",
|
|
1996
2373
|
host_name: cl.asset.hostName ?? "",
|
|
@@ -2132,6 +2509,9 @@ function assetToComponent(a) {
|
|
|
2132
2509
|
function rootExtensions(cl) {
|
|
2133
2510
|
const ext = { checklistFormat: cl.format || "ckl" };
|
|
2134
2511
|
if (cl.cklbVersion) ext["cklbVersion"] = cl.cklbVersion;
|
|
2512
|
+
if (cl.active) ext["cklbActive"] = true;
|
|
2513
|
+
if (cl.hasPath) ext["cklbHasPath"] = true;
|
|
2514
|
+
if (cl.mode) ext["cklbMode"] = cl.mode;
|
|
2135
2515
|
const ax = {};
|
|
2136
2516
|
setIf$1(ax, "role", cl.asset.role);
|
|
2137
2517
|
setIf$1(ax, "assetType", cl.asset.assetType);
|
|
@@ -2178,6 +2558,9 @@ function hdfToChecklist(input) {
|
|
|
2178
2558
|
return {
|
|
2179
2559
|
format,
|
|
2180
2560
|
cklbVersion: cklbVersion || void 0,
|
|
2561
|
+
active: boolVal(ext, "cklbActive"),
|
|
2562
|
+
hasPath: boolVal(ext, "cklbHasPath"),
|
|
2563
|
+
mode: numVal(ext, "cklbMode"),
|
|
2181
2564
|
asset,
|
|
2182
2565
|
stigs
|
|
2183
2566
|
};
|
|
@@ -2279,6 +2662,13 @@ function strVal(m, key) {
|
|
|
2279
2662
|
const v = m[key];
|
|
2280
2663
|
return typeof v === "string" ? v : "";
|
|
2281
2664
|
}
|
|
2665
|
+
function boolVal(m, key) {
|
|
2666
|
+
return m[key] === true;
|
|
2667
|
+
}
|
|
2668
|
+
function numVal(m, key) {
|
|
2669
|
+
const v = m[key];
|
|
2670
|
+
return typeof v === "number" ? v : 0;
|
|
2671
|
+
}
|
|
2282
2672
|
function strSlice(m, key) {
|
|
2283
2673
|
const v = m[key];
|
|
2284
2674
|
if (Array.isArray(v)) return v.filter((e) => typeof e === "string");
|
|
@@ -3471,6 +3861,18 @@ function checkRevisionAlignment(rules) {
|
|
|
3471
3861
|
if (isNistStrict()) throw new Error(`aws-config: ${detail}\nre-run with a matching revision, or disable strict mode to convert with the gaps`);
|
|
3472
3862
|
console.warn(`WARNING: ${detail}`);
|
|
3473
3863
|
}
|
|
3864
|
+
function buildFixText(rule) {
|
|
3865
|
+
const r = rule.Remediation;
|
|
3866
|
+
if (!r || !r.TargetId) return "";
|
|
3867
|
+
const trigger = r.Automatic ? "automatically on non-compliance" : "on demand";
|
|
3868
|
+
const doc = r.TargetVersion ? `${r.TargetId} (version ${r.TargetVersion})` : r.TargetId;
|
|
3869
|
+
return `Remediate via ${remediationTargetLabel(r.TargetType)} ${doc}, applied ${trigger}.`;
|
|
3870
|
+
}
|
|
3871
|
+
function remediationTargetLabel(targetType) {
|
|
3872
|
+
if (targetType === "SSM_DOCUMENT") return "SSM Automation document";
|
|
3873
|
+
if (!targetType) return "automation document";
|
|
3874
|
+
return `${targetType} document`;
|
|
3875
|
+
}
|
|
3474
3876
|
function buildCheckText(rule) {
|
|
3475
3877
|
const parts = [`ARN: ${rule.ConfigRuleArn || "N/A"}`, `Source Identifier: ${rule.Source.SourceIdentifier || "N/A"}`];
|
|
3476
3878
|
if (rule.InputParameters && rule.InputParameters !== "{}") {
|
|
@@ -3497,31 +3899,25 @@ function buildResult(r) {
|
|
|
3497
3899
|
const q = r.EvaluationResultIdentifier.EvaluationResultQualifier;
|
|
3498
3900
|
const status = mapComplianceStatus(r.ComplianceType);
|
|
3499
3901
|
const codeDesc = buildCodeDesc$7(q);
|
|
3500
|
-
|
|
3501
|
-
const startTime = (r.ConfigRuleInvokedTime ? parseTimestamp(r.ConfigRuleInvokedTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
3502
|
-
const runTime = computeRunTime(r.ConfigRuleInvokedTime, r.ResultRecordedTime);
|
|
3503
|
-
return {
|
|
3504
|
-
status,
|
|
3902
|
+
return createResult(status, buildResultMessage(codeDesc, r.Annotation, status), {
|
|
3505
3903
|
codeDesc,
|
|
3506
|
-
startTime,
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
};
|
|
3904
|
+
startTime: (r.ConfigRuleInvokedTime ? parseTimestamp(r.ConfigRuleInvokedTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z"),
|
|
3905
|
+
runTime: computeRunTime(r.ConfigRuleInvokedTime, r.ResultRecordedTime)
|
|
3906
|
+
});
|
|
3510
3907
|
}
|
|
3511
3908
|
/**
|
|
3512
3909
|
* Synthesizes a single HDF result for a Config rule whose live evaluation
|
|
3513
3910
|
* returned zero in-scope resources. The HDF schema requires `results` to have
|
|
3514
3911
|
* minItems >= 1; this honestly signals to auditors that the rule's check ran
|
|
3515
3912
|
* but had no scope in this account/region rather than vacuously claiming
|
|
3516
|
-
* "passed".
|
|
3913
|
+
* "passed".
|
|
3517
3914
|
*/
|
|
3518
3915
|
function buildNotApplicableResult(rule) {
|
|
3519
3916
|
const codeDesc = `AWS Config rule ${rule.ConfigRuleName} evaluated zero in-scope resources in this account/region.`;
|
|
3520
|
-
return {
|
|
3521
|
-
status: ResultStatus.NotApplicable,
|
|
3917
|
+
return createResult(ResultStatus.NotApplicable, void 0, {
|
|
3522
3918
|
codeDesc,
|
|
3523
3919
|
startTime: /* @__PURE__ */ new Date()
|
|
3524
|
-
};
|
|
3920
|
+
});
|
|
3525
3921
|
}
|
|
3526
3922
|
function buildRequirement$15(rule) {
|
|
3527
3923
|
const nist = buildNistTags$3(rule.Source.SourceIdentifier, rule.ConfigRuleName);
|
|
@@ -3533,6 +3929,11 @@ function buildRequirement$15(rule) {
|
|
|
3533
3929
|
label: "check",
|
|
3534
3930
|
data: buildCheckText(rule)
|
|
3535
3931
|
}];
|
|
3932
|
+
const fix = buildFixText(rule);
|
|
3933
|
+
if (fix) descriptions.push({
|
|
3934
|
+
label: "fix",
|
|
3935
|
+
data: fix
|
|
3936
|
+
});
|
|
3536
3937
|
const title = `${getAccountId(rule.ConfigRuleArn)} - ${rule.ConfigRuleName}`;
|
|
3537
3938
|
const results = rule.EvaluationResults.length > 0 ? rule.EvaluationResults.map(buildResult) : [buildNotApplicableResult(rule)];
|
|
3538
3939
|
const req = createRequirement(rule.ConfigRuleId, title, descriptions, .5, results, {
|
|
@@ -3618,12 +4019,10 @@ function checkToResult(check, scanTime) {
|
|
|
3618
4019
|
const codeDesc = `Resource: ${check.resource}\nFile: ${check.file_path} (lines ${JSON.stringify(check.file_line_range)})`;
|
|
3619
4020
|
let message;
|
|
3620
4021
|
if (status === ResultStatus.NotReviewed && check.check_result.suppress_comment) message = check.check_result.suppress_comment;
|
|
3621
|
-
return {
|
|
3622
|
-
status,
|
|
4022
|
+
return createResult(status, message, {
|
|
3623
4023
|
codeDesc,
|
|
3624
|
-
startTime: scanTime
|
|
3625
|
-
|
|
3626
|
-
};
|
|
4024
|
+
startTime: scanTime
|
|
4025
|
+
});
|
|
3627
4026
|
}
|
|
3628
4027
|
/**
|
|
3629
4028
|
* Converts a group of checks sharing a check_id into one EvaluatedRequirement.
|
|
@@ -4645,472 +5044,202 @@ function parseBom(input) {
|
|
|
4645
5044
|
}
|
|
4646
5045
|
}
|
|
4647
5046
|
//#endregion
|
|
4648
|
-
//#region
|
|
4649
|
-
const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
4650
|
-
"CVSSv2",
|
|
4651
|
-
"CVSSv3",
|
|
4652
|
-
"CVSSv31",
|
|
4653
|
-
"CVSSv4"
|
|
4654
|
-
]);
|
|
5047
|
+
//#region shared/typescript/exportmap.ts
|
|
4655
5048
|
/**
|
|
4656
|
-
*
|
|
4657
|
-
*
|
|
5049
|
+
* Generic, source-tool-agnostic mapping helpers shared by the HDF export
|
|
5050
|
+
* converters (hdf-to-ecs, hdf-to-splunk, ...).
|
|
5051
|
+
*
|
|
5052
|
+
* The export converters deliberately operate on generically-parsed JSON rather
|
|
5053
|
+
* than typed HDF structs, so their output can be held byte-identical with the
|
|
5054
|
+
* Go implementations. This module centralizes the generic accessors, the status
|
|
5055
|
+
* roll-up, the requirement/document field extraction, and the canonical
|
|
5056
|
+
* (key-sorted, HTML-unescaped) line serialization. Target-specific event shaping
|
|
5057
|
+
* (ECS field names, CIM field names, envelopes) stays in each converter.
|
|
4658
5058
|
*/
|
|
4659
|
-
function
|
|
4660
|
-
|
|
4661
|
-
let max = 0;
|
|
4662
|
-
for (const rating of ratings) {
|
|
4663
|
-
let impact;
|
|
4664
|
-
if (rating.method && CVSS_METHODS.has(rating.method) && rating.score !== void 0 && rating.score !== null) impact = rating.score / 10;
|
|
4665
|
-
else impact = severityToImpact$1(rating.severity ?? "medium");
|
|
4666
|
-
if (impact > max) max = impact;
|
|
4667
|
-
}
|
|
4668
|
-
return max;
|
|
5059
|
+
function asMap(v) {
|
|
5060
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
4669
5061
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
*/
|
|
4673
|
-
function formatRatingsTag(ratings) {
|
|
4674
|
-
return ratings.map((r) => `${r.source?.name ?? "Unknown"} - ${r.severity ?? "unrated"}`).join(", ");
|
|
5062
|
+
function asArr(v) {
|
|
5063
|
+
return Array.isArray(v) ? v : void 0;
|
|
4675
5064
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
function formatCodeDesc$4(componentLookup, ref) {
|
|
4680
|
-
const comp = componentLookup.get(ref);
|
|
4681
|
-
if (!comp) return `Component ${ref} is vulnerable`;
|
|
4682
|
-
let name = "";
|
|
4683
|
-
if (comp.group) name += comp.group + "/";
|
|
4684
|
-
name += comp.name;
|
|
4685
|
-
if (comp.version) name += "@" + comp.version;
|
|
4686
|
-
return `Component ${name} is vulnerable`;
|
|
5065
|
+
function getStr(m, key) {
|
|
5066
|
+
const v = m?.[key];
|
|
5067
|
+
return typeof v === "string" ? v : "";
|
|
4687
5068
|
}
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
function
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
5069
|
+
function setIf(m, key, val) {
|
|
5070
|
+
if (val !== "") m[key] = val;
|
|
5071
|
+
}
|
|
5072
|
+
function stringSlice(v) {
|
|
5073
|
+
if (typeof v === "string") return [v];
|
|
5074
|
+
if (Array.isArray(v)) return v.filter((e) => typeof e === "string");
|
|
5075
|
+
return [];
|
|
5076
|
+
}
|
|
5077
|
+
/** Most-significant status across a requirement's results[] (lossless). */
|
|
5078
|
+
function worstOfResults(req) {
|
|
5079
|
+
const results = asArr(req.results) ?? [];
|
|
5080
|
+
const precedence = [
|
|
5081
|
+
"failed",
|
|
5082
|
+
"error",
|
|
5083
|
+
"passed",
|
|
5084
|
+
"notReviewed",
|
|
5085
|
+
"notApplicable"
|
|
5086
|
+
];
|
|
5087
|
+
const present = /* @__PURE__ */ new Set();
|
|
5088
|
+
for (const rRaw of results) {
|
|
5089
|
+
const r = asMap(rRaw);
|
|
5090
|
+
if (r) present.add(getStr(r, "status"));
|
|
4696
5091
|
}
|
|
4697
|
-
return
|
|
5092
|
+
for (const s of precedence) if (present.has(s)) return s;
|
|
5093
|
+
return "notReviewed";
|
|
4698
5094
|
}
|
|
4699
5095
|
/**
|
|
4700
|
-
*
|
|
4701
|
-
*
|
|
5096
|
+
* Whether an HDF Result_Status is a failing verdict. Only 'failed' fails;
|
|
5097
|
+
* 'error' is indeterminate (not a compliance failure) and every other value is
|
|
5098
|
+
* non-failing. Single shared definition of "failing" for the suppression axis
|
|
5099
|
+
* and the per-exporter outcome maps.
|
|
4702
5100
|
*/
|
|
4703
|
-
function
|
|
4704
|
-
return
|
|
5101
|
+
function isFailing(status) {
|
|
5102
|
+
return status === "failed";
|
|
4705
5103
|
}
|
|
4706
|
-
/**
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
5104
|
+
/** Resolve the status context for a requirement. */
|
|
5105
|
+
function statusOf(req) {
|
|
5106
|
+
const raw = worstOfResults(req);
|
|
5107
|
+
const effective = getStr(req, "effectiveStatus");
|
|
5108
|
+
const overrides = asArr(req.statusOverrides);
|
|
5109
|
+
const rollup = effective !== "" ? effective : raw;
|
|
5110
|
+
return {
|
|
5111
|
+
raw,
|
|
5112
|
+
effective,
|
|
5113
|
+
rollup,
|
|
5114
|
+
overridden: overrides !== void 0 && overrides.length > 0 || "effectiveStatus" in req,
|
|
5115
|
+
suppressed: isFailing(raw) && !isFailing(rollup)
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
function firstComponent(doc) {
|
|
5119
|
+
const comps = asArr(doc.components);
|
|
5120
|
+
if (!comps || comps.length === 0) return void 0;
|
|
5121
|
+
return asMap(comps[0]);
|
|
5122
|
+
}
|
|
5123
|
+
function firstResultStartTime(req, fallback) {
|
|
5124
|
+
const results = asArr(req.results) ?? [];
|
|
5125
|
+
if (results.length > 0) {
|
|
5126
|
+
const st = getStr(asMap(results[0]), "startTime");
|
|
5127
|
+
if (st !== "") return st;
|
|
4722
5128
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
const
|
|
4727
|
-
const
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
const { items: limitedVulns, truncated: truncatedVulns } = limitArray(bom.vulnerabilities ?? []);
|
|
4731
|
-
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
4732
|
-
if (truncatedVulns) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${(bom.vulnerabilities ?? []).length})`);
|
|
4733
|
-
for (const vuln of limitedVulns) {
|
|
4734
|
-
const ratings = vuln.ratings ?? [];
|
|
4735
|
-
const impact = maxImpact(ratings);
|
|
4736
|
-
const cwes = vuln.cwes ?? [];
|
|
4737
|
-
const nist = mapCWEToNIST(cwes.map((c) => `${c}`), DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
4738
|
-
const tags = {
|
|
4739
|
-
nist,
|
|
4740
|
-
cci: nistToCci(nist)
|
|
4741
|
-
};
|
|
4742
|
-
if (cwes.length > 0) tags["cweid"] = cwes.map((c) => `CWE-${c}`);
|
|
4743
|
-
if (ratings.length > 0) tags["ratings"] = formatRatingsTag(ratings);
|
|
4744
|
-
const descriptions = [];
|
|
4745
|
-
const defaultParts = [];
|
|
4746
|
-
if (vuln.description) defaultParts.push(`Description: ${vuln.description}`);
|
|
4747
|
-
if (vuln.detail) defaultParts.push(`Detail: ${vuln.detail}`);
|
|
4748
|
-
if (defaultParts.length > 0) descriptions.push({
|
|
4749
|
-
label: "default",
|
|
4750
|
-
data: defaultParts.join("\n\n")
|
|
4751
|
-
});
|
|
4752
|
-
else descriptions.push({
|
|
4753
|
-
label: "default",
|
|
4754
|
-
data: vuln.id
|
|
4755
|
-
});
|
|
4756
|
-
const fixParts = [];
|
|
4757
|
-
if (vuln.recommendation) fixParts.push(vuln.recommendation);
|
|
4758
|
-
if (vuln.analysis?.detail) fixParts.push(`Workaround: ${vuln.analysis.detail}`);
|
|
4759
|
-
if (fixParts.length > 0) descriptions.push({
|
|
4760
|
-
label: "fix",
|
|
4761
|
-
data: fixParts.join("\n\n")
|
|
4762
|
-
});
|
|
4763
|
-
const affects = vuln.affects ?? [];
|
|
4764
|
-
const toResult = (codeDesc) => ({
|
|
4765
|
-
status: ResultStatus.Failed,
|
|
4766
|
-
codeDesc,
|
|
4767
|
-
startTime: scanTime
|
|
4768
|
-
});
|
|
4769
|
-
const results = affects.length > 0 ? affects.map((affect) => toResult(formatCodeDesc$4(componentLookup, affect.ref))) : [toResult(`Vulnerability ${vuln.id}`)];
|
|
4770
|
-
const title = vuln.source?.name ? `${vuln.id} (${vuln.source.name})` : vuln.id;
|
|
4771
|
-
const req = createRequirement(vuln.id, title, descriptions, impact, results, { tags });
|
|
4772
|
-
const controlType = deriveControlTypeFromTags(nist);
|
|
4773
|
-
if (controlType !== void 0) req.controlType = controlType;
|
|
4774
|
-
requirements.push(req);
|
|
5129
|
+
return fallback;
|
|
5130
|
+
}
|
|
5131
|
+
function defaultDescription(req) {
|
|
5132
|
+
const descs = asArr(req.descriptions) ?? [];
|
|
5133
|
+
for (const dRaw of descs) {
|
|
5134
|
+
const d = asMap(dRaw);
|
|
5135
|
+
if (d && getStr(d, "label") === "default") return getStr(d, "data");
|
|
4775
5136
|
}
|
|
4776
|
-
|
|
4777
|
-
const targetComponent = {
|
|
4778
|
-
name: bom.metadata?.component?.name ?? "",
|
|
4779
|
-
type: TargetType.Application
|
|
4780
|
-
};
|
|
4781
|
-
if (bom.metadata?.component?.version) targetComponent.version = bom.metadata.component.version;
|
|
4782
|
-
const parsedBom = parseBom(input);
|
|
4783
|
-
const bomParts = {
|
|
4784
|
-
bomType: BOMType.Sbom,
|
|
4785
|
-
format: "cyclonedx",
|
|
4786
|
-
uniqueId: parsedBom.normalized.uniqueId,
|
|
4787
|
-
document: JSON.parse(input)
|
|
4788
|
-
};
|
|
4789
|
-
if (parsedBom.normalized.packages && parsedBom.normalized.packages.length > 0) bomParts.packages = parsedBom.normalized.packages;
|
|
4790
|
-
const componentBom = buildBom(bomParts);
|
|
4791
|
-
return buildHdfResults({
|
|
4792
|
-
generatorName: "cyclonedx-to-hdf",
|
|
4793
|
-
converterVersion,
|
|
4794
|
-
toolName: "CycloneDX",
|
|
4795
|
-
toolFormat: "JSON",
|
|
4796
|
-
baselines: [baseline],
|
|
4797
|
-
components: [{
|
|
4798
|
-
...targetComponent,
|
|
4799
|
-
boms: [componentBom]
|
|
4800
|
-
}],
|
|
4801
|
-
timestamp: scanTime
|
|
4802
|
-
});
|
|
5137
|
+
return "";
|
|
4803
5138
|
}
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
if (
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
})) : [{
|
|
4822
|
-
name: "",
|
|
4823
|
-
type: ""
|
|
4824
|
-
}];
|
|
4825
|
-
for (const baseline of hdf.baselines) for (const requirement of baseline.requirements) for (const target of targetList) rows.push(createRow(baseline, requirement, target));
|
|
4826
|
-
return buildCsv(rows, { sanitize: true });
|
|
5139
|
+
function firstRefURL(req) {
|
|
5140
|
+
const refs = asArr(req.refs) ?? [];
|
|
5141
|
+
for (const rRaw of refs) {
|
|
5142
|
+
const url = getStr(asMap(rRaw), "url");
|
|
5143
|
+
if (url !== "") return url;
|
|
5144
|
+
}
|
|
5145
|
+
return "";
|
|
5146
|
+
}
|
|
5147
|
+
/** Deterministic event id: component | baseline | control. */
|
|
5148
|
+
function eventID(component, baselineName, controlID) {
|
|
5149
|
+
let comp = "";
|
|
5150
|
+
if (component) comp = getStr(component, "componentId") || getStr(component, "name");
|
|
5151
|
+
return [
|
|
5152
|
+
comp,
|
|
5153
|
+
baselineName,
|
|
5154
|
+
controlID
|
|
5155
|
+
].join("|");
|
|
4827
5156
|
}
|
|
4828
5157
|
/**
|
|
4829
|
-
*
|
|
5158
|
+
* Build the lossless hdf.* namespace shared by the export converters: promoted
|
|
5159
|
+
* snake_case scalars plus the full requirement sub-objects preserved verbatim.
|
|
5160
|
+
* `status` is the lossless results roll-up (statusOf().raw); `suppressed` is the
|
|
5161
|
+
* acceptance axis (statusOf().suppressed).
|
|
4830
5162
|
*/
|
|
4831
|
-
function
|
|
4832
|
-
const
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
const references = flattenRefs(requirement.refs);
|
|
4838
|
-
const severity = getSeverity(requirement);
|
|
4839
|
-
const firstResult = requirement.results[0];
|
|
4840
|
-
const status = firstResult?.status || "";
|
|
4841
|
-
const message = firstResult?.message || "";
|
|
4842
|
-
const nistControls = extractArrayFromTags(requirement.tags, "nist");
|
|
4843
|
-
const cciControls = extractArrayFromTags(requirement.tags, "cci");
|
|
4844
|
-
return {
|
|
4845
|
-
"Baseline ID": baseline.name,
|
|
4846
|
-
"Baseline Version": baseline.version || "",
|
|
4847
|
-
"Baseline Title": baseline.title || "",
|
|
4848
|
-
"Target ID": target.name,
|
|
4849
|
-
"Target Type": target.type,
|
|
4850
|
-
"Requirement ID": requirement.id,
|
|
4851
|
-
"Requirement Title": requirement.title || "",
|
|
4852
|
-
"Description": description,
|
|
4853
|
-
"Check": check,
|
|
4854
|
-
"Fix": fix,
|
|
4855
|
-
"Rationale": rationale,
|
|
4856
|
-
"Code": code,
|
|
4857
|
-
"References": references,
|
|
4858
|
-
"Severity": severity,
|
|
4859
|
-
"Impact": requirement.impact,
|
|
4860
|
-
"Status": String(status),
|
|
4861
|
-
"NIST Controls": nistControls,
|
|
4862
|
-
"CCI Controls": cciControls,
|
|
4863
|
-
"Control Type": requirement.controlType ?? "",
|
|
4864
|
-
"Verification Method": requirement.verificationMethod ?? "",
|
|
4865
|
-
"Applicability": requirement.applicability ?? "",
|
|
4866
|
-
"Result Message": message
|
|
5163
|
+
function buildHDFBlock(req, baseline, status, overridden, suppressed, generator, tool, converterVersion) {
|
|
5164
|
+
const hdf = {
|
|
5165
|
+
status,
|
|
5166
|
+
overridden,
|
|
5167
|
+
suppressed,
|
|
5168
|
+
exporter_version: converterVersion
|
|
4867
5169
|
};
|
|
5170
|
+
setIf(hdf, "control_id", getStr(req, "id"));
|
|
5171
|
+
setIf(hdf, "baseline", getStr(baseline, "name"));
|
|
5172
|
+
if ("effectiveStatus" in req) hdf.effective_status = req.effectiveStatus;
|
|
5173
|
+
if ("effectiveImpact" in req) hdf.effective_impact = req.effectiveImpact;
|
|
5174
|
+
if ("impact" in req) hdf.impact = req.impact;
|
|
5175
|
+
if ("severity" in req) hdf.severity = req.severity;
|
|
5176
|
+
if ("disposition" in req) hdf.disposition = req.disposition;
|
|
5177
|
+
const tags = asMap(req.tags);
|
|
5178
|
+
if (tags?.nist !== void 0) hdf.nist = tags.nist;
|
|
5179
|
+
if (tags?.cci !== void 0) hdf.cci = tags.cci;
|
|
5180
|
+
for (const [src, dst] of Object.entries({
|
|
5181
|
+
tags: "tags",
|
|
5182
|
+
cvss: "cvss",
|
|
5183
|
+
cwe: "cwe",
|
|
5184
|
+
epss: "epss",
|
|
5185
|
+
kev: "kev",
|
|
5186
|
+
affectedPackages: "affected_packages",
|
|
5187
|
+
descriptions: "descriptions",
|
|
5188
|
+
results: "results",
|
|
5189
|
+
statusOverrides: "status_overrides",
|
|
5190
|
+
poams: "poams",
|
|
5191
|
+
code: "code",
|
|
5192
|
+
refs: "refs"
|
|
5193
|
+
})) if (src in req) hdf[dst] = req[src];
|
|
5194
|
+
if (generator) hdf.generator = generator;
|
|
5195
|
+
if (tool) hdf.tool = tool;
|
|
5196
|
+
return hdf;
|
|
4868
5197
|
}
|
|
4869
5198
|
/**
|
|
4870
|
-
*
|
|
5199
|
+
* A numeric value that must serialize with an explicit decimal point, so a
|
|
5200
|
+
* whole-number renders as `10.0` rather than the integer `10`. Some consumers
|
|
5201
|
+
* type-check strictly (OCSF's `float_t` rejects an integer-shaped token).
|
|
5202
|
+
* JSON.stringify cannot emit `10.0` for a JS number, so the value is carried in
|
|
5203
|
+
* this wrapper (passed through canonicalize untouched) and rendered as a bare
|
|
5204
|
+
* numeric token by stringifyLine. Go's counterpart is encoding/json's json.Number.
|
|
4871
5205
|
*/
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
const sev = requirement.tags.severity;
|
|
4876
|
-
if (typeof sev === "string") return sev;
|
|
4877
|
-
if (Array.isArray(sev) && sev.length > 0) return String(sev[0]);
|
|
4878
|
-
}
|
|
5206
|
+
var RawNumber = class {
|
|
5207
|
+
constructor(token) {
|
|
5208
|
+
this.token = token;
|
|
4879
5209
|
}
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
5210
|
+
};
|
|
5211
|
+
/** Wrap a number as a RawNumber whose token always bears a decimal point. */
|
|
5212
|
+
function floatNumber(f) {
|
|
5213
|
+
let s = String(f);
|
|
5214
|
+
if (!/[.eE]/.test(s)) s += ".0";
|
|
5215
|
+
return new RawNumber(s);
|
|
4884
5216
|
}
|
|
5217
|
+
const RAWNUM_MARK = String.fromCharCode(1);
|
|
5218
|
+
const RAWNUM_RE = /"\\u0001(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\\u0001"/g;
|
|
4885
5219
|
/**
|
|
4886
|
-
*
|
|
5220
|
+
* Compare two strings by Unicode code point. This matches Go's `encoding/json`
|
|
5221
|
+
* key ordering, which is a bytewise comparison of the UTF-8 encoding \u2014 and UTF-8
|
|
5222
|
+
* byte order is identical to code-point order. JS's default Array.sort() instead
|
|
5223
|
+
* compares UTF-16 code units, which diverges for supplementary-plane (non-BMP)
|
|
5224
|
+
* characters: a lead surrogate (0xD800\u20130xDBFF) sorts before a BMP char in
|
|
5225
|
+
* [0xE000,0xFFFF] under UTF-16, but a non-BMP code point (\u22650x10000) sorts after
|
|
5226
|
+
* it under UTF-8. Iterating the string yields whole code points (surrogate pairs
|
|
5227
|
+
* combined), so this comparator is correct for the full Unicode range.
|
|
4887
5228
|
*/
|
|
4888
|
-
function
|
|
4889
|
-
|
|
4890
|
-
const
|
|
4891
|
-
|
|
4892
|
-
|
|
5229
|
+
function byCodePoint(a, b) {
|
|
5230
|
+
const ai = [...a];
|
|
5231
|
+
const bi = [...b];
|
|
5232
|
+
const n = Math.min(ai.length, bi.length);
|
|
5233
|
+
for (let i = 0; i < n; i++) {
|
|
5234
|
+
const ca = ai[i]?.codePointAt(0) ?? 0;
|
|
5235
|
+
const cb = bi[i]?.codePointAt(0) ?? 0;
|
|
5236
|
+
if (ca !== cb) return ca - cb;
|
|
5237
|
+
}
|
|
5238
|
+
return ai.length - bi.length;
|
|
4893
5239
|
}
|
|
4894
5240
|
/**
|
|
4895
|
-
*
|
|
4896
|
-
|
|
4897
|
-
function descriptionByLabel(requirement, label) {
|
|
4898
|
-
return requirement.descriptions.find((d) => d.label === label)?.data || "";
|
|
4899
|
-
}
|
|
4900
|
-
/**
|
|
4901
|
-
* Flatten a requirement's refs to one string: each Reference rendered as its
|
|
4902
|
-
* url/uri (or a string `ref`); array-form refs are skipped. Joined with '; ' to
|
|
4903
|
-
* match the NIST/CCI column convention.
|
|
4904
|
-
*/
|
|
4905
|
-
function flattenRefs(refs) {
|
|
4906
|
-
if (!Array.isArray(refs)) return "";
|
|
4907
|
-
const out = [];
|
|
4908
|
-
for (const r of refs) {
|
|
4909
|
-
if (!r || typeof r !== "object") continue;
|
|
4910
|
-
const o = r;
|
|
4911
|
-
if (typeof o.url === "string") out.push(o.url);
|
|
4912
|
-
else if (typeof o.uri === "string") out.push(o.uri);
|
|
4913
|
-
else if (typeof o.ref === "string") out.push(o.ref);
|
|
4914
|
-
}
|
|
4915
|
-
return out.join("; ");
|
|
4916
|
-
}
|
|
4917
|
-
//#endregion
|
|
4918
|
-
//#region shared/typescript/exportmap.ts
|
|
4919
|
-
/**
|
|
4920
|
-
* Generic, source-tool-agnostic mapping helpers shared by the HDF export
|
|
4921
|
-
* converters (hdf-to-ecs, hdf-to-splunk, ...).
|
|
4922
|
-
*
|
|
4923
|
-
* The export converters deliberately operate on generically-parsed JSON rather
|
|
4924
|
-
* than typed HDF structs, so their output can be held byte-identical with the
|
|
4925
|
-
* Go implementations. This module centralizes the generic accessors, the status
|
|
4926
|
-
* roll-up, the requirement/document field extraction, and the canonical
|
|
4927
|
-
* (key-sorted, HTML-unescaped) line serialization. Target-specific event shaping
|
|
4928
|
-
* (ECS field names, CIM field names, envelopes) stays in each converter.
|
|
4929
|
-
*/
|
|
4930
|
-
function asMap(v) {
|
|
4931
|
-
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
4932
|
-
}
|
|
4933
|
-
function asArr(v) {
|
|
4934
|
-
return Array.isArray(v) ? v : void 0;
|
|
4935
|
-
}
|
|
4936
|
-
function getStr(m, key) {
|
|
4937
|
-
const v = m?.[key];
|
|
4938
|
-
return typeof v === "string" ? v : "";
|
|
4939
|
-
}
|
|
4940
|
-
function setIf(m, key, val) {
|
|
4941
|
-
if (val !== "") m[key] = val;
|
|
4942
|
-
}
|
|
4943
|
-
function stringSlice(v) {
|
|
4944
|
-
if (typeof v === "string") return [v];
|
|
4945
|
-
if (Array.isArray(v)) return v.filter((e) => typeof e === "string");
|
|
4946
|
-
return [];
|
|
4947
|
-
}
|
|
4948
|
-
/** Most-significant status across a requirement's results[] (lossless). */
|
|
4949
|
-
function worstOfResults(req) {
|
|
4950
|
-
const results = asArr(req.results) ?? [];
|
|
4951
|
-
const precedence = [
|
|
4952
|
-
"failed",
|
|
4953
|
-
"error",
|
|
4954
|
-
"passed",
|
|
4955
|
-
"notReviewed",
|
|
4956
|
-
"notApplicable"
|
|
4957
|
-
];
|
|
4958
|
-
const present = /* @__PURE__ */ new Set();
|
|
4959
|
-
for (const rRaw of results) {
|
|
4960
|
-
const r = asMap(rRaw);
|
|
4961
|
-
if (r) present.add(getStr(r, "status"));
|
|
4962
|
-
}
|
|
4963
|
-
for (const s of precedence) if (present.has(s)) return s;
|
|
4964
|
-
return "notReviewed";
|
|
4965
|
-
}
|
|
4966
|
-
/**
|
|
4967
|
-
* Whether an HDF Result_Status is a failing verdict. Only 'failed' fails;
|
|
4968
|
-
* 'error' is indeterminate (not a compliance failure) and every other value is
|
|
4969
|
-
* non-failing. Single shared definition of "failing" for the suppression axis
|
|
4970
|
-
* and the per-exporter outcome maps.
|
|
4971
|
-
*/
|
|
4972
|
-
function isFailing(status) {
|
|
4973
|
-
return status === "failed";
|
|
4974
|
-
}
|
|
4975
|
-
/** Resolve the status context for a requirement. */
|
|
4976
|
-
function statusOf(req) {
|
|
4977
|
-
const raw = worstOfResults(req);
|
|
4978
|
-
const effective = getStr(req, "effectiveStatus");
|
|
4979
|
-
const overrides = asArr(req.statusOverrides);
|
|
4980
|
-
const rollup = effective !== "" ? effective : raw;
|
|
4981
|
-
return {
|
|
4982
|
-
raw,
|
|
4983
|
-
effective,
|
|
4984
|
-
rollup,
|
|
4985
|
-
overridden: overrides !== void 0 && overrides.length > 0 || "effectiveStatus" in req,
|
|
4986
|
-
suppressed: isFailing(raw) && !isFailing(rollup)
|
|
4987
|
-
};
|
|
4988
|
-
}
|
|
4989
|
-
function firstComponent(doc) {
|
|
4990
|
-
const comps = asArr(doc.components);
|
|
4991
|
-
if (!comps || comps.length === 0) return void 0;
|
|
4992
|
-
return asMap(comps[0]);
|
|
4993
|
-
}
|
|
4994
|
-
function firstResultStartTime(req, fallback) {
|
|
4995
|
-
const results = asArr(req.results) ?? [];
|
|
4996
|
-
if (results.length > 0) {
|
|
4997
|
-
const st = getStr(asMap(results[0]), "startTime");
|
|
4998
|
-
if (st !== "") return st;
|
|
4999
|
-
}
|
|
5000
|
-
return fallback;
|
|
5001
|
-
}
|
|
5002
|
-
function defaultDescription(req) {
|
|
5003
|
-
const descs = asArr(req.descriptions) ?? [];
|
|
5004
|
-
for (const dRaw of descs) {
|
|
5005
|
-
const d = asMap(dRaw);
|
|
5006
|
-
if (d && getStr(d, "label") === "default") return getStr(d, "data");
|
|
5007
|
-
}
|
|
5008
|
-
return "";
|
|
5009
|
-
}
|
|
5010
|
-
function firstRefURL(req) {
|
|
5011
|
-
const refs = asArr(req.refs) ?? [];
|
|
5012
|
-
for (const rRaw of refs) {
|
|
5013
|
-
const url = getStr(asMap(rRaw), "url");
|
|
5014
|
-
if (url !== "") return url;
|
|
5015
|
-
}
|
|
5016
|
-
return "";
|
|
5017
|
-
}
|
|
5018
|
-
/** Deterministic event id: component | baseline | control. */
|
|
5019
|
-
function eventID(component, baselineName, controlID) {
|
|
5020
|
-
let comp = "";
|
|
5021
|
-
if (component) comp = getStr(component, "componentId") || getStr(component, "name");
|
|
5022
|
-
return [
|
|
5023
|
-
comp,
|
|
5024
|
-
baselineName,
|
|
5025
|
-
controlID
|
|
5026
|
-
].join("|");
|
|
5027
|
-
}
|
|
5028
|
-
/**
|
|
5029
|
-
* Build the lossless hdf.* namespace shared by the export converters: promoted
|
|
5030
|
-
* snake_case scalars plus the full requirement sub-objects preserved verbatim.
|
|
5031
|
-
* `status` is the lossless results roll-up (statusOf().raw); `suppressed` is the
|
|
5032
|
-
* acceptance axis (statusOf().suppressed).
|
|
5033
|
-
*/
|
|
5034
|
-
function buildHDFBlock(req, baseline, status, overridden, suppressed, generator, tool, converterVersion) {
|
|
5035
|
-
const hdf = {
|
|
5036
|
-
status,
|
|
5037
|
-
overridden,
|
|
5038
|
-
suppressed,
|
|
5039
|
-
exporter_version: converterVersion
|
|
5040
|
-
};
|
|
5041
|
-
setIf(hdf, "control_id", getStr(req, "id"));
|
|
5042
|
-
setIf(hdf, "baseline", getStr(baseline, "name"));
|
|
5043
|
-
if ("effectiveStatus" in req) hdf.effective_status = req.effectiveStatus;
|
|
5044
|
-
if ("effectiveImpact" in req) hdf.effective_impact = req.effectiveImpact;
|
|
5045
|
-
if ("impact" in req) hdf.impact = req.impact;
|
|
5046
|
-
if ("severity" in req) hdf.severity = req.severity;
|
|
5047
|
-
if ("disposition" in req) hdf.disposition = req.disposition;
|
|
5048
|
-
const tags = asMap(req.tags);
|
|
5049
|
-
if (tags?.nist !== void 0) hdf.nist = tags.nist;
|
|
5050
|
-
if (tags?.cci !== void 0) hdf.cci = tags.cci;
|
|
5051
|
-
for (const [src, dst] of Object.entries({
|
|
5052
|
-
tags: "tags",
|
|
5053
|
-
cvss: "cvss",
|
|
5054
|
-
cwe: "cwe",
|
|
5055
|
-
epss: "epss",
|
|
5056
|
-
kev: "kev",
|
|
5057
|
-
affectedPackages: "affected_packages",
|
|
5058
|
-
descriptions: "descriptions",
|
|
5059
|
-
results: "results",
|
|
5060
|
-
statusOverrides: "status_overrides",
|
|
5061
|
-
poams: "poams",
|
|
5062
|
-
code: "code",
|
|
5063
|
-
refs: "refs"
|
|
5064
|
-
})) if (src in req) hdf[dst] = req[src];
|
|
5065
|
-
if (generator) hdf.generator = generator;
|
|
5066
|
-
if (tool) hdf.tool = tool;
|
|
5067
|
-
return hdf;
|
|
5068
|
-
}
|
|
5069
|
-
/**
|
|
5070
|
-
* A numeric value that must serialize with an explicit decimal point, so a
|
|
5071
|
-
* whole-number renders as `10.0` rather than the integer `10`. Some consumers
|
|
5072
|
-
* type-check strictly (OCSF's `float_t` rejects an integer-shaped token).
|
|
5073
|
-
* JSON.stringify cannot emit `10.0` for a JS number, so the value is carried in
|
|
5074
|
-
* this wrapper (passed through canonicalize untouched) and rendered as a bare
|
|
5075
|
-
* numeric token by stringifyLine. Go's counterpart is encoding/json's json.Number.
|
|
5076
|
-
*/
|
|
5077
|
-
var RawNumber = class {
|
|
5078
|
-
constructor(token) {
|
|
5079
|
-
this.token = token;
|
|
5080
|
-
}
|
|
5081
|
-
};
|
|
5082
|
-
/** Wrap a number as a RawNumber whose token always bears a decimal point. */
|
|
5083
|
-
function floatNumber(f) {
|
|
5084
|
-
let s = String(f);
|
|
5085
|
-
if (!/[.eE]/.test(s)) s += ".0";
|
|
5086
|
-
return new RawNumber(s);
|
|
5087
|
-
}
|
|
5088
|
-
const RAWNUM_MARK = String.fromCharCode(1);
|
|
5089
|
-
const RAWNUM_RE = /"\\u0001(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\\u0001"/g;
|
|
5090
|
-
/**
|
|
5091
|
-
* Compare two strings by Unicode code point. This matches Go's `encoding/json`
|
|
5092
|
-
* key ordering, which is a bytewise comparison of the UTF-8 encoding \u2014 and UTF-8
|
|
5093
|
-
* byte order is identical to code-point order. JS's default Array.sort() instead
|
|
5094
|
-
* compares UTF-16 code units, which diverges for supplementary-plane (non-BMP)
|
|
5095
|
-
* characters: a lead surrogate (0xD800\u20130xDBFF) sorts before a BMP char in
|
|
5096
|
-
* [0xE000,0xFFFF] under UTF-16, but a non-BMP code point (\u22650x10000) sorts after
|
|
5097
|
-
* it under UTF-8. Iterating the string yields whole code points (surrogate pairs
|
|
5098
|
-
* combined), so this comparator is correct for the full Unicode range.
|
|
5099
|
-
*/
|
|
5100
|
-
function byCodePoint(a, b) {
|
|
5101
|
-
const ai = [...a];
|
|
5102
|
-
const bi = [...b];
|
|
5103
|
-
const n = Math.min(ai.length, bi.length);
|
|
5104
|
-
for (let i = 0; i < n; i++) {
|
|
5105
|
-
const ca = ai[i]?.codePointAt(0) ?? 0;
|
|
5106
|
-
const cb = bi[i]?.codePointAt(0) ?? 0;
|
|
5107
|
-
if (ca !== cb) return ca - cb;
|
|
5108
|
-
}
|
|
5109
|
-
return ai.length - bi.length;
|
|
5110
|
-
}
|
|
5111
|
-
/**
|
|
5112
|
-
* Recursively sort object keys in Go's map-key order (bytewise UTF-8 / code
|
|
5113
|
-
* point) so the emitted JSON is byte-identical to Go's encoder.
|
|
5241
|
+
* Recursively sort object keys in Go's map-key order (bytewise UTF-8 / code
|
|
5242
|
+
* point) so the emitted JSON is byte-identical to Go's encoder.
|
|
5114
5243
|
*/
|
|
5115
5244
|
function canonicalize$1(v) {
|
|
5116
5245
|
if (v instanceof RawNumber) return v;
|
|
@@ -5194,38 +5323,307 @@ function epochMillis(s) {
|
|
|
5194
5323
|
return d.getTime();
|
|
5195
5324
|
}
|
|
5196
5325
|
//#endregion
|
|
5197
|
-
//#region converters/
|
|
5326
|
+
//#region converters/cyclonedx-to-hdf/typescript/converter.ts
|
|
5327
|
+
const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
5328
|
+
"CVSSv2",
|
|
5329
|
+
"CVSSv3",
|
|
5330
|
+
"CVSSv31",
|
|
5331
|
+
"CVSSv4"
|
|
5332
|
+
]);
|
|
5198
5333
|
/**
|
|
5199
|
-
*
|
|
5200
|
-
*
|
|
5201
|
-
* One ECS event per Evaluated_Requirement, in the hybrid shape from ADR-0002:
|
|
5202
|
-
* a core-ECS-native projection plus a lossless hdf.* block, hot filter scalars
|
|
5203
|
-
* promoted flat. Output is plain NDJSON (LF-delimited, trailing newline), ECS
|
|
5204
|
-
* 9.4.0.
|
|
5205
|
-
*
|
|
5206
|
-
* Status is raw-primary: event.outcome carries the RAW verdict (a waived failure
|
|
5207
|
-
* is still failure), and hdf.suppressed is the separate acceptance axis. The
|
|
5208
|
-
* canonical "still actionable" query is
|
|
5209
|
-
* event.outcome:"failure" AND hdf.suppressed:false.
|
|
5210
|
-
*
|
|
5211
|
-
* Generic JSON access, the status roll-up, requirement/document field
|
|
5212
|
-
* extraction, and the canonical line serialization are shared with the other
|
|
5213
|
-
* export converters via ../../../shared/typescript/exportmap.js; only
|
|
5214
|
-
* ECS-specific event shaping lives here. Output is held byte-identical with the
|
|
5215
|
-
* Go implementation via key-sorted, HTML-unescaped compact JSON.
|
|
5334
|
+
* Computes the maximum impact across all ratings for a vulnerability.
|
|
5335
|
+
* Prefers CVSS score/10 when available, falls back to severityToImpact().
|
|
5216
5336
|
*/
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5337
|
+
function maxImpact(ratings) {
|
|
5338
|
+
if (ratings.length === 0) return .5;
|
|
5339
|
+
let max = 0;
|
|
5340
|
+
for (const rating of ratings) {
|
|
5341
|
+
let impact;
|
|
5342
|
+
if (rating.method && CVSS_METHODS.has(rating.method) && rating.score !== void 0 && rating.score !== null) impact = rating.score / 10;
|
|
5343
|
+
else impact = severityToImpact$1(rating.severity ?? "medium");
|
|
5344
|
+
if (impact > max) max = impact;
|
|
5345
|
+
}
|
|
5346
|
+
return max;
|
|
5347
|
+
}
|
|
5348
|
+
/**
|
|
5349
|
+
* Formats the ratings as a human-readable tag string.
|
|
5350
|
+
*/
|
|
5351
|
+
function formatRatingsTag(ratings) {
|
|
5352
|
+
return ratings.map((r) => `${r.source?.name ?? "Unknown"} - ${r.severity ?? "unrated"}`).join(", ");
|
|
5353
|
+
}
|
|
5354
|
+
/**
|
|
5355
|
+
* Formats a component reference as a code_desc string.
|
|
5356
|
+
*/
|
|
5357
|
+
function formatCodeDesc$4(componentLookup, ref) {
|
|
5358
|
+
const comp = componentLookup.get(ref);
|
|
5359
|
+
if (!comp) return `Component ${ref} is vulnerable`;
|
|
5360
|
+
let name = "";
|
|
5361
|
+
if (comp.group) name += comp.group + "/";
|
|
5362
|
+
name += comp.name;
|
|
5363
|
+
if (comp.version) name += "@" + comp.version;
|
|
5364
|
+
return `Component ${name} is vulnerable`;
|
|
5365
|
+
}
|
|
5366
|
+
/**
|
|
5367
|
+
* Flattens nested CycloneDX components into a single array.
|
|
5368
|
+
*/
|
|
5369
|
+
function flattenComponents(components) {
|
|
5370
|
+
const result = [];
|
|
5371
|
+
for (const comp of components) {
|
|
5372
|
+
result.push(comp);
|
|
5373
|
+
if (comp.components) result.push(...flattenComponents(comp.components));
|
|
5374
|
+
}
|
|
5375
|
+
return result;
|
|
5376
|
+
}
|
|
5377
|
+
/**
|
|
5378
|
+
* Reports whether any (possibly nested) component is a machine-learning-model,
|
|
5379
|
+
* i.e. the CycloneDX document is an AI-BOM.
|
|
5380
|
+
*/
|
|
5381
|
+
function hasMLModelComponent(components) {
|
|
5382
|
+
return flattenComponents(components).some((comp) => comp.type === "machine-learning-model");
|
|
5383
|
+
}
|
|
5384
|
+
/**
|
|
5385
|
+
* Converts CycloneDX SBOM/VEX JSON to HDF format.
|
|
5386
|
+
*
|
|
5387
|
+
* @param input - CycloneDX JSON string
|
|
5388
|
+
* @returns HDF JSON string
|
|
5389
|
+
*/
|
|
5390
|
+
async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
5391
|
+
if (!input || input.trim().length === 0) throw new Error("cyclonedx: empty input");
|
|
5392
|
+
validateInputSize(input, "cyclonedx");
|
|
5393
|
+
const bom = parseJSON(input);
|
|
5394
|
+
if (!bom || typeof bom !== "object") throw new Error("cyclonedx: invalid JSON");
|
|
5395
|
+
if (bom.bomFormat !== "CycloneDX") throw new Error(`cyclonedx: missing or invalid bomFormat (expected "CycloneDX", got "${bom.bomFormat ?? "undefined"}")`);
|
|
5396
|
+
if ((!bom.components || bom.components.length === 0) && (!bom.vulnerabilities || bom.vulnerabilities.length === 0)) throw new Error("cyclonedx: input has neither components nor vulnerabilities");
|
|
5397
|
+
if (!bom.vulnerabilities || bom.vulnerabilities.length === 0) {
|
|
5398
|
+
if (hasMLModelComponent(bom.components ?? [])) throw new Error("cyclonedx: this file is a CycloneDX AI-BOM (machine-learning-model inventory) with no vulnerabilities; to import it into a system document, use:\n hdf system create <file> --from cyclonedx-mlbom");
|
|
5399
|
+
throw new Error("cyclonedx: this file is an SBOM inventory with no vulnerabilities; to import SBOM data into a system document, use:\n hdf system create <sbom-file> --component-name <name>");
|
|
5400
|
+
}
|
|
5401
|
+
const resultsChecksum = await inputChecksum(input);
|
|
5402
|
+
const parsedTimestamp = bom.metadata?.timestamp ? parseTimestamp(bom.metadata.timestamp) ?? void 0 : void 0;
|
|
5403
|
+
const scanTime = parsedTimestamp && !isNaN(parsedTimestamp.getTime()) ? parsedTimestamp : /* @__PURE__ */ new Date();
|
|
5404
|
+
const allComponents = flattenComponents(bom.components ?? []);
|
|
5405
|
+
const componentLookup = /* @__PURE__ */ new Map();
|
|
5406
|
+
for (const comp of allComponents) if (comp["bom-ref"]) componentLookup.set(comp["bom-ref"], comp);
|
|
5407
|
+
const requirements = [];
|
|
5408
|
+
const { items: limitedVulns, truncated: truncatedVulns } = limitArray(bom.vulnerabilities ?? []);
|
|
5409
|
+
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
5410
|
+
if (truncatedVulns) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${(bom.vulnerabilities ?? []).length})`);
|
|
5411
|
+
for (const vuln of limitedVulns) {
|
|
5412
|
+
const ratings = vuln.ratings ?? [];
|
|
5413
|
+
const impact = maxImpact(ratings);
|
|
5414
|
+
const cwes = vuln.cwes ?? [];
|
|
5415
|
+
const nist = mapCWEToNIST(cwes.map((c) => `${c}`), DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
5416
|
+
const tags = {
|
|
5417
|
+
nist,
|
|
5418
|
+
cci: nistToCci(nist)
|
|
5419
|
+
};
|
|
5420
|
+
if (cwes.length > 0) tags["cweid"] = cwes.map((c) => `CWE-${c}`);
|
|
5421
|
+
if (ratings.length > 0) tags["ratings"] = formatRatingsTag(ratings);
|
|
5422
|
+
const descriptions = [];
|
|
5423
|
+
const defaultParts = [];
|
|
5424
|
+
if (vuln.description) defaultParts.push(`Description: ${vuln.description}`);
|
|
5425
|
+
if (vuln.detail) defaultParts.push(`Detail: ${vuln.detail}`);
|
|
5426
|
+
if (defaultParts.length > 0) descriptions.push({
|
|
5427
|
+
label: "default",
|
|
5428
|
+
data: defaultParts.join("\n\n")
|
|
5429
|
+
});
|
|
5430
|
+
else descriptions.push({
|
|
5431
|
+
label: "default",
|
|
5432
|
+
data: vuln.id
|
|
5433
|
+
});
|
|
5434
|
+
const fixParts = [];
|
|
5435
|
+
if (vuln.recommendation) fixParts.push(vuln.recommendation);
|
|
5436
|
+
if (vuln.analysis?.detail) fixParts.push(`Workaround: ${vuln.analysis.detail}`);
|
|
5437
|
+
if (fixParts.length > 0) descriptions.push({
|
|
5438
|
+
label: "fix",
|
|
5439
|
+
data: fixParts.join("\n\n")
|
|
5440
|
+
});
|
|
5441
|
+
const affects = vuln.affects ?? [];
|
|
5442
|
+
const toResult = (codeDesc) => createResult(ResultStatus.Failed, void 0, {
|
|
5443
|
+
codeDesc,
|
|
5444
|
+
startTime: scanTime
|
|
5445
|
+
});
|
|
5446
|
+
const results = affects.length > 0 ? affects.map((affect) => toResult(formatCodeDesc$4(componentLookup, affect.ref))) : [toResult(`Vulnerability ${vuln.id}`)];
|
|
5447
|
+
const title = vuln.source?.name ? `${vuln.id} (${vuln.source.name})` : vuln.id;
|
|
5448
|
+
const req = createRequirement(vuln.id, title, descriptions, impact, results, { tags });
|
|
5449
|
+
const controlType = deriveControlTypeFromTags(nist);
|
|
5450
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
5451
|
+
requirements.push(req);
|
|
5452
|
+
}
|
|
5453
|
+
const baseline = createMinimalBaseline("CycloneDX Scan", requirements, { resultsChecksum });
|
|
5454
|
+
const targetComponent = {
|
|
5455
|
+
name: bom.metadata?.component?.name ?? "",
|
|
5456
|
+
type: TargetType.Application
|
|
5457
|
+
};
|
|
5458
|
+
if (bom.metadata?.component?.version) targetComponent.version = bom.metadata.component.version;
|
|
5459
|
+
const parsedBom = parseBom(input);
|
|
5460
|
+
const bomParts = {
|
|
5461
|
+
bomType: BOMType.Sbom,
|
|
5462
|
+
format: "cyclonedx",
|
|
5463
|
+
uniqueId: parsedBom.normalized.uniqueId,
|
|
5464
|
+
document: canonicalize$1(JSON.parse(input))
|
|
5465
|
+
};
|
|
5466
|
+
if (parsedBom.normalized.packages && parsedBom.normalized.packages.length > 0) bomParts.packages = parsedBom.normalized.packages;
|
|
5467
|
+
const componentBom = buildBom(bomParts);
|
|
5468
|
+
return buildHdfResults({
|
|
5469
|
+
generatorName: "cyclonedx-to-hdf",
|
|
5470
|
+
converterVersion,
|
|
5471
|
+
toolName: "CycloneDX",
|
|
5472
|
+
toolFormat: "JSON",
|
|
5473
|
+
baselines: [baseline],
|
|
5474
|
+
components: [{
|
|
5475
|
+
...targetComponent,
|
|
5476
|
+
boms: [componentBom]
|
|
5477
|
+
}],
|
|
5478
|
+
timestamp: scanTime
|
|
5479
|
+
});
|
|
5480
|
+
}
|
|
5481
|
+
//#endregion
|
|
5482
|
+
//#region converters/hdf-to-csv/typescript/converter.ts
|
|
5483
|
+
/**
|
|
5484
|
+
* Convert HDF JSON to CSV format
|
|
5485
|
+
* @param input HDF JSON string
|
|
5486
|
+
* @returns CSV string with sanitized output
|
|
5487
|
+
*/
|
|
5488
|
+
function convertHdfToCsv(input) {
|
|
5489
|
+
validateInputSize(input, "hdf-to-csv");
|
|
5490
|
+
const hdf = parseHdf(input);
|
|
5491
|
+
if (!hdf || typeof hdf !== "object" || !("baselines" in hdf)) throw new Error("Invalid HDF structure: missing baselines field");
|
|
5492
|
+
if (!Array.isArray(hdf.baselines)) throw new Error("Invalid HDF structure: baselines must be an array");
|
|
5493
|
+
const rows = [];
|
|
5494
|
+
const components = hdf.components || [];
|
|
5495
|
+
const targetList = components.length > 0 ? components.map((t) => ({
|
|
5496
|
+
name: t.name,
|
|
5497
|
+
type: t.type
|
|
5498
|
+
})) : [{
|
|
5499
|
+
name: "",
|
|
5500
|
+
type: ""
|
|
5501
|
+
}];
|
|
5502
|
+
for (const baseline of hdf.baselines) for (const requirement of baseline.requirements) for (const target of targetList) rows.push(createRow(baseline, requirement, target));
|
|
5503
|
+
return buildCsv(rows, { sanitize: true });
|
|
5504
|
+
}
|
|
5505
|
+
/**
|
|
5506
|
+
* Create a CSV row from baseline, requirement, and target data
|
|
5507
|
+
*/
|
|
5508
|
+
function createRow(baseline, requirement, target) {
|
|
5509
|
+
const description = requirement.descriptions.find((d) => d.label === "default")?.data || "";
|
|
5510
|
+
const check = descriptionByLabel(requirement, "check");
|
|
5511
|
+
const fix = descriptionByLabel(requirement, "fix");
|
|
5512
|
+
const rationale = descriptionByLabel(requirement, "rationale");
|
|
5513
|
+
const code = requirement.code ?? "";
|
|
5514
|
+
const references = flattenRefs(requirement.refs);
|
|
5515
|
+
const severity = getSeverity(requirement);
|
|
5516
|
+
const firstResult = requirement.results[0];
|
|
5517
|
+
const status = firstResult?.status || "";
|
|
5518
|
+
const message = firstResult?.message || "";
|
|
5519
|
+
const nistControls = extractArrayFromTags(requirement.tags, "nist");
|
|
5520
|
+
const cciControls = extractArrayFromTags(requirement.tags, "cci");
|
|
5521
|
+
return {
|
|
5522
|
+
"Baseline ID": baseline.name,
|
|
5523
|
+
"Baseline Version": baseline.version || "",
|
|
5524
|
+
"Baseline Title": baseline.title || "",
|
|
5525
|
+
"Target ID": target.name,
|
|
5526
|
+
"Target Type": target.type,
|
|
5527
|
+
"Requirement ID": requirement.id,
|
|
5528
|
+
"Requirement Title": requirement.title || "",
|
|
5529
|
+
"Description": description,
|
|
5530
|
+
"Check": check,
|
|
5531
|
+
"Fix": fix,
|
|
5532
|
+
"Rationale": rationale,
|
|
5533
|
+
"Code": code,
|
|
5534
|
+
"References": references,
|
|
5535
|
+
"Severity": severity,
|
|
5536
|
+
"Impact": requirement.impact,
|
|
5537
|
+
"Status": String(status),
|
|
5538
|
+
"NIST Controls": nistControls,
|
|
5539
|
+
"CCI Controls": cciControls,
|
|
5540
|
+
"Control Type": requirement.controlType ?? "",
|
|
5541
|
+
"Verification Method": requirement.verificationMethod ?? "",
|
|
5542
|
+
"Applicability": requirement.applicability ?? "",
|
|
5543
|
+
"Result Message": message
|
|
5544
|
+
};
|
|
5545
|
+
}
|
|
5546
|
+
/**
|
|
5547
|
+
* Get severity from requirement tags or derive from impact
|
|
5548
|
+
*/
|
|
5549
|
+
function getSeverity(requirement) {
|
|
5550
|
+
if (requirement.tags && typeof requirement.tags === "object") {
|
|
5551
|
+
if ("severity" in requirement.tags) {
|
|
5552
|
+
const sev = requirement.tags.severity;
|
|
5553
|
+
if (typeof sev === "string") return sev;
|
|
5554
|
+
if (Array.isArray(sev) && sev.length > 0) return String(sev[0]);
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
const impact = requirement.impact;
|
|
5558
|
+
if (impact >= .7) return "high";
|
|
5559
|
+
if (impact >= .4) return "medium";
|
|
5560
|
+
return "low";
|
|
5561
|
+
}
|
|
5562
|
+
/**
|
|
5563
|
+
* Extract array values from tags object and join with semicolons
|
|
5564
|
+
*/
|
|
5565
|
+
function extractArrayFromTags(tags, key) {
|
|
5566
|
+
if (!tags || typeof tags !== "object") return "";
|
|
5567
|
+
const value = tags[key];
|
|
5568
|
+
if (Array.isArray(value)) return value.map((v) => String(v)).join("; ");
|
|
5569
|
+
return "";
|
|
5570
|
+
}
|
|
5571
|
+
/**
|
|
5572
|
+
* Get the data of the first description matching a label. Empty when absent.
|
|
5573
|
+
*/
|
|
5574
|
+
function descriptionByLabel(requirement, label) {
|
|
5575
|
+
return requirement.descriptions.find((d) => d.label === label)?.data || "";
|
|
5576
|
+
}
|
|
5577
|
+
/**
|
|
5578
|
+
* Flatten a requirement's refs to one string: each Reference rendered as its
|
|
5579
|
+
* url/uri (or a string `ref`); array-form refs are skipped. Joined with '; ' to
|
|
5580
|
+
* match the NIST/CCI column convention.
|
|
5581
|
+
*/
|
|
5582
|
+
function flattenRefs(refs) {
|
|
5583
|
+
if (!Array.isArray(refs)) return "";
|
|
5584
|
+
const out = [];
|
|
5585
|
+
for (const r of refs) {
|
|
5586
|
+
if (!r || typeof r !== "object") continue;
|
|
5587
|
+
const o = r;
|
|
5588
|
+
if (typeof o.url === "string") out.push(o.url);
|
|
5589
|
+
else if (typeof o.uri === "string") out.push(o.uri);
|
|
5590
|
+
else if (typeof o.ref === "string") out.push(o.ref);
|
|
5591
|
+
}
|
|
5592
|
+
return out.join("; ");
|
|
5593
|
+
}
|
|
5594
|
+
//#endregion
|
|
5595
|
+
//#region converters/hdf-to-ecs/typescript/converter.ts
|
|
5596
|
+
/**
|
|
5597
|
+
* HDF Results -> Elastic Common Schema (ECS) NDJSON exporter.
|
|
5598
|
+
*
|
|
5599
|
+
* One ECS event per Evaluated_Requirement, in the hybrid shape from ADR-0002:
|
|
5600
|
+
* a core-ECS-native projection plus a lossless hdf.* block, hot filter scalars
|
|
5601
|
+
* promoted flat. Output is plain NDJSON (LF-delimited, trailing newline), ECS
|
|
5602
|
+
* 9.4.0.
|
|
5603
|
+
*
|
|
5604
|
+
* Status is raw-primary: event.outcome carries the RAW verdict (a waived failure
|
|
5605
|
+
* is still failure), and hdf.suppressed is the separate acceptance axis. The
|
|
5606
|
+
* canonical "still actionable" query is
|
|
5607
|
+
* event.outcome:"failure" AND hdf.suppressed:false.
|
|
5608
|
+
*
|
|
5609
|
+
* Generic JSON access, the status roll-up, requirement/document field
|
|
5610
|
+
* extraction, and the canonical line serialization are shared with the other
|
|
5611
|
+
* export converters via ../../../shared/typescript/exportmap.js; only
|
|
5612
|
+
* ECS-specific event shaping lives here. Output is held byte-identical with the
|
|
5613
|
+
* Go implementation via key-sorted, HTML-unescaped compact JSON.
|
|
5614
|
+
*/
|
|
5615
|
+
const ECS_VERSION = "9.4.0";
|
|
5616
|
+
function convertHdfToEcs(input, converterVersion = "0.1.0") {
|
|
5617
|
+
return runExport(input, "hdf-to-ecs", (req, baseline, docTimestamp, tool, generator, component) => buildEvent(req, baseline, docTimestamp, tool, generator, component, converterVersion));
|
|
5618
|
+
}
|
|
5619
|
+
function buildEvent(req, baseline, docTimestamp, tool, generator, component, converterVersion) {
|
|
5620
|
+
const st = statusOf(req);
|
|
5621
|
+
const outcome = statusToOutcome(st.raw);
|
|
5622
|
+
const controlID = getStr(req, "id");
|
|
5623
|
+
const baselineName = getStr(baseline, "name");
|
|
5624
|
+
const title = getStr(req, "title");
|
|
5625
|
+
const cvssList = asArr(req.cvss);
|
|
5626
|
+
const hasCVSS = cvssList !== void 0 && cvssList.length > 0;
|
|
5229
5627
|
const categories = ["configuration"];
|
|
5230
5628
|
if (hasCVSS) categories.push("vulnerability");
|
|
5231
5629
|
const event = {
|
|
@@ -5383,7 +5781,7 @@ function buildHECEvent(req, baseline, docTimestamp, tool, generator, component,
|
|
|
5383
5781
|
const controlID = getStr(req, "id");
|
|
5384
5782
|
const signature = title !== "" ? title : controlID;
|
|
5385
5783
|
const dest = destHost(component);
|
|
5386
|
-
const sev = severity(req);
|
|
5784
|
+
const sev = severity$1(req);
|
|
5387
5785
|
const [cvss, hasCVSS] = maxCVSS(req);
|
|
5388
5786
|
const cve = firstCVE(asArr(req.cvss) ?? []);
|
|
5389
5787
|
const category = firstCWE(req);
|
|
@@ -5426,7 +5824,7 @@ function destHost(component) {
|
|
|
5426
5824
|
if (!component) return "";
|
|
5427
5825
|
return getStr(component, "fqdn") || getStr(component, "name") || getStr(component, "ipAddress");
|
|
5428
5826
|
}
|
|
5429
|
-
function severity(req) {
|
|
5827
|
+
function severity$1(req) {
|
|
5430
5828
|
if (typeof req.impact === "number") return impactToSeverity(req.impact);
|
|
5431
5829
|
return normalizeSeverity(getStr(req, "severity"));
|
|
5432
5830
|
}
|
|
@@ -5487,9 +5885,9 @@ const ACTIVITY_CREATE = 1;
|
|
|
5487
5885
|
const STATUS_NEW = 1;
|
|
5488
5886
|
const STATUS_SUPPRESSED = 3;
|
|
5489
5887
|
function convertHdfToOcsf(input, converterVersion = "0.1.0") {
|
|
5490
|
-
return runExport(input, "hdf-to-ocsf", (req, baseline, docTimestamp, tool, generator, component) => buildFinding(req, baseline, docTimestamp, tool, generator, component, converterVersion));
|
|
5888
|
+
return runExport(input, "hdf-to-ocsf", (req, baseline, docTimestamp, tool, generator, component) => buildFinding$1(req, baseline, docTimestamp, tool, generator, component, converterVersion));
|
|
5491
5889
|
}
|
|
5492
|
-
function buildFinding(req, baseline, docTimestamp, tool, generator, component, converterVersion) {
|
|
5890
|
+
function buildFinding$1(req, baseline, docTimestamp, tool, generator, component, converterVersion) {
|
|
5493
5891
|
const st = statusOf(req);
|
|
5494
5892
|
const cvssList = asArr(req.cvss);
|
|
5495
5893
|
const hasCVSS = cvssList !== void 0 && cvssList.length > 0;
|
|
@@ -5697,6 +6095,174 @@ function buildVulnerabilities(cvssList, req) {
|
|
|
5697
6095
|
return [vuln];
|
|
5698
6096
|
}
|
|
5699
6097
|
//#endregion
|
|
6098
|
+
//#region converters/hdf-to-asff/typescript/converter.ts
|
|
6099
|
+
/**
|
|
6100
|
+
* HDF Results -> AWS Security Finding Format (ASFF), the reverse of
|
|
6101
|
+
* asff-to-hdf. Output is the standard {"Findings": [...]} envelope that
|
|
6102
|
+
* BatchImportFindings accepts and asff-to-hdf reads back.
|
|
6103
|
+
*
|
|
6104
|
+
* One finding per requirement (matching hdf-to-ocsf): a requirement's results
|
|
6105
|
+
* roll up to one Compliance.Status via the shared statusOf. The mapping is
|
|
6106
|
+
* deliberately LOSSY and standard-compliant — HDF structure ASFF cannot hold is
|
|
6107
|
+
* dropped, NOT crammed into Types[] (heimdall2's rejected Types-string
|
|
6108
|
+
* encoding). Provenance rides ProductFields (ASFF's official string map).
|
|
6109
|
+
*
|
|
6110
|
+
* ProductArn / AwsAccountId / Region are ASFF-required but absent from HDF: the
|
|
6111
|
+
* account is recovered from a cloudAccount component when present, otherwise a
|
|
6112
|
+
* placeholder is emitted; the push path overrides all three. Output is held
|
|
6113
|
+
* byte-identical with the Go implementation via canonicalize + stringifyLine.
|
|
6114
|
+
*/
|
|
6115
|
+
const ASFF_SCHEMA_VERSION = "2018-10-08";
|
|
6116
|
+
const PLACEHOLDER_REGION = "us-east-1";
|
|
6117
|
+
const PLACEHOLDER_ACCOUNT_ID = "000000000000";
|
|
6118
|
+
const DEFAULT_PARTITION = "aws";
|
|
6119
|
+
const MAX_TITLE = 256;
|
|
6120
|
+
const MAX_DESCRIPTION = 1024;
|
|
6121
|
+
const EPOCH_SENTINEL = "1970-01-01T00:00:00Z";
|
|
6122
|
+
function convertHdfToAsff(input, converterVersion = "0.1.0") {
|
|
6123
|
+
const name = "hdf-to-asff";
|
|
6124
|
+
validateInputSize(input, name);
|
|
6125
|
+
const doc = parseHdf(input);
|
|
6126
|
+
const baselines = asArr(doc.baselines);
|
|
6127
|
+
if (!baselines) throw new Error(`${name}: invalid HDF structure: missing baselines field`);
|
|
6128
|
+
const docTimestamp = getStr(doc, "timestamp");
|
|
6129
|
+
const component = firstComponent(doc);
|
|
6130
|
+
const accountID = recoverAccountID(doc);
|
|
6131
|
+
const productArn = `arn:aws:securityhub:${PLACEHOLDER_REGION}:${accountID}:product/${accountID}/default`;
|
|
6132
|
+
const findings = [];
|
|
6133
|
+
for (const bRaw of baselines) {
|
|
6134
|
+
const baseline = asMap(bRaw);
|
|
6135
|
+
if (!baseline) continue;
|
|
6136
|
+
const baselineName = getStr(baseline, "name");
|
|
6137
|
+
const reqs = asArr(baseline.requirements) ?? [];
|
|
6138
|
+
for (const rRaw of reqs) {
|
|
6139
|
+
const req = asMap(rRaw);
|
|
6140
|
+
if (!req) continue;
|
|
6141
|
+
findings.push(buildFinding(req, baselineName, docTimestamp, component, accountID, productArn, converterVersion));
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
return stringifyLine(canonicalize$1({ Findings: findings })) + "\n";
|
|
6145
|
+
}
|
|
6146
|
+
function recoverAccountID(doc) {
|
|
6147
|
+
for (const cRaw of asArr(doc.components) ?? []) {
|
|
6148
|
+
const c = asMap(cRaw);
|
|
6149
|
+
if (!c || getStr(c, "type") !== "cloudAccount") continue;
|
|
6150
|
+
const id = getStr(c, "accountId") || getStr(c, "name");
|
|
6151
|
+
if (id) return id;
|
|
6152
|
+
}
|
|
6153
|
+
return PLACEHOLDER_ACCOUNT_ID;
|
|
6154
|
+
}
|
|
6155
|
+
function buildFinding(req, baselineName, docTimestamp, component, accountID, productArn, converterVersion) {
|
|
6156
|
+
const controlID = getStr(req, "id");
|
|
6157
|
+
const st = statusOf(req);
|
|
6158
|
+
const title = getStr(req, "title") || controlID;
|
|
6159
|
+
const desc = defaultDescription(req) || title;
|
|
6160
|
+
const cvssList = asArr(req.cvss);
|
|
6161
|
+
const hasCVSS = cvssList !== void 0 && cvssList.length > 0;
|
|
6162
|
+
const ts = canonicalTime(firstResultStartTime(req, docTimestamp));
|
|
6163
|
+
const id = findingID(accountID, baselineName, controlID);
|
|
6164
|
+
const finding = {
|
|
6165
|
+
SchemaVersion: ASFF_SCHEMA_VERSION,
|
|
6166
|
+
Id: id,
|
|
6167
|
+
ProductArn: productArn,
|
|
6168
|
+
GeneratorId: controlID,
|
|
6169
|
+
AwsAccountId: accountID,
|
|
6170
|
+
CreatedAt: ts,
|
|
6171
|
+
UpdatedAt: ts,
|
|
6172
|
+
Title: truncate(title, MAX_TITLE),
|
|
6173
|
+
Description: truncate(desc, MAX_DESCRIPTION),
|
|
6174
|
+
Types: asffTypes(hasCVSS),
|
|
6175
|
+
Severity: severity(req),
|
|
6176
|
+
Resources: resources(component, id),
|
|
6177
|
+
RecordState: "ACTIVE",
|
|
6178
|
+
Compliance: { Status: complianceStatus(st.rollup) }
|
|
6179
|
+
};
|
|
6180
|
+
if (st.suppressed) finding.Workflow = { Status: "SUPPRESSED" };
|
|
6181
|
+
const pf = productFields(baselineName, controlID, converterVersion);
|
|
6182
|
+
if (Object.keys(pf).length > 0) finding.ProductFields = pf;
|
|
6183
|
+
const rem = remediation(req);
|
|
6184
|
+
if (rem) finding.Remediation = rem;
|
|
6185
|
+
return finding;
|
|
6186
|
+
}
|
|
6187
|
+
function findingID(accountID, baselineName, controlID) {
|
|
6188
|
+
return [
|
|
6189
|
+
accountID,
|
|
6190
|
+
PLACEHOLDER_REGION,
|
|
6191
|
+
baselineName,
|
|
6192
|
+
controlID
|
|
6193
|
+
].join("/");
|
|
6194
|
+
}
|
|
6195
|
+
function asffTypes(hasCVSS) {
|
|
6196
|
+
return hasCVSS ? ["Software and Configuration Checks/Vulnerabilities/CVE"] : ["Software and Configuration Checks"];
|
|
6197
|
+
}
|
|
6198
|
+
function complianceStatus(status) {
|
|
6199
|
+
switch (status) {
|
|
6200
|
+
case "passed": return "PASSED";
|
|
6201
|
+
case "failed": return "FAILED";
|
|
6202
|
+
case "notApplicable": return "NOT_AVAILABLE";
|
|
6203
|
+
default: return "WARNING";
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
function severity(req) {
|
|
6207
|
+
let label = "INFORMATIONAL";
|
|
6208
|
+
let normalized = 0;
|
|
6209
|
+
if (typeof req.impact === "number") {
|
|
6210
|
+
label = impactToSeverity(req.impact).toUpperCase();
|
|
6211
|
+
normalized = Math.trunc(req.impact * 100);
|
|
6212
|
+
}
|
|
6213
|
+
return {
|
|
6214
|
+
Label: label,
|
|
6215
|
+
Normalized: normalized
|
|
6216
|
+
};
|
|
6217
|
+
}
|
|
6218
|
+
function resources(component, id) {
|
|
6219
|
+
const res = {
|
|
6220
|
+
Type: "Other",
|
|
6221
|
+
Id: id,
|
|
6222
|
+
Partition: DEFAULT_PARTITION,
|
|
6223
|
+
Region: PLACEHOLDER_REGION
|
|
6224
|
+
};
|
|
6225
|
+
if (component) {
|
|
6226
|
+
const details = {};
|
|
6227
|
+
setIf(details, "Name", getStr(component, "name"));
|
|
6228
|
+
setIf(details, "Type", getStr(component, "type"));
|
|
6229
|
+
setIf(details, "IpAddress", getStr(component, "ipAddress"));
|
|
6230
|
+
setIf(details, "OsName", getStr(component, "osName"));
|
|
6231
|
+
if (Object.keys(details).length > 0) res.Details = { Other: details };
|
|
6232
|
+
}
|
|
6233
|
+
return [res];
|
|
6234
|
+
}
|
|
6235
|
+
function productFields(baselineName, controlID, converterVersion) {
|
|
6236
|
+
const pf = {};
|
|
6237
|
+
setIf(pf, "hdf/baseline", baselineName);
|
|
6238
|
+
setIf(pf, "hdf/control_id", controlID);
|
|
6239
|
+
setIf(pf, "hdf/exporter_version", converterVersion);
|
|
6240
|
+
return pf;
|
|
6241
|
+
}
|
|
6242
|
+
function remediation(req) {
|
|
6243
|
+
let fix = "";
|
|
6244
|
+
for (const dRaw of asArr(req.descriptions) ?? []) {
|
|
6245
|
+
const d = asMap(dRaw);
|
|
6246
|
+
if (d && getStr(d, "label") === "fix") {
|
|
6247
|
+
fix = getStr(d, "data");
|
|
6248
|
+
break;
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
const url = firstRefURL(req);
|
|
6252
|
+
if (fix === "" && url === "") return void 0;
|
|
6253
|
+
const rec = {};
|
|
6254
|
+
setIf(rec, "Text", truncate(fix, MAX_DESCRIPTION));
|
|
6255
|
+
setIf(rec, "Url", url);
|
|
6256
|
+
return { Recommendation: rec };
|
|
6257
|
+
}
|
|
6258
|
+
function canonicalTime(s) {
|
|
6259
|
+
return parseTimestamp(s) ? s : EPOCH_SENTINEL;
|
|
6260
|
+
}
|
|
6261
|
+
function truncate(s, max) {
|
|
6262
|
+
const r = [...s];
|
|
6263
|
+
return r.length <= max ? s : r.slice(0, max).join("");
|
|
6264
|
+
}
|
|
6265
|
+
//#endregion
|
|
5700
6266
|
//#region converters/splunk-to-hdf/typescript/converter.ts
|
|
5701
6267
|
/**
|
|
5702
6268
|
* Map a Splunk result status string to the HDF ResultStatus enum.
|
|
@@ -6282,9 +6848,8 @@ function buildRequirement$12(reqID, findings) {
|
|
|
6282
6848
|
* @returns HDF JSON string
|
|
6283
6849
|
*/
|
|
6284
6850
|
async function convertTrufflehogToHdf(input, converterVersion = "1.0.0") {
|
|
6285
|
-
if (!input || input.trim().length === 0) throw new Error("trufflehog: empty input");
|
|
6286
6851
|
validateInputSize(input, "trufflehog");
|
|
6287
|
-
const findings = parseFindings(input);
|
|
6852
|
+
const findings = !input || input.trim().length === 0 ? [] : parseFindings(input);
|
|
6288
6853
|
const resultsChecksum = await inputChecksum(input);
|
|
6289
6854
|
const limitedFindings = limitArrayWithWarning(findings, "finding");
|
|
6290
6855
|
const groups = groupFindings(limitedFindings);
|
|
@@ -6562,11 +7127,10 @@ function buildRequirement$10(checkID, findings, hasStatus) {
|
|
|
6562
7127
|
data: formatDesc$1(rep)
|
|
6563
7128
|
}];
|
|
6564
7129
|
const results = findings.map((f) => {
|
|
6565
|
-
return {
|
|
6566
|
-
status: hasStatus ? getStatus$2(f["Result Status"] ?? "") : ResultStatus.Failed,
|
|
7130
|
+
return createResult(hasStatus ? getStatus$2(f["Result Status"] ?? "") : ResultStatus.Failed, void 0, {
|
|
6567
7131
|
codeDesc: f["Details"] ?? "",
|
|
6568
7132
|
startTime: parseDate(f["Date"] ?? "")
|
|
6569
|
-
};
|
|
7133
|
+
});
|
|
6570
7134
|
});
|
|
6571
7135
|
const req = createRequirement(checkID, rep["Check"] ?? "", descriptions, getImpact$6(rep["Risk DV"] ?? ""), results, { tags });
|
|
6572
7136
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
@@ -6832,11 +7396,10 @@ function buildRequirement$9(vuln, packageTypes, distro) {
|
|
|
6832
7396
|
data: vuln.description
|
|
6833
7397
|
}];
|
|
6834
7398
|
const startTime = (vuln.discoveredDate ? parseTimestamp(vuln.discoveredDate) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
6835
|
-
const results = [{
|
|
6836
|
-
status: ResultStatus.Failed,
|
|
7399
|
+
const results = [createResult(ResultStatus.Failed, void 0, {
|
|
6837
7400
|
codeDesc: formatCodeDesc$2(vuln),
|
|
6838
7401
|
startTime
|
|
6839
|
-
}];
|
|
7402
|
+
})];
|
|
6840
7403
|
const req = createRequirement(vuln.id, vuln.id, descriptions, twistlockSeverityToImpact(vuln.severity), results, { tags });
|
|
6841
7404
|
const controlType = deriveControlTypeFromTags(nist);
|
|
6842
7405
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -6963,11 +7526,10 @@ function buildRequirement$8(finding, timestamp) {
|
|
|
6963
7526
|
data: finding.vulnerability.recommendation
|
|
6964
7527
|
});
|
|
6965
7528
|
const codeDesc = finding.vulnerability.recommendation ?? "No recommendation available";
|
|
6966
|
-
const results = [{
|
|
6967
|
-
status: ResultStatus.Failed,
|
|
7529
|
+
const results = [createResult(ResultStatus.Failed, void 0, {
|
|
6968
7530
|
codeDesc,
|
|
6969
7531
|
startTime: (timestamp ? parseTimestamp(timestamp) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
6970
|
-
}];
|
|
7532
|
+
})];
|
|
6971
7533
|
const req = createRequirement(finding.matrix, getTitle$1(finding), descriptions, getImpact$5(finding.vulnerability.severity), results, { tags });
|
|
6972
7534
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
6973
7535
|
const controlType = deriveControlTypeFromTags(nist);
|
|
@@ -8098,8 +8660,7 @@ function buildRequirementFromResult(result, filename) {
|
|
|
8098
8660
|
const startTime = (startTimeStr ? parseTimestamp(startTimeStr) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
8099
8661
|
const score = result.result.score;
|
|
8100
8662
|
const status = determineStatus(score);
|
|
8101
|
-
const toResult = (codeDesc) => ({
|
|
8102
|
-
status,
|
|
8663
|
+
const toResult = (codeDesc) => createResult(status, void 0, {
|
|
8103
8664
|
codeDesc,
|
|
8104
8665
|
startTime
|
|
8105
8666
|
});
|
|
@@ -8344,8 +8905,7 @@ function buildCWERequirement(cat, impact, firstBuildDate) {
|
|
|
8344
8905
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
8345
8906
|
const results = cwes.flatMap((c) => {
|
|
8346
8907
|
const staticflaws = c.staticflaws;
|
|
8347
|
-
return ensureArray(staticflaws?.flaw).map((flaw) => ({
|
|
8348
|
-
status: ResultStatus.Failed,
|
|
8908
|
+
return ensureArray(staticflaws?.flaw).map((flaw) => createResult(ResultStatus.Failed, void 0, {
|
|
8349
8909
|
codeDesc: formatFlawCodeDesc(flaw),
|
|
8350
8910
|
startTime
|
|
8351
8911
|
}));
|
|
@@ -8407,8 +8967,7 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
|
|
|
8407
8967
|
if (cweID) extras.cwe = cweID;
|
|
8408
8968
|
const tags = buildNistCciTags(nist, cciTags, extras);
|
|
8409
8969
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
8410
|
-
const results = components.map((comp) => ({
|
|
8411
|
-
status: ResultStatus.Failed,
|
|
8970
|
+
const results = components.map((comp) => createResult(ResultStatus.Failed, void 0, {
|
|
8412
8971
|
codeDesc: formatSCACodeDesc(comp),
|
|
8413
8972
|
startTime
|
|
8414
8973
|
}));
|
|
@@ -9720,23 +10279,31 @@ function overrideToPOAMItem(override) {
|
|
|
9720
10279
|
const remediations = [];
|
|
9721
10280
|
if (override.milestones) for (const ms of override.milestones) {
|
|
9722
10281
|
const msProps = [];
|
|
9723
|
-
if (ms.estimatedCompletion) {
|
|
9724
|
-
const d = toDate(ms.estimatedCompletion);
|
|
9725
|
-
if (d) msProps.push({
|
|
9726
|
-
name: "estimated-completion",
|
|
9727
|
-
value: formatTimestampSeconds(d)
|
|
9728
|
-
});
|
|
9729
|
-
}
|
|
9730
10282
|
if (ms.status) msProps.push({
|
|
9731
10283
|
name: "milestone-status",
|
|
9732
10284
|
value: String(ms.status)
|
|
9733
10285
|
});
|
|
10286
|
+
let tasks;
|
|
10287
|
+
const d = ms.estimatedCompletion ? toDate(ms.estimatedCompletion) : void 0;
|
|
10288
|
+
if (d) {
|
|
10289
|
+
const eta = formatTimestampSeconds(d);
|
|
10290
|
+
tasks = [{
|
|
10291
|
+
uuid: crypto.randomUUID(),
|
|
10292
|
+
type: "milestone",
|
|
10293
|
+
title: ms.description,
|
|
10294
|
+
timing: { "within-date-range": {
|
|
10295
|
+
start: eta,
|
|
10296
|
+
end: eta
|
|
10297
|
+
} }
|
|
10298
|
+
}];
|
|
10299
|
+
}
|
|
9734
10300
|
remediations.push({
|
|
9735
10301
|
uuid: crypto.randomUUID(),
|
|
9736
10302
|
lifecycle: "planned",
|
|
9737
10303
|
title: ms.description,
|
|
9738
10304
|
description: ms.description,
|
|
9739
|
-
props: msProps.length > 0 ? msProps : void 0
|
|
10305
|
+
props: msProps.length > 0 ? msProps : void 0,
|
|
10306
|
+
tasks
|
|
9740
10307
|
});
|
|
9741
10308
|
}
|
|
9742
10309
|
let riskLog;
|
|
@@ -9748,12 +10315,14 @@ function overrideToPOAMItem(override) {
|
|
|
9748
10315
|
start: formatTimestampSeconds(expiresDate),
|
|
9749
10316
|
"status-change": riskStatus
|
|
9750
10317
|
}] };
|
|
10318
|
+
const deadline = expiresDate && expiresDate.getTime() > 0 ? formatTimestampSeconds(expiresDate) : void 0;
|
|
9751
10319
|
const risk = {
|
|
9752
10320
|
uuid: riskUUID,
|
|
9753
10321
|
title: override.requirementId,
|
|
9754
10322
|
description: override.reason,
|
|
9755
10323
|
statement: override.reason,
|
|
9756
10324
|
status: riskStatus,
|
|
10325
|
+
deadline,
|
|
9757
10326
|
props: riskProps,
|
|
9758
10327
|
remediations: remediations.length > 0 ? remediations : void 0,
|
|
9759
10328
|
"risk-log": riskLog
|
|
@@ -10152,7 +10721,7 @@ function supplierEvidence(sourceURI, description) {
|
|
|
10152
10721
|
* arrives; one year is a defensive default consistent with the
|
|
10153
10722
|
* no-permanent-amendment rule on Standalone_Override. */
|
|
10154
10723
|
const DEFAULT_EXPIRY_HORIZON_MS$2 = 365 * 24 * 60 * 60 * 1e3;
|
|
10155
|
-
async function convertOpenVexToHdf(input, converterVersion) {
|
|
10724
|
+
async function convertOpenVexToHdf(input, converterVersion = "1.0.0") {
|
|
10156
10725
|
validateInputSize(input, "openvex-to-hdf");
|
|
10157
10726
|
const doc = parseJSON(input);
|
|
10158
10727
|
const docTime = (doc.timestamp ? parseTimestamp(doc.timestamp) : null) ?? /* @__PURE__ */ new Date();
|
|
@@ -10244,7 +10813,7 @@ function truncateId(id) {
|
|
|
10244
10813
|
* Spec: https://docs.oasis-open.org/csaf/csaf/v2.0/csaf-v2.0.html
|
|
10245
10814
|
*/
|
|
10246
10815
|
const DEFAULT_EXPIRY_HORIZON_MS$1 = 365 * 24 * 60 * 60 * 1e3;
|
|
10247
|
-
async function convertCsafVexToHdf(input, converterVersion) {
|
|
10816
|
+
async function convertCsafVexToHdf(input, converterVersion = "1.0.0") {
|
|
10248
10817
|
validateInputSize(input, "csaf-vex-to-hdf");
|
|
10249
10818
|
const doc = parseJSON(input);
|
|
10250
10819
|
if (doc.document?.category !== "csaf_vex") throw new Error(`csaf-vex-to-hdf: document.category is ${JSON.stringify(doc.document?.category)}; only 'csaf_vex' is supported`);
|
|
@@ -10457,7 +11026,7 @@ function overlap(ids, scope) {
|
|
|
10457
11026
|
* Spec: https://cyclonedx.org/use-cases/#vulnerability-exploitability
|
|
10458
11027
|
*/
|
|
10459
11028
|
const DEFAULT_EXPIRY_HORIZON_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
10460
|
-
async function convertCyclonedxVexToHdf(input, converterVersion) {
|
|
11029
|
+
async function convertCyclonedxVexToHdf(input, converterVersion = "1.0.0") {
|
|
10461
11030
|
validateInputSize(input, "cyclonedx-vex-to-hdf");
|
|
10462
11031
|
const bom = parseJSON(input);
|
|
10463
11032
|
if (bom.bomFormat !== "CycloneDX") throw new Error(`cyclonedx-vex-to-hdf: bomFormat is ${JSON.stringify(bom.bomFormat)}; only 'CycloneDX' is supported`);
|
|
@@ -11217,245 +11786,91 @@ async function catalogToBaseline(catalog, rawInput) {
|
|
|
11217
11786
|
for (const ctrl of group.controls ?? []) {
|
|
11218
11787
|
const req = controlToBaselineRequirement(ctrl);
|
|
11219
11788
|
requirements.push(req);
|
|
11220
|
-
reqIDs.push(req.id);
|
|
11221
|
-
for (const enh of ctrl.controls ?? []) {
|
|
11222
|
-
const enhReq = controlToBaselineRequirement(enh);
|
|
11223
|
-
requirements.push(enhReq);
|
|
11224
|
-
reqIDs.push(enhReq.id);
|
|
11225
|
-
}
|
|
11226
|
-
}
|
|
11227
|
-
if (reqIDs.length > 0) groups.push({
|
|
11228
|
-
id: group.id ?? "",
|
|
11229
|
-
title: group.title,
|
|
11230
|
-
requirements: reqIDs
|
|
11231
|
-
});
|
|
11232
|
-
}
|
|
11233
|
-
for (const ctrl of catalog.controls ?? []) {
|
|
11234
|
-
const req = controlToBaselineRequirement(ctrl);
|
|
11235
|
-
requirements.push(req);
|
|
11236
|
-
for (const enh of ctrl.controls ?? []) {
|
|
11237
|
-
const enhReq = controlToBaselineRequirement(enh);
|
|
11238
|
-
requirements.push(enhReq);
|
|
11239
|
-
}
|
|
11240
|
-
}
|
|
11241
|
-
return {
|
|
11242
|
-
name: catalogBaselineName(catalog),
|
|
11243
|
-
title: meta.title,
|
|
11244
|
-
version: meta.version,
|
|
11245
|
-
status: "loaded",
|
|
11246
|
-
integrity,
|
|
11247
|
-
requirements,
|
|
11248
|
-
groups,
|
|
11249
|
-
generator: {
|
|
11250
|
-
name: "oscal-catalog-to-hdf",
|
|
11251
|
-
version: "1.0.0"
|
|
11252
|
-
}
|
|
11253
|
-
};
|
|
11254
|
-
}
|
|
11255
|
-
/** Converts a single OSCAL Control to an HDF BaselineRequirement. */
|
|
11256
|
-
function controlToBaselineRequirement(ctrl) {
|
|
11257
|
-
const nistTag = controlIdToNistTag(ctrl.id);
|
|
11258
|
-
const descriptions = buildCatalogDescriptions(ctrl);
|
|
11259
|
-
const tags = buildCatalogTags(ctrl);
|
|
11260
|
-
const req = {
|
|
11261
|
-
id: nistTag,
|
|
11262
|
-
title: ctrl.title,
|
|
11263
|
-
impact: .5,
|
|
11264
|
-
descriptions,
|
|
11265
|
-
tags
|
|
11266
|
-
};
|
|
11267
|
-
const controlType = deriveControlTypeFromTags([nistTag]);
|
|
11268
|
-
if (controlType !== void 0) req.controlType = controlType;
|
|
11269
|
-
if (extractPropValue(ctrl.props, "CORE") === "true") req.applicability = Applicability.Required;
|
|
11270
|
-
return req;
|
|
11271
|
-
}
|
|
11272
|
-
/** Creates HDF Description entries from control parts. */
|
|
11273
|
-
function buildCatalogDescriptions(ctrl) {
|
|
11274
|
-
const descriptions = [];
|
|
11275
|
-
const statement = flattenPartsByName(ctrl.parts, "statement");
|
|
11276
|
-
descriptions.push({
|
|
11277
|
-
label: "default",
|
|
11278
|
-
data: statement || ""
|
|
11279
|
-
});
|
|
11280
|
-
const guidance = flattenPartsByName(ctrl.parts, "guidance");
|
|
11281
|
-
if (guidance) descriptions.push({
|
|
11282
|
-
label: "rationale",
|
|
11283
|
-
data: guidance
|
|
11284
|
-
});
|
|
11285
|
-
const check = flattenPartsByName(ctrl.parts, "assessment-objective");
|
|
11286
|
-
if (check) descriptions.push({
|
|
11287
|
-
label: "check",
|
|
11288
|
-
data: check
|
|
11289
|
-
});
|
|
11290
|
-
return descriptions;
|
|
11291
|
-
}
|
|
11292
|
-
/** Builds the tags map for an OSCAL catalog control. */
|
|
11293
|
-
function buildCatalogTags(ctrl) {
|
|
11294
|
-
const tags = {};
|
|
11295
|
-
tags["nist"] = [controlIdToNistTag(ctrl.id)];
|
|
11296
|
-
const label = extractPropValue(ctrl.props, "label");
|
|
11297
|
-
if (label !== void 0) tags["label"] = label;
|
|
11298
|
-
const sortId = extractPropValue(ctrl.props, "sort-id");
|
|
11299
|
-
if (sortId !== void 0) tags["sort-id"] = sortId;
|
|
11300
|
-
return tags;
|
|
11301
|
-
}
|
|
11302
|
-
/** Derives a baseline name from catalog metadata. */
|
|
11303
|
-
function catalogBaselineName(catalog) {
|
|
11304
|
-
return toKebabCase(catalog.metadata.title, "oscal-catalog");
|
|
11305
|
-
}
|
|
11306
|
-
//#endregion
|
|
11307
|
-
//#region converters/oscal-to-hdf/typescript/converter-profile.ts
|
|
11308
|
-
/**
|
|
11309
|
-
* OSCAL Profile to HDF Baseline converter.
|
|
11310
|
-
*
|
|
11311
|
-
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_profile.go.
|
|
11312
|
-
*
|
|
11313
|
-
* This implements the "simple resolver" -- it handles:
|
|
11314
|
-
* - A single catalog import
|
|
11315
|
-
* - include-controls with with-ids filtering
|
|
11316
|
-
* - set-parameters from modify section
|
|
11317
|
-
* - merge as-is (preserve catalog structure)
|
|
11318
|
-
*
|
|
11319
|
-
* It does NOT handle:
|
|
11320
|
-
* - Multiple imports
|
|
11321
|
-
* - Nested profile imports
|
|
11322
|
-
* - alter directives
|
|
11323
|
-
* - Complex merge strategies
|
|
11324
|
-
*/
|
|
11325
|
-
/**
|
|
11326
|
-
* Converts an OSCAL Profile against a provided catalog to HDF Baseline JSON.
|
|
11327
|
-
*
|
|
11328
|
-
* @param profileInput - Raw JSON string containing an OSCAL profile
|
|
11329
|
-
* @param catalogInput - Raw JSON string containing an OSCAL catalog
|
|
11330
|
-
* @returns HDF Baseline JSON string
|
|
11331
|
-
*/
|
|
11332
|
-
async function convertOscalProfileToHdf(profileInput, catalogInput) {
|
|
11333
|
-
validateInputSize(profileInput, "oscal-profile");
|
|
11334
|
-
if (!profileInput || profileInput.trim().length === 0) throw new Error("empty profile input");
|
|
11335
|
-
if (!catalogInput || catalogInput.trim().length === 0) throw new Error("empty catalog input");
|
|
11336
|
-
const profileDoc = parseJSON(profileInput);
|
|
11337
|
-
if (!profileDoc.profile) throw new Error("oscal-profile: input is not a profile document (root key is not 'profile')");
|
|
11338
|
-
const profile = profileDoc.profile;
|
|
11339
|
-
const catalogDoc = parseJSON(catalogInput);
|
|
11340
|
-
if (!catalogDoc.catalog) throw new Error("oscal-profile: catalog input is not a catalog document (root key is not 'catalog')");
|
|
11341
|
-
const catalog = catalogDoc.catalog;
|
|
11342
|
-
if (!profile.imports || profile.imports.length === 0) throw new Error("oscal-profile: profile has no imports");
|
|
11343
|
-
if (profile.imports.length > 1) throw new Error(`oscal-profile: profile has ${profile.imports.length} imports -- this converter only supports single-catalog imports. Use NIST's oscal-cli to resolve complex profiles, or use a pre-resolved catalog`);
|
|
11344
|
-
if (profile.modify?.alters && profile.modify.alters.length > 0) throw new Error(`oscal-profile: profile contains ${profile.modify.alters.length} alter directives -- this converter only supports parameter overrides. Use NIST's oscal-cli to resolve profiles with alter directives, or use a pre-resolved catalog`);
|
|
11345
|
-
const imp = profile.imports[0];
|
|
11346
|
-
const resolvedCatalog = filterCatalog(catalog, collectIncludedIDs(imp), collectExcludedIDs(imp));
|
|
11347
|
-
if (profile.modify?.["set-parameters"]) applyParameterOverrides(resolvedCatalog, profile.modify["set-parameters"]);
|
|
11348
|
-
resolvedCatalog.metadata = profile.metadata;
|
|
11349
|
-
resolvedCatalog.uuid = profile.uuid;
|
|
11350
|
-
const baseline = await catalogToBaseline(resolvedCatalog, profileInput);
|
|
11351
|
-
baseline.integrity = await inputIntegrity(profileInput);
|
|
11352
|
-
return JSON.stringify(baseline, null, 2);
|
|
11353
|
-
}
|
|
11354
|
-
/** Extracts all control IDs from an import's include-controls. Returns null if include all. */
|
|
11355
|
-
function collectIncludedIDs(imp) {
|
|
11356
|
-
const includeControls = imp["include-controls"];
|
|
11357
|
-
if (!includeControls || includeControls.length === 0) return null;
|
|
11358
|
-
const ids = /* @__PURE__ */ new Set();
|
|
11359
|
-
for (const ic of includeControls) for (const id of ic["with-ids"] ?? []) ids.add(id);
|
|
11360
|
-
return ids;
|
|
11361
|
-
}
|
|
11362
|
-
/** Extracts all control IDs from an import's exclude-controls. */
|
|
11363
|
-
function collectExcludedIDs(imp) {
|
|
11364
|
-
const excludeControls = imp["exclude-controls"];
|
|
11365
|
-
if (!excludeControls || excludeControls.length === 0) return null;
|
|
11366
|
-
const ids = /* @__PURE__ */ new Set();
|
|
11367
|
-
for (const ec of excludeControls) for (const id of ec["with-ids"] ?? []) ids.add(id);
|
|
11368
|
-
return ids;
|
|
11369
|
-
}
|
|
11370
|
-
/** Creates a new catalog containing only the controls matching filters. */
|
|
11371
|
-
function filterCatalog(catalog, includedIDs, excludedIDs) {
|
|
11372
|
-
const includeAll = includedIDs === null;
|
|
11373
|
-
const result = {
|
|
11374
|
-
uuid: catalog.uuid,
|
|
11375
|
-
metadata: catalog.metadata,
|
|
11376
|
-
"back-matter": catalog["back-matter"],
|
|
11377
|
-
groups: [],
|
|
11378
|
-
controls: []
|
|
11379
|
-
};
|
|
11380
|
-
for (const group of catalog.groups ?? []) {
|
|
11381
|
-
const filtered = filterGroupControls(group, includedIDs, excludedIDs, includeAll);
|
|
11382
|
-
if (filtered.controls && filtered.controls.length > 0) result.groups.push(filtered);
|
|
11789
|
+
reqIDs.push(req.id);
|
|
11790
|
+
for (const enh of ctrl.controls ?? []) {
|
|
11791
|
+
const enhReq = controlToBaselineRequirement(enh);
|
|
11792
|
+
requirements.push(enhReq);
|
|
11793
|
+
reqIDs.push(enhReq.id);
|
|
11794
|
+
}
|
|
11795
|
+
}
|
|
11796
|
+
if (reqIDs.length > 0) groups.push({
|
|
11797
|
+
id: group.id ?? "",
|
|
11798
|
+
title: group.title,
|
|
11799
|
+
requirements: reqIDs
|
|
11800
|
+
});
|
|
11383
11801
|
}
|
|
11384
|
-
for (const ctrl of catalog.controls ?? [])
|
|
11385
|
-
const
|
|
11386
|
-
|
|
11802
|
+
for (const ctrl of catalog.controls ?? []) {
|
|
11803
|
+
const req = controlToBaselineRequirement(ctrl);
|
|
11804
|
+
requirements.push(req);
|
|
11805
|
+
for (const enh of ctrl.controls ?? []) {
|
|
11806
|
+
const enhReq = controlToBaselineRequirement(enh);
|
|
11807
|
+
requirements.push(enhReq);
|
|
11808
|
+
}
|
|
11387
11809
|
}
|
|
11388
|
-
return
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11810
|
+
return {
|
|
11811
|
+
name: catalogBaselineName(catalog),
|
|
11812
|
+
title: meta.title,
|
|
11813
|
+
version: meta.version,
|
|
11814
|
+
status: "loaded",
|
|
11815
|
+
integrity,
|
|
11816
|
+
requirements,
|
|
11817
|
+
groups,
|
|
11818
|
+
generator: {
|
|
11819
|
+
name: "oscal-catalog-to-hdf",
|
|
11820
|
+
version: "1.0.0"
|
|
11821
|
+
}
|
|
11398
11822
|
};
|
|
11399
|
-
for (const ctrl of group.controls ?? []) if (shouldIncludeControl(ctrl.id, includedIDs, excludedIDs, includeAll)) {
|
|
11400
|
-
const filteredCtrl = filterControlEnhancements(ctrl, includedIDs, excludedIDs, includeAll);
|
|
11401
|
-
filtered.controls.push(filteredCtrl);
|
|
11402
|
-
}
|
|
11403
|
-
return filtered;
|
|
11404
11823
|
}
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11824
|
+
/** Converts a single OSCAL Control to an HDF BaselineRequirement. */
|
|
11825
|
+
function controlToBaselineRequirement(ctrl) {
|
|
11826
|
+
const nistTag = controlIdToNistTag(ctrl.id);
|
|
11827
|
+
const descriptions = buildCatalogDescriptions(ctrl);
|
|
11828
|
+
const tags = buildCatalogTags(ctrl);
|
|
11829
|
+
const req = {
|
|
11830
|
+
id: nistTag,
|
|
11409
11831
|
title: ctrl.title,
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
parts: ctrl.parts,
|
|
11414
|
-
controls: []
|
|
11832
|
+
impact: .5,
|
|
11833
|
+
descriptions,
|
|
11834
|
+
tags
|
|
11415
11835
|
};
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
if (excludedIDs !== null && excludedIDs.has(id)) return false;
|
|
11421
|
-
if (includeAll) return true;
|
|
11422
|
-
return includedIDs !== null && includedIDs.has(id);
|
|
11423
|
-
}
|
|
11424
|
-
/** Applies set-parameters from the profile's modify section to the resolved catalog. */
|
|
11425
|
-
function applyParameterOverrides(catalog, setParams) {
|
|
11426
|
-
if (setParams.length === 0) return;
|
|
11427
|
-
const overrides = /* @__PURE__ */ new Map();
|
|
11428
|
-
for (const sp of setParams) if (sp["param-id"]) overrides.set(sp["param-id"], sp.values ?? []);
|
|
11429
|
-
for (const group of catalog.groups ?? []) for (const ctrl of group.controls ?? []) {
|
|
11430
|
-
applyParamOverridesToControl(ctrl, overrides);
|
|
11431
|
-
for (const enh of ctrl.controls ?? []) applyParamOverridesToControl(enh, overrides);
|
|
11432
|
-
}
|
|
11433
|
-
for (const ctrl of catalog.controls ?? []) {
|
|
11434
|
-
applyParamOverridesToControl(ctrl, overrides);
|
|
11435
|
-
for (const enh of ctrl.controls ?? []) applyParamOverridesToControl(enh, overrides);
|
|
11436
|
-
}
|
|
11836
|
+
const controlType = deriveControlTypeFromTags([nistTag]);
|
|
11837
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
11838
|
+
if (extractPropValue(ctrl.props, "CORE") === "true") req.applicability = Applicability.Required;
|
|
11839
|
+
return req;
|
|
11437
11840
|
}
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11841
|
+
/** Creates HDF Description entries from control parts. */
|
|
11842
|
+
function buildCatalogDescriptions(ctrl) {
|
|
11843
|
+
const descriptions = [];
|
|
11844
|
+
const statement = flattenPartsByName(ctrl.parts, "statement");
|
|
11845
|
+
descriptions.push({
|
|
11846
|
+
label: "default",
|
|
11847
|
+
data: statement || ""
|
|
11848
|
+
});
|
|
11849
|
+
const guidance = flattenPartsByName(ctrl.parts, "guidance");
|
|
11850
|
+
if (guidance) descriptions.push({
|
|
11851
|
+
label: "rationale",
|
|
11852
|
+
data: guidance
|
|
11853
|
+
});
|
|
11854
|
+
const check = flattenPartsByName(ctrl.parts, "assessment-objective");
|
|
11855
|
+
if (check) descriptions.push({
|
|
11856
|
+
label: "check",
|
|
11857
|
+
data: check
|
|
11858
|
+
});
|
|
11859
|
+
return descriptions;
|
|
11445
11860
|
}
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11861
|
+
/** Builds the tags map for an OSCAL catalog control. */
|
|
11862
|
+
function buildCatalogTags(ctrl) {
|
|
11863
|
+
const tags = {};
|
|
11864
|
+
tags["nist"] = [controlIdToNistTag(ctrl.id)];
|
|
11865
|
+
const label = extractPropValue(ctrl.props, "label");
|
|
11866
|
+
if (label !== void 0) tags["label"] = label;
|
|
11867
|
+
const sortId = extractPropValue(ctrl.props, "sort-id");
|
|
11868
|
+
if (sortId !== void 0) tags["sort-id"] = sortId;
|
|
11869
|
+
return tags;
|
|
11449
11870
|
}
|
|
11450
|
-
/**
|
|
11451
|
-
function
|
|
11452
|
-
|
|
11453
|
-
for (const [paramId, values] of overrides) {
|
|
11454
|
-
const placeholder = `{{ insert: param, ${paramId} }}`;
|
|
11455
|
-
const replacement = values.join(", ");
|
|
11456
|
-
result = result.split(placeholder).join(replacement);
|
|
11457
|
-
}
|
|
11458
|
-
return result;
|
|
11871
|
+
/** Derives a baseline name from catalog metadata. */
|
|
11872
|
+
function catalogBaselineName(catalog) {
|
|
11873
|
+
return toKebabCase(catalog.metadata.title, "oscal-catalog");
|
|
11459
11874
|
}
|
|
11460
11875
|
//#endregion
|
|
11461
11876
|
//#region converters/oscal-to-hdf/typescript/converter-component.ts
|
|
@@ -11527,164 +11942,160 @@ function componentBaselineName(comp, compDef) {
|
|
|
11527
11942
|
return toKebabCase(comp.title || compDef.metadata.title, "oscal-component-definition");
|
|
11528
11943
|
}
|
|
11529
11944
|
//#endregion
|
|
11530
|
-
//#region converters/oscal-to-hdf/typescript/converter-
|
|
11945
|
+
//#region converters/oscal-to-hdf/typescript/converter-poam.ts
|
|
11531
11946
|
/**
|
|
11532
|
-
* OSCAL
|
|
11947
|
+
* OSCAL Plan of Action and Milestones (POA&M) to HDF Amendments converter.
|
|
11533
11948
|
*
|
|
11534
|
-
* Mirrors the Go implementation in converters/oscal-to-hdf/go/
|
|
11949
|
+
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_poam.go.
|
|
11535
11950
|
*/
|
|
11536
11951
|
/**
|
|
11537
|
-
* Converts an OSCAL
|
|
11952
|
+
* Converts an OSCAL POA&M document to HDF Amendments JSON.
|
|
11538
11953
|
*
|
|
11539
|
-
* @param input - Raw JSON string containing an OSCAL
|
|
11540
|
-
* @returns HDF
|
|
11954
|
+
* @param input - Raw JSON string containing an OSCAL POA&M
|
|
11955
|
+
* @returns HDF Amendments JSON string
|
|
11541
11956
|
*/
|
|
11542
|
-
async function
|
|
11543
|
-
validateInputSize(input, "oscal-
|
|
11957
|
+
async function convertOscalPoamToHdf(input) {
|
|
11958
|
+
validateInputSize(input, "oscal-poam");
|
|
11544
11959
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
11545
11960
|
const doc = parseJSON(input);
|
|
11546
|
-
if (!doc["
|
|
11547
|
-
const
|
|
11961
|
+
if (!doc["plan-of-action-and-milestones"]) throw new Error("oscal-poam: input is not a plan-of-action-and-milestones document (root key is not 'plan-of-action-and-milestones')");
|
|
11962
|
+
const poam = doc["plan-of-action-and-milestones"];
|
|
11548
11963
|
const integrity = await inputIntegrity(input);
|
|
11549
|
-
const
|
|
11550
|
-
|
|
11964
|
+
const meta = extractMetadata(poam.metadata);
|
|
11965
|
+
const riskMap = buildRiskMap$1(poam.risks ?? []);
|
|
11966
|
+
const overrides = [];
|
|
11967
|
+
for (const item of poam["poam-items"]) overrides.push(poamItemToOverride(item, riskMap, poam));
|
|
11968
|
+
const systemRef = poam["import-ssp"]?.href || void 0;
|
|
11969
|
+
const appliedBy = extractAppliedBy(poam.metadata);
|
|
11970
|
+
return serializeHdf({
|
|
11971
|
+
name: toKebabCase(poam.metadata.title, "oscal-poam"),
|
|
11972
|
+
overrides,
|
|
11551
11973
|
integrity,
|
|
11552
|
-
|
|
11974
|
+
systemRef,
|
|
11975
|
+
version: meta.version,
|
|
11976
|
+
appliedBy,
|
|
11553
11977
|
generator: {
|
|
11554
|
-
name: "oscal-
|
|
11978
|
+
name: "oscal-poam-to-hdf",
|
|
11555
11979
|
version: "1.0.0"
|
|
11556
11980
|
}
|
|
11557
|
-
};
|
|
11558
|
-
const sc = ssp["system-characteristics"];
|
|
11559
|
-
if (sc) {
|
|
11560
|
-
if (sc.description) {
|
|
11561
|
-
let desc = sc.description;
|
|
11562
|
-
if (sc["authorization-boundary"]?.description) desc += "\n\nAuthorization Boundary: " + sc["authorization-boundary"].description;
|
|
11563
|
-
system.description = desc;
|
|
11564
|
-
}
|
|
11565
|
-
const sil = sc["security-impact-level"];
|
|
11566
|
-
if (sil) system.categorizationLevel = sspCategorizationLevel(sil) ?? void 0;
|
|
11567
|
-
else if (sc["security-sensitivity-level"]) system.categorizationLevel = mapSensitivityToCategorizationLevel(sc["security-sensitivity-level"]) ?? void 0;
|
|
11568
|
-
if (sc.status) system.authorizationStatus = sspAuthorizationStatus(sc.status.state) ?? void 0;
|
|
11569
|
-
if (sc["authorization-boundary"]?.description) system.boundaryDescription = sc["authorization-boundary"].description;
|
|
11570
|
-
const systemIds = sc["system-ids"];
|
|
11571
|
-
if (systemIds && systemIds.length > 0) {
|
|
11572
|
-
system.identifier = systemIds[0].id;
|
|
11573
|
-
if (systemIds[0]["identifier-type"]) system.identifierScheme = systemIds[0]["identifier-type"];
|
|
11574
|
-
}
|
|
11575
|
-
}
|
|
11576
|
-
const meta = extractMetadata(ssp.metadata);
|
|
11577
|
-
if (meta.version) system.version = meta.version;
|
|
11578
|
-
const componentControls = buildComponentControlMap(ssp["control-implementation"]);
|
|
11579
|
-
const si = ssp["system-implementation"];
|
|
11580
|
-
if (si?.components) for (const comp of si.components) system.components.push(sspComponentToHDFComponent(comp, componentControls));
|
|
11581
|
-
return JSON.stringify(system, null, 2);
|
|
11981
|
+
});
|
|
11582
11982
|
}
|
|
11583
|
-
function
|
|
11584
|
-
const
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
return "oscal-ssp";
|
|
11983
|
+
function buildRiskMap$1(risks) {
|
|
11984
|
+
const m = /* @__PURE__ */ new Map();
|
|
11985
|
+
for (const risk of risks) m.set(risk.uuid, risk);
|
|
11986
|
+
return m;
|
|
11588
11987
|
}
|
|
11589
|
-
function
|
|
11590
|
-
const
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
switch (highest) {
|
|
11602
|
-
case "high": return CategorizationLevel.High;
|
|
11603
|
-
case "moderate": return CategorizationLevel.Moderate;
|
|
11604
|
-
case "low": return CategorizationLevel.Low;
|
|
11605
|
-
default: return null;
|
|
11606
|
-
}
|
|
11988
|
+
function poamItemToOverride(item, riskMap, poam) {
|
|
11989
|
+
const requirementId = extractRequirementIdFromPOAMItem(item, riskMap);
|
|
11990
|
+
return {
|
|
11991
|
+
type: OverrideType.Poam,
|
|
11992
|
+
requirementId,
|
|
11993
|
+
reason: poamItemReason(item),
|
|
11994
|
+
status: poamItemStatus(item, riskMap),
|
|
11995
|
+
appliedBy: poamItemAppliedBy(poam),
|
|
11996
|
+
appliedAt: poamItemAppliedAt(poam, requirementId),
|
|
11997
|
+
expiresAt: poamItemExpiresAt(item, riskMap, requirementId),
|
|
11998
|
+
milestones: extractMilestones(item, riskMap)
|
|
11999
|
+
};
|
|
11607
12000
|
}
|
|
11608
|
-
function
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
12001
|
+
function extractRequirementIdFromPOAMItem(item, riskMap) {
|
|
12002
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
12003
|
+
const riskUuid = rr["risk-uuid"];
|
|
12004
|
+
if (riskUuid) {
|
|
12005
|
+
const risk = riskMap.get(riskUuid);
|
|
12006
|
+
if (risk) {
|
|
12007
|
+
const controlId = extractPropValue(risk.props, "impacted-control-id");
|
|
12008
|
+
if (controlId) return controlIdToNistTag(controlId);
|
|
12009
|
+
}
|
|
12010
|
+
}
|
|
11617
12011
|
}
|
|
12012
|
+
const poamId = extractPropValue(item.props, "POAM-ID");
|
|
12013
|
+
if (poamId) return poamId;
|
|
12014
|
+
if (item.title) return item.title;
|
|
12015
|
+
return "unknown";
|
|
11618
12016
|
}
|
|
11619
|
-
function
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
case "low": return 1;
|
|
11624
|
-
default: return 0;
|
|
11625
|
-
}
|
|
12017
|
+
function poamItemReason(item) {
|
|
12018
|
+
if (item.description) return item.description;
|
|
12019
|
+
if (item.title) return item.title;
|
|
12020
|
+
return "POA&M item";
|
|
11626
12021
|
}
|
|
11627
|
-
function
|
|
11628
|
-
const
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
12022
|
+
function poamItemStatus(item, riskMap) {
|
|
12023
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
12024
|
+
const riskUuid = rr["risk-uuid"];
|
|
12025
|
+
if (riskUuid) {
|
|
12026
|
+
const risk = riskMap.get(riskUuid);
|
|
12027
|
+
if (risk) {
|
|
12028
|
+
const status = oscalStatusToHdf(risk.status);
|
|
12029
|
+
if (status === "passed") return "passed";
|
|
12030
|
+
if (status === "failed") return "failed";
|
|
12031
|
+
}
|
|
12032
|
+
}
|
|
11635
12033
|
}
|
|
12034
|
+
return "failed";
|
|
11636
12035
|
}
|
|
11637
|
-
function
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
12036
|
+
function poamItemAppliedBy(poam) {
|
|
12037
|
+
const rps = poam.metadata["responsible-parties"];
|
|
12038
|
+
if (rps) {
|
|
12039
|
+
for (const rp of rps) if (rp["role-id"] === "prepared-by" && rp["party-uuids"].length > 0) return {
|
|
12040
|
+
type: "simple",
|
|
12041
|
+
identifier: rp["party-uuids"][0]
|
|
12042
|
+
};
|
|
12043
|
+
if (rps.length > 0 && rps[0]["party-uuids"].length > 0) return {
|
|
12044
|
+
type: "simple",
|
|
12045
|
+
identifier: rps[0]["party-uuids"][0]
|
|
12046
|
+
};
|
|
11644
12047
|
}
|
|
12048
|
+
return {
|
|
12049
|
+
type: "system",
|
|
12050
|
+
identifier: "oscal-poam-converter"
|
|
12051
|
+
};
|
|
11645
12052
|
}
|
|
11646
|
-
function
|
|
11647
|
-
const
|
|
11648
|
-
if (
|
|
11649
|
-
|
|
11650
|
-
const controlId = ir["control-id"];
|
|
11651
|
-
for (const bc of ir["by-components"] ?? []) addComponentControl(result, bc["component-uuid"], controlId);
|
|
11652
|
-
for (const stmt of ir.statements ?? []) for (const bc of stmt["by-components"] ?? []) addComponentControl(result, bc["component-uuid"], controlId);
|
|
11653
|
-
}
|
|
11654
|
-
return result;
|
|
12053
|
+
function poamItemAppliedAt(poam, requirementId) {
|
|
12054
|
+
const t = poam.metadata["last-modified"] && parseTimestamp(String(poam.metadata["last-modified"]));
|
|
12055
|
+
if (t) return t;
|
|
12056
|
+
throw new Error(`poam-item "${requirementId}": no usable metadata.last-modified for appliedAt`);
|
|
11655
12057
|
}
|
|
11656
|
-
function
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
12058
|
+
function poamItemExpiresAt(item, riskMap, requirementId) {
|
|
12059
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
12060
|
+
const riskUuid = rr["risk-uuid"];
|
|
12061
|
+
if (!riskUuid) continue;
|
|
12062
|
+
const risk = riskMap.get(riskUuid);
|
|
12063
|
+
if (!risk?.deadline) continue;
|
|
12064
|
+
const t = parseTimestamp(String(risk.deadline));
|
|
12065
|
+
if (t) return t;
|
|
11661
12066
|
}
|
|
11662
|
-
|
|
12067
|
+
throw new Error(`poam-item "${requirementId}": no related risk carries a usable deadline; a POA&M requires a time commitment`);
|
|
11663
12068
|
}
|
|
11664
|
-
function
|
|
11665
|
-
const
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
12069
|
+
function extractMilestones(item, riskMap) {
|
|
12070
|
+
const milestones = [];
|
|
12071
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
12072
|
+
const riskUuid = rr["risk-uuid"];
|
|
12073
|
+
if (!riskUuid) continue;
|
|
12074
|
+
const risk = riskMap.get(riskUuid);
|
|
12075
|
+
if (!risk) continue;
|
|
12076
|
+
for (const rem of risk.remediations ?? []) {
|
|
12077
|
+
if (rem.lifecycle !== "planned") continue;
|
|
12078
|
+
for (const task of rem.tasks ?? []) {
|
|
12079
|
+
const end = task.timing?.["within-date-range"]?.end;
|
|
12080
|
+
const estimatedCompletion = end && parseTimestamp(String(end));
|
|
12081
|
+
if (!estimatedCompletion) continue;
|
|
12082
|
+
milestones.push({
|
|
12083
|
+
description: task.description ? `${task.title}: ${task.description}` : task.title,
|
|
12084
|
+
estimatedCompletion,
|
|
12085
|
+
status: "pending"
|
|
12086
|
+
});
|
|
12087
|
+
}
|
|
12088
|
+
}
|
|
11675
12089
|
}
|
|
11676
|
-
return
|
|
12090
|
+
return milestones;
|
|
11677
12091
|
}
|
|
11678
|
-
function
|
|
11679
|
-
|
|
11680
|
-
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
case "database": return TargetType.Database;
|
|
11686
|
-
case "storage": return TargetType.Artifact;
|
|
11687
|
-
default: return TargetType.Application;
|
|
12092
|
+
function extractAppliedBy(meta) {
|
|
12093
|
+
const rps = meta["responsible-parties"];
|
|
12094
|
+
if (rps) {
|
|
12095
|
+
for (const rp of rps) if (rp["role-id"] === "prepared-by" && rp["party-uuids"].length > 0) return {
|
|
12096
|
+
type: "simple",
|
|
12097
|
+
identifier: rp["party-uuids"][0]
|
|
12098
|
+
};
|
|
11688
12099
|
}
|
|
11689
12100
|
}
|
|
11690
12101
|
//#endregion
|
|
@@ -11790,181 +12201,32 @@ function buildTargetSelector(ap) {
|
|
|
11790
12201
|
const selector = {};
|
|
11791
12202
|
for (const subject of subjects) {
|
|
11792
12203
|
if (subject.type) {
|
|
11793
|
-
const key = "subject-type";
|
|
11794
|
-
if (selector[key] !== void 0) selector[key] += "," + subject.type;
|
|
11795
|
-
else selector[key] = subject.type;
|
|
11796
|
-
}
|
|
11797
|
-
if (subject["include-all"] !== void 0) selector["include-" + subject.type] = "all";
|
|
11798
|
-
}
|
|
11799
|
-
if (Object.keys(selector).length === 0) return null;
|
|
11800
|
-
return selector;
|
|
11801
|
-
}
|
|
11802
|
-
function determinePlanType(ap) {
|
|
11803
|
-
const aType = extractPropValue(ap.metadata.props, "assessment-type");
|
|
11804
|
-
if (aType) switch (aType.toLowerCase()) {
|
|
11805
|
-
case "automated": return PlanType.Automated;
|
|
11806
|
-
case "manual": return PlanType.Manual;
|
|
11807
|
-
}
|
|
11808
|
-
if (ap.tasks && ap.tasks.length > 0) return PlanType.Hybrid;
|
|
11809
|
-
}
|
|
11810
|
-
function buildPlanDescription(ap) {
|
|
11811
|
-
const parts = [];
|
|
11812
|
-
if (ap.metadata.remarks) parts.push(ap.metadata.remarks);
|
|
11813
|
-
if (ap["terms-and-conditions"]?.parts && ap["terms-and-conditions"].parts.length > 0) {
|
|
11814
|
-
const terms = flattenParts(ap["terms-and-conditions"].parts);
|
|
11815
|
-
if (terms) parts.push("Terms and Conditions: " + terms);
|
|
11816
|
-
}
|
|
11817
|
-
if (parts.length === 0) return void 0;
|
|
11818
|
-
return parts.join("\n\n");
|
|
11819
|
-
}
|
|
11820
|
-
//#endregion
|
|
11821
|
-
//#region converters/oscal-to-hdf/typescript/converter-poam.ts
|
|
11822
|
-
/**
|
|
11823
|
-
* OSCAL Plan of Action and Milestones (POA&M) to HDF Amendments converter.
|
|
11824
|
-
*
|
|
11825
|
-
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_poam.go.
|
|
11826
|
-
*/
|
|
11827
|
-
/**
|
|
11828
|
-
* Converts an OSCAL POA&M document to HDF Amendments JSON.
|
|
11829
|
-
*
|
|
11830
|
-
* @param input - Raw JSON string containing an OSCAL POA&M
|
|
11831
|
-
* @returns HDF Amendments JSON string
|
|
11832
|
-
*/
|
|
11833
|
-
async function convertOscalPoamToHdf(input) {
|
|
11834
|
-
validateInputSize(input, "oscal-poam");
|
|
11835
|
-
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
11836
|
-
const doc = parseJSON(input);
|
|
11837
|
-
if (!doc["plan-of-action-and-milestones"]) throw new Error("oscal-poam: input is not a plan-of-action-and-milestones document (root key is not 'plan-of-action-and-milestones')");
|
|
11838
|
-
const poam = doc["plan-of-action-and-milestones"];
|
|
11839
|
-
const integrity = await inputIntegrity(input);
|
|
11840
|
-
const meta = extractMetadata(poam.metadata);
|
|
11841
|
-
const riskMap = buildRiskMap$1(poam.risks ?? []);
|
|
11842
|
-
const overrides = [];
|
|
11843
|
-
for (const item of poam["poam-items"]) overrides.push(poamItemToOverride(item, riskMap, poam));
|
|
11844
|
-
const systemRef = poam["import-ssp"]?.href || void 0;
|
|
11845
|
-
const appliedBy = extractAppliedBy(poam.metadata);
|
|
11846
|
-
return serializeHdf({
|
|
11847
|
-
name: toKebabCase(poam.metadata.title, "oscal-poam"),
|
|
11848
|
-
overrides,
|
|
11849
|
-
integrity,
|
|
11850
|
-
systemRef,
|
|
11851
|
-
version: meta.version,
|
|
11852
|
-
appliedBy,
|
|
11853
|
-
generator: {
|
|
11854
|
-
name: "oscal-poam-to-hdf",
|
|
11855
|
-
version: "1.0.0"
|
|
11856
|
-
}
|
|
11857
|
-
});
|
|
11858
|
-
}
|
|
11859
|
-
function buildRiskMap$1(risks) {
|
|
11860
|
-
const m = /* @__PURE__ */ new Map();
|
|
11861
|
-
for (const risk of risks) m.set(risk.uuid, risk);
|
|
11862
|
-
return m;
|
|
11863
|
-
}
|
|
11864
|
-
function poamItemToOverride(item, riskMap, poam) {
|
|
11865
|
-
return {
|
|
11866
|
-
type: OverrideType.Poam,
|
|
11867
|
-
requirementId: extractRequirementIdFromPOAMItem(item, riskMap),
|
|
11868
|
-
reason: poamItemReason(item),
|
|
11869
|
-
status: poamItemStatus(item, riskMap),
|
|
11870
|
-
appliedBy: poamItemAppliedBy(poam),
|
|
11871
|
-
appliedAt: poamItemAppliedAt(poam),
|
|
11872
|
-
expiresAt: poamItemExpiresAt(),
|
|
11873
|
-
milestones: extractMilestones(item, riskMap)
|
|
11874
|
-
};
|
|
11875
|
-
}
|
|
11876
|
-
function extractRequirementIdFromPOAMItem(item, riskMap) {
|
|
11877
|
-
for (const rr of item["related-risks"] ?? []) {
|
|
11878
|
-
const riskUuid = rr["risk-uuid"];
|
|
11879
|
-
if (riskUuid) {
|
|
11880
|
-
const risk = riskMap.get(riskUuid);
|
|
11881
|
-
if (risk) {
|
|
11882
|
-
const controlId = extractPropValue(risk.props, "impacted-control-id");
|
|
11883
|
-
if (controlId) return controlIdToNistTag(controlId);
|
|
11884
|
-
}
|
|
11885
|
-
}
|
|
11886
|
-
}
|
|
11887
|
-
const poamId = extractPropValue(item.props, "POAM-ID");
|
|
11888
|
-
if (poamId) return poamId;
|
|
11889
|
-
if (item.title) return item.title;
|
|
11890
|
-
return "unknown";
|
|
11891
|
-
}
|
|
11892
|
-
function poamItemReason(item) {
|
|
11893
|
-
if (item.description) return item.description;
|
|
11894
|
-
if (item.title) return item.title;
|
|
11895
|
-
return "POA&M item";
|
|
11896
|
-
}
|
|
11897
|
-
function poamItemStatus(item, riskMap) {
|
|
11898
|
-
for (const rr of item["related-risks"] ?? []) {
|
|
11899
|
-
const riskUuid = rr["risk-uuid"];
|
|
11900
|
-
if (riskUuid) {
|
|
11901
|
-
const risk = riskMap.get(riskUuid);
|
|
11902
|
-
if (risk) {
|
|
11903
|
-
const status = oscalStatusToHdf(risk.status);
|
|
11904
|
-
if (status === "passed") return "passed";
|
|
11905
|
-
if (status === "failed") return "failed";
|
|
11906
|
-
}
|
|
11907
|
-
}
|
|
11908
|
-
}
|
|
11909
|
-
return "failed";
|
|
11910
|
-
}
|
|
11911
|
-
function poamItemAppliedBy(poam) {
|
|
11912
|
-
const rps = poam.metadata["responsible-parties"];
|
|
11913
|
-
if (rps) {
|
|
11914
|
-
for (const rp of rps) if (rp["role-id"] === "prepared-by" && rp["party-uuids"].length > 0) return {
|
|
11915
|
-
type: "simple",
|
|
11916
|
-
identifier: rp["party-uuids"][0]
|
|
11917
|
-
};
|
|
11918
|
-
if (rps.length > 0 && rps[0]["party-uuids"].length > 0) return {
|
|
11919
|
-
type: "simple",
|
|
11920
|
-
identifier: rps[0]["party-uuids"][0]
|
|
11921
|
-
};
|
|
11922
|
-
}
|
|
11923
|
-
return {
|
|
11924
|
-
type: "system",
|
|
11925
|
-
identifier: "oscal-poam-converter"
|
|
11926
|
-
};
|
|
11927
|
-
}
|
|
11928
|
-
function poamItemAppliedAt(poam) {
|
|
11929
|
-
if (poam.metadata["last-modified"]) {
|
|
11930
|
-
const t = parseTimestamp(String(poam.metadata["last-modified"]));
|
|
11931
|
-
if (t) return t;
|
|
12204
|
+
const key = "subject-type";
|
|
12205
|
+
if (selector[key] !== void 0) selector[key] += "," + subject.type;
|
|
12206
|
+
else selector[key] = subject.type;
|
|
12207
|
+
}
|
|
12208
|
+
if (subject["include-all"] !== void 0) selector["include-" + subject.type] = "all";
|
|
11932
12209
|
}
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
function poamItemExpiresAt() {
|
|
11936
|
-
const d = /* @__PURE__ */ new Date();
|
|
11937
|
-
d.setFullYear(d.getFullYear() + 1);
|
|
11938
|
-
return d;
|
|
12210
|
+
if (Object.keys(selector).length === 0) return null;
|
|
12211
|
+
return selector;
|
|
11939
12212
|
}
|
|
11940
|
-
function
|
|
11941
|
-
const
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
const risk = riskMap.get(riskUuid);
|
|
11946
|
-
if (!risk) continue;
|
|
11947
|
-
for (const rem of risk.remediations ?? []) {
|
|
11948
|
-
if (rem.lifecycle !== "planned") continue;
|
|
11949
|
-
const estimatedCompletion = /* @__PURE__ */ new Date();
|
|
11950
|
-
estimatedCompletion.setMonth(estimatedCompletion.getMonth() + 3);
|
|
11951
|
-
milestones.push({
|
|
11952
|
-
description: rem.title + ": " + rem.description,
|
|
11953
|
-
estimatedCompletion,
|
|
11954
|
-
status: "pending"
|
|
11955
|
-
});
|
|
11956
|
-
}
|
|
12213
|
+
function determinePlanType(ap) {
|
|
12214
|
+
const aType = extractPropValue(ap.metadata.props, "assessment-type");
|
|
12215
|
+
if (aType) switch (aType.toLowerCase()) {
|
|
12216
|
+
case "automated": return PlanType.Automated;
|
|
12217
|
+
case "manual": return PlanType.Manual;
|
|
11957
12218
|
}
|
|
11958
|
-
return
|
|
12219
|
+
if (ap.tasks && ap.tasks.length > 0) return PlanType.Hybrid;
|
|
11959
12220
|
}
|
|
11960
|
-
function
|
|
11961
|
-
const
|
|
11962
|
-
if (
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
};
|
|
12221
|
+
function buildPlanDescription(ap) {
|
|
12222
|
+
const parts = [];
|
|
12223
|
+
if (ap.metadata.remarks) parts.push(ap.metadata.remarks);
|
|
12224
|
+
if (ap["terms-and-conditions"]?.parts && ap["terms-and-conditions"].parts.length > 0) {
|
|
12225
|
+
const terms = flattenParts(ap["terms-and-conditions"].parts);
|
|
12226
|
+
if (terms) parts.push("Terms and Conditions: " + terms);
|
|
11967
12227
|
}
|
|
12228
|
+
if (parts.length === 0) return void 0;
|
|
12229
|
+
return parts.join("\n\n");
|
|
11968
12230
|
}
|
|
11969
12231
|
//#endregion
|
|
11970
12232
|
//#region converters/oscal-to-hdf/typescript/converter-sar.ts
|
|
@@ -12075,99 +12337,417 @@ function extractControlIdFromFinding(f) {
|
|
|
12075
12337
|
if (idx > 0) controlId = controlId.slice(0, idx);
|
|
12076
12338
|
return controlId;
|
|
12077
12339
|
}
|
|
12078
|
-
function mapFindingStatus(f) {
|
|
12079
|
-
const status = oscalStatusToHdf(f.target.status.state);
|
|
12080
|
-
if (status === "passed") return ResultStatus.Passed;
|
|
12081
|
-
if (status === "failed") return ResultStatus.Failed;
|
|
12082
|
-
return ResultStatus.NotReviewed;
|
|
12340
|
+
function mapFindingStatus(f) {
|
|
12341
|
+
const status = oscalStatusToHdf(f.target.status.state);
|
|
12342
|
+
if (status === "passed") return ResultStatus.Passed;
|
|
12343
|
+
if (status === "failed") return ResultStatus.Failed;
|
|
12344
|
+
return ResultStatus.NotReviewed;
|
|
12345
|
+
}
|
|
12346
|
+
function buildCodeDesc(f, obsMap) {
|
|
12347
|
+
const parts = [];
|
|
12348
|
+
for (const ref of f["related-observations"] ?? []) {
|
|
12349
|
+
const obsUuid = ref["observation-uuid"];
|
|
12350
|
+
if (!obsUuid) continue;
|
|
12351
|
+
const obs = obsMap.get(obsUuid);
|
|
12352
|
+
if (!obs) continue;
|
|
12353
|
+
if (obs.methods && obs.methods.length > 0) parts.push("Methods: " + obs.methods.join(", "));
|
|
12354
|
+
for (const subj of obs.subjects ?? []) {
|
|
12355
|
+
let subjDesc = subj.type;
|
|
12356
|
+
if (subj.title) subjDesc = subj.title + " (" + subj.type + ")";
|
|
12357
|
+
parts.push("Subject: " + subjDesc);
|
|
12358
|
+
}
|
|
12359
|
+
}
|
|
12360
|
+
if (parts.length === 0) return f.title;
|
|
12361
|
+
return parts.join("; ");
|
|
12362
|
+
}
|
|
12363
|
+
function buildRiskMessage(f, riskMap) {
|
|
12364
|
+
const messages = [];
|
|
12365
|
+
for (const ref of f["related-risks"] ?? []) {
|
|
12366
|
+
const riskUuid = ref["risk-uuid"];
|
|
12367
|
+
if (!riskUuid) continue;
|
|
12368
|
+
const risk = riskMap.get(riskUuid);
|
|
12369
|
+
if (!risk) continue;
|
|
12370
|
+
let msg = risk.title;
|
|
12371
|
+
if (risk.description) msg += ": " + risk.description;
|
|
12372
|
+
messages.push(msg);
|
|
12373
|
+
}
|
|
12374
|
+
return messages.join("\n");
|
|
12375
|
+
}
|
|
12376
|
+
function sarFindingsImpact(findings, riskMap) {
|
|
12377
|
+
let highestImpact = -1;
|
|
12378
|
+
for (const f of findings) for (const ref of f["related-risks"] ?? []) {
|
|
12379
|
+
const riskUuid = ref["risk-uuid"];
|
|
12380
|
+
if (!riskUuid) continue;
|
|
12381
|
+
const risk = riskMap.get(riskUuid);
|
|
12382
|
+
if (!risk) continue;
|
|
12383
|
+
const impact = extractRiskSeverity(risk.characterizations, -1);
|
|
12384
|
+
if (impact > highestImpact) highestImpact = impact;
|
|
12385
|
+
}
|
|
12386
|
+
if (highestImpact < 0) return .5;
|
|
12387
|
+
return highestImpact;
|
|
12388
|
+
}
|
|
12389
|
+
function sarBuildDescriptions(findings, obsMap) {
|
|
12390
|
+
const descriptions = [];
|
|
12391
|
+
const findingDescs = [];
|
|
12392
|
+
for (const f of findings) if (f.description) findingDescs.push(f.description);
|
|
12393
|
+
descriptions.push({
|
|
12394
|
+
label: "default",
|
|
12395
|
+
data: findingDescs.join("\n") || ""
|
|
12396
|
+
});
|
|
12397
|
+
const obsDescs = [];
|
|
12398
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12399
|
+
for (const f of findings) for (const ref of f["related-observations"] ?? []) {
|
|
12400
|
+
const obsUuid = ref["observation-uuid"];
|
|
12401
|
+
if (!obsUuid || seen.has(obsUuid)) continue;
|
|
12402
|
+
seen.add(obsUuid);
|
|
12403
|
+
const obs = obsMap.get(obsUuid);
|
|
12404
|
+
if (obs?.description) obsDescs.push(obs.description);
|
|
12405
|
+
}
|
|
12406
|
+
if (obsDescs.length > 0) descriptions.push({
|
|
12407
|
+
label: "rationale",
|
|
12408
|
+
data: obsDescs.join("\n")
|
|
12409
|
+
});
|
|
12410
|
+
return descriptions;
|
|
12411
|
+
}
|
|
12412
|
+
function buildObservationMap(observations) {
|
|
12413
|
+
const m = /* @__PURE__ */ new Map();
|
|
12414
|
+
for (const obs of observations) m.set(obs.uuid, obs);
|
|
12415
|
+
return m;
|
|
12416
|
+
}
|
|
12417
|
+
function buildRiskMap(risks) {
|
|
12418
|
+
const m = /* @__PURE__ */ new Map();
|
|
12419
|
+
for (const risk of risks) m.set(risk.uuid, risk);
|
|
12420
|
+
return m;
|
|
12421
|
+
}
|
|
12422
|
+
function parseResultStartTime(result) {
|
|
12423
|
+
if (result.start) {
|
|
12424
|
+
const t = parseTimestamp(String(result.start));
|
|
12425
|
+
if (t) return t;
|
|
12426
|
+
}
|
|
12427
|
+
return /* @__PURE__ */ new Date(0);
|
|
12428
|
+
}
|
|
12429
|
+
function sarBaselineName(result, sar) {
|
|
12430
|
+
return toKebabCase(result.title || sar.metadata.title, "oscal-assessment-results");
|
|
12431
|
+
}
|
|
12432
|
+
//#endregion
|
|
12433
|
+
//#region converters/oscal-to-hdf/typescript/converter-ssp.ts
|
|
12434
|
+
/**
|
|
12435
|
+
* OSCAL System Security Plan (SSP) to HDF System converter.
|
|
12436
|
+
*
|
|
12437
|
+
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_ssp.go.
|
|
12438
|
+
*/
|
|
12439
|
+
/**
|
|
12440
|
+
* Converts an OSCAL System Security Plan document to HDF System JSON.
|
|
12441
|
+
*
|
|
12442
|
+
* @param input - Raw JSON string containing an OSCAL SSP
|
|
12443
|
+
* @returns HDF System JSON string
|
|
12444
|
+
*/
|
|
12445
|
+
async function convertOscalSspToHdf(input) {
|
|
12446
|
+
validateInputSize(input, "oscal-ssp");
|
|
12447
|
+
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
12448
|
+
const doc = parseJSON(input);
|
|
12449
|
+
if (!doc["system-security-plan"]) throw new Error("oscal-ssp: input is not a system-security-plan document (root key is not 'system-security-plan')");
|
|
12450
|
+
const ssp = doc["system-security-plan"];
|
|
12451
|
+
const integrity = await inputIntegrity(input);
|
|
12452
|
+
const system = {
|
|
12453
|
+
name: sspSystemName(ssp),
|
|
12454
|
+
integrity,
|
|
12455
|
+
components: [],
|
|
12456
|
+
generator: {
|
|
12457
|
+
name: "oscal-ssp-to-hdf",
|
|
12458
|
+
version: "1.0.0"
|
|
12459
|
+
}
|
|
12460
|
+
};
|
|
12461
|
+
const sc = ssp["system-characteristics"];
|
|
12462
|
+
if (sc) {
|
|
12463
|
+
if (sc.description) {
|
|
12464
|
+
let desc = sc.description;
|
|
12465
|
+
if (sc["authorization-boundary"]?.description) desc += "\n\nAuthorization Boundary: " + sc["authorization-boundary"].description;
|
|
12466
|
+
system.description = desc;
|
|
12467
|
+
}
|
|
12468
|
+
const sil = sc["security-impact-level"];
|
|
12469
|
+
if (sil) system.categorizationLevel = sspCategorizationLevel(sil) ?? void 0;
|
|
12470
|
+
else if (sc["security-sensitivity-level"]) system.categorizationLevel = mapSensitivityToCategorizationLevel(sc["security-sensitivity-level"]) ?? void 0;
|
|
12471
|
+
if (sc.status) system.authorizationStatus = sspAuthorizationStatus(sc.status.state) ?? void 0;
|
|
12472
|
+
if (sc["authorization-boundary"]?.description) system.boundaryDescription = sc["authorization-boundary"].description;
|
|
12473
|
+
const systemIds = sc["system-ids"];
|
|
12474
|
+
if (systemIds && systemIds.length > 0) {
|
|
12475
|
+
system.identifier = systemIds[0].id;
|
|
12476
|
+
if (systemIds[0]["identifier-type"]) system.identifierScheme = systemIds[0]["identifier-type"];
|
|
12477
|
+
}
|
|
12478
|
+
}
|
|
12479
|
+
const meta = extractMetadata(ssp.metadata);
|
|
12480
|
+
if (meta.version) system.version = meta.version;
|
|
12481
|
+
const componentControls = buildComponentControlMap(ssp["control-implementation"]);
|
|
12482
|
+
const si = ssp["system-implementation"];
|
|
12483
|
+
if (si?.components) for (const comp of si.components) system.components.push(sspComponentToHDFComponent(comp, componentControls));
|
|
12484
|
+
return JSON.stringify(system, null, 2);
|
|
12485
|
+
}
|
|
12486
|
+
function sspSystemName(ssp) {
|
|
12487
|
+
const sc = ssp["system-characteristics"];
|
|
12488
|
+
if (sc?.["system-name"]) return sc["system-name"];
|
|
12489
|
+
if (ssp.metadata.title) return ssp.metadata.title;
|
|
12490
|
+
return "oscal-ssp";
|
|
12491
|
+
}
|
|
12492
|
+
function sspCategorizationLevel(sil) {
|
|
12493
|
+
const levels = [
|
|
12494
|
+
sil["security-objective-confidentiality"] ?? "",
|
|
12495
|
+
sil["security-objective-integrity"] ?? "",
|
|
12496
|
+
sil["security-objective-availability"] ?? ""
|
|
12497
|
+
];
|
|
12498
|
+
let highest = "";
|
|
12499
|
+
for (const l of levels) {
|
|
12500
|
+
const normalized = normalizeFIPSLevel(l);
|
|
12501
|
+
if (fipsLevelRank(normalized) > fipsLevelRank(highest)) highest = normalized;
|
|
12502
|
+
}
|
|
12503
|
+
if (!highest) return null;
|
|
12504
|
+
switch (highest) {
|
|
12505
|
+
case "high": return CategorizationLevel.High;
|
|
12506
|
+
case "moderate": return CategorizationLevel.Moderate;
|
|
12507
|
+
case "low": return CategorizationLevel.Low;
|
|
12508
|
+
default: return null;
|
|
12509
|
+
}
|
|
12510
|
+
}
|
|
12511
|
+
function normalizeFIPSLevel(level) {
|
|
12512
|
+
let lower = level.toLowerCase();
|
|
12513
|
+
lower = lower.replace(/^fips-199-/, "");
|
|
12514
|
+
switch (lower) {
|
|
12515
|
+
case "high": return "high";
|
|
12516
|
+
case "moderate":
|
|
12517
|
+
case "medium": return "moderate";
|
|
12518
|
+
case "low": return "low";
|
|
12519
|
+
default: return "";
|
|
12520
|
+
}
|
|
12521
|
+
}
|
|
12522
|
+
function fipsLevelRank(level) {
|
|
12523
|
+
switch (level) {
|
|
12524
|
+
case "high": return 3;
|
|
12525
|
+
case "moderate": return 2;
|
|
12526
|
+
case "low": return 1;
|
|
12527
|
+
default: return 0;
|
|
12528
|
+
}
|
|
12529
|
+
}
|
|
12530
|
+
function mapSensitivityToCategorizationLevel(level) {
|
|
12531
|
+
const normalized = normalizeFIPSLevel(level);
|
|
12532
|
+
if (!normalized) return null;
|
|
12533
|
+
switch (normalized) {
|
|
12534
|
+
case "high": return CategorizationLevel.High;
|
|
12535
|
+
case "moderate": return CategorizationLevel.Moderate;
|
|
12536
|
+
case "low": return CategorizationLevel.Low;
|
|
12537
|
+
default: return null;
|
|
12538
|
+
}
|
|
12539
|
+
}
|
|
12540
|
+
function sspAuthorizationStatus(state) {
|
|
12541
|
+
switch (state.toLowerCase()) {
|
|
12542
|
+
case "operational": return AuthorizationStatus.Authorized;
|
|
12543
|
+
case "under-development": return AuthorizationStatus.PendingAuthorization;
|
|
12544
|
+
case "disposition": return AuthorizationStatus.Revoked;
|
|
12545
|
+
case "other": return AuthorizationStatus.NotYetRequested;
|
|
12546
|
+
default: return null;
|
|
12547
|
+
}
|
|
12548
|
+
}
|
|
12549
|
+
function buildComponentControlMap(ci) {
|
|
12550
|
+
const result = /* @__PURE__ */ new Map();
|
|
12551
|
+
if (!ci) return result;
|
|
12552
|
+
for (const ir of ci["implemented-requirements"] ?? []) {
|
|
12553
|
+
const controlId = ir["control-id"];
|
|
12554
|
+
for (const bc of ir["by-components"] ?? []) addComponentControl(result, bc["component-uuid"], controlId);
|
|
12555
|
+
for (const stmt of ir.statements ?? []) for (const bc of stmt["by-components"] ?? []) addComponentControl(result, bc["component-uuid"], controlId);
|
|
12556
|
+
}
|
|
12557
|
+
return result;
|
|
12558
|
+
}
|
|
12559
|
+
function addComponentControl(m, compUUID, controlId) {
|
|
12560
|
+
let controls = m.get(compUUID);
|
|
12561
|
+
if (!controls) {
|
|
12562
|
+
controls = /* @__PURE__ */ new Set();
|
|
12563
|
+
m.set(compUUID, controls);
|
|
12564
|
+
}
|
|
12565
|
+
controls.add(controlId);
|
|
12083
12566
|
}
|
|
12084
|
-
function
|
|
12085
|
-
const
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12567
|
+
function sspComponentToHDFComponent(sc, componentControls) {
|
|
12568
|
+
const comp = {
|
|
12569
|
+
name: sc.title,
|
|
12570
|
+
type: mapOSCALComponentType(sc.type)
|
|
12571
|
+
};
|
|
12572
|
+
if (sc.description) comp.description = sc.description;
|
|
12573
|
+
const controls = componentControls.get(sc.uuid);
|
|
12574
|
+
if (controls && controls.size > 0) {
|
|
12575
|
+
const refs = [];
|
|
12576
|
+
for (const controlId of controls) refs.push(controlIdToNistTag(controlId));
|
|
12577
|
+
if (refs.length > 0) {
|
|
12578
|
+
refs.sort();
|
|
12579
|
+
comp.baselineRefs = refs;
|
|
12096
12580
|
}
|
|
12097
12581
|
}
|
|
12098
|
-
|
|
12099
|
-
return parts.join("; ");
|
|
12582
|
+
return comp;
|
|
12100
12583
|
}
|
|
12101
|
-
function
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12584
|
+
function mapOSCALComponentType(oscalType) {
|
|
12585
|
+
switch (oscalType.toLowerCase()) {
|
|
12586
|
+
case "software":
|
|
12587
|
+
case "this-system": return TargetType.Application;
|
|
12588
|
+
case "service": return TargetType.Application;
|
|
12589
|
+
case "hardware": return TargetType.Host;
|
|
12590
|
+
case "network": return TargetType.Network;
|
|
12591
|
+
case "database": return TargetType.Database;
|
|
12592
|
+
case "storage": return TargetType.Artifact;
|
|
12593
|
+
default: return TargetType.Application;
|
|
12111
12594
|
}
|
|
12112
|
-
return messages.join("\n");
|
|
12113
12595
|
}
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12596
|
+
//#endregion
|
|
12597
|
+
//#region converters/oscal-to-hdf/typescript/converter-profile.ts
|
|
12598
|
+
/**
|
|
12599
|
+
* OSCAL Profile to HDF Baseline converter.
|
|
12600
|
+
*
|
|
12601
|
+
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_profile.go.
|
|
12602
|
+
*
|
|
12603
|
+
* This implements the "simple resolver" -- it handles:
|
|
12604
|
+
* - A single catalog import
|
|
12605
|
+
* - include-controls with with-ids filtering
|
|
12606
|
+
* - set-parameters from modify section
|
|
12607
|
+
* - merge as-is (preserve catalog structure)
|
|
12608
|
+
*
|
|
12609
|
+
* It does NOT handle:
|
|
12610
|
+
* - Multiple imports
|
|
12611
|
+
* - Nested profile imports
|
|
12612
|
+
* - alter directives
|
|
12613
|
+
* - Complex merge strategies
|
|
12614
|
+
*/
|
|
12615
|
+
/**
|
|
12616
|
+
* Converts an OSCAL Profile against a provided catalog to HDF Baseline JSON.
|
|
12617
|
+
*
|
|
12618
|
+
* @param profileInput - Raw JSON string containing an OSCAL profile
|
|
12619
|
+
* @param catalogInput - Raw JSON string containing an OSCAL catalog
|
|
12620
|
+
* @returns HDF Baseline JSON string
|
|
12621
|
+
*/
|
|
12622
|
+
async function convertOscalProfileToHdf(profileInput, catalogInput) {
|
|
12623
|
+
validateInputSize(profileInput, "oscal-profile");
|
|
12624
|
+
if (!profileInput || profileInput.trim().length === 0) throw new Error("empty profile input");
|
|
12625
|
+
if (!catalogInput || catalogInput.trim().length === 0) throw new Error("empty catalog input");
|
|
12626
|
+
const profileDoc = parseJSON(profileInput);
|
|
12627
|
+
if (!profileDoc.profile) throw new Error("oscal-profile: input is not a profile document (root key is not 'profile')");
|
|
12628
|
+
const profile = profileDoc.profile;
|
|
12629
|
+
const catalogDoc = parseJSON(catalogInput);
|
|
12630
|
+
if (!catalogDoc.catalog) throw new Error("oscal-profile: catalog input is not a catalog document (root key is not 'catalog')");
|
|
12631
|
+
const catalog = catalogDoc.catalog;
|
|
12632
|
+
if (!profile.imports || profile.imports.length === 0) throw new Error("oscal-profile: profile has no imports");
|
|
12633
|
+
if (profile.imports.length > 1) throw new Error(`oscal-profile: profile has ${profile.imports.length} imports -- this converter only supports single-catalog imports. Use NIST's oscal-cli to resolve complex profiles, or use a pre-resolved catalog`);
|
|
12634
|
+
if (profile.modify?.alters && profile.modify.alters.length > 0) throw new Error(`oscal-profile: profile contains ${profile.modify.alters.length} alter directives -- this converter only supports parameter overrides. Use NIST's oscal-cli to resolve profiles with alter directives, or use a pre-resolved catalog`);
|
|
12635
|
+
const imp = profile.imports[0];
|
|
12636
|
+
const resolvedCatalog = filterCatalog(catalog, collectIncludedIDs(imp), collectExcludedIDs(imp));
|
|
12637
|
+
if (profile.modify?.["set-parameters"]) applyParameterOverrides(resolvedCatalog, profile.modify["set-parameters"]);
|
|
12638
|
+
resolvedCatalog.metadata = profile.metadata;
|
|
12639
|
+
resolvedCatalog.uuid = profile.uuid;
|
|
12640
|
+
const baseline = await catalogToBaseline(resolvedCatalog, profileInput);
|
|
12641
|
+
baseline.integrity = await inputIntegrity(profileInput);
|
|
12642
|
+
return JSON.stringify(baseline, null, 2);
|
|
12643
|
+
}
|
|
12644
|
+
/** Extracts all control IDs from an import's include-controls. Returns null if include all. */
|
|
12645
|
+
function collectIncludedIDs(imp) {
|
|
12646
|
+
const includeControls = imp["include-controls"];
|
|
12647
|
+
if (!includeControls || includeControls.length === 0) return null;
|
|
12648
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12649
|
+
for (const ic of includeControls) for (const id of ic["with-ids"] ?? []) ids.add(id);
|
|
12650
|
+
return ids;
|
|
12651
|
+
}
|
|
12652
|
+
/** Extracts all control IDs from an import's exclude-controls. */
|
|
12653
|
+
function collectExcludedIDs(imp) {
|
|
12654
|
+
const excludeControls = imp["exclude-controls"];
|
|
12655
|
+
if (!excludeControls || excludeControls.length === 0) return null;
|
|
12656
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12657
|
+
for (const ec of excludeControls) for (const id of ec["with-ids"] ?? []) ids.add(id);
|
|
12658
|
+
return ids;
|
|
12659
|
+
}
|
|
12660
|
+
/** Creates a new catalog containing only the controls matching filters. */
|
|
12661
|
+
function filterCatalog(catalog, includedIDs, excludedIDs) {
|
|
12662
|
+
const includeAll = includedIDs === null;
|
|
12663
|
+
const result = {
|
|
12664
|
+
uuid: catalog.uuid,
|
|
12665
|
+
metadata: catalog.metadata,
|
|
12666
|
+
"back-matter": catalog["back-matter"],
|
|
12667
|
+
groups: [],
|
|
12668
|
+
controls: []
|
|
12669
|
+
};
|
|
12670
|
+
for (const group of catalog.groups ?? []) {
|
|
12671
|
+
const filtered = filterGroupControls(group, includedIDs, excludedIDs, includeAll);
|
|
12672
|
+
if (filtered.controls && filtered.controls.length > 0) result.groups.push(filtered);
|
|
12123
12673
|
}
|
|
12124
|
-
|
|
12125
|
-
|
|
12674
|
+
for (const ctrl of catalog.controls ?? []) if (shouldIncludeControl(ctrl.id, includedIDs, excludedIDs, includeAll)) {
|
|
12675
|
+
const filteredCtrl = filterControlEnhancements(ctrl, includedIDs, excludedIDs, includeAll);
|
|
12676
|
+
result.controls.push(filteredCtrl);
|
|
12677
|
+
}
|
|
12678
|
+
return result;
|
|
12126
12679
|
}
|
|
12127
|
-
function
|
|
12128
|
-
const
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
|
|
12136
|
-
const
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
if (!obsUuid || seen.has(obsUuid)) continue;
|
|
12140
|
-
seen.add(obsUuid);
|
|
12141
|
-
const obs = obsMap.get(obsUuid);
|
|
12142
|
-
if (obs?.description) obsDescs.push(obs.description);
|
|
12680
|
+
function filterGroupControls(group, includedIDs, excludedIDs, includeAll) {
|
|
12681
|
+
const filtered = {
|
|
12682
|
+
id: group.id,
|
|
12683
|
+
class: group.class,
|
|
12684
|
+
title: group.title,
|
|
12685
|
+
props: group.props,
|
|
12686
|
+
parts: group.parts,
|
|
12687
|
+
controls: []
|
|
12688
|
+
};
|
|
12689
|
+
for (const ctrl of group.controls ?? []) if (shouldIncludeControl(ctrl.id, includedIDs, excludedIDs, includeAll)) {
|
|
12690
|
+
const filteredCtrl = filterControlEnhancements(ctrl, includedIDs, excludedIDs, includeAll);
|
|
12691
|
+
filtered.controls.push(filteredCtrl);
|
|
12143
12692
|
}
|
|
12144
|
-
|
|
12145
|
-
label: "rationale",
|
|
12146
|
-
data: obsDescs.join("\n")
|
|
12147
|
-
});
|
|
12148
|
-
return descriptions;
|
|
12693
|
+
return filtered;
|
|
12149
12694
|
}
|
|
12150
|
-
function
|
|
12151
|
-
const
|
|
12152
|
-
|
|
12153
|
-
|
|
12695
|
+
function filterControlEnhancements(ctrl, includedIDs, excludedIDs, includeAll) {
|
|
12696
|
+
const result = {
|
|
12697
|
+
id: ctrl.id,
|
|
12698
|
+
class: ctrl.class,
|
|
12699
|
+
title: ctrl.title,
|
|
12700
|
+
params: ctrl.params,
|
|
12701
|
+
props: ctrl.props,
|
|
12702
|
+
links: ctrl.links,
|
|
12703
|
+
parts: ctrl.parts,
|
|
12704
|
+
controls: []
|
|
12705
|
+
};
|
|
12706
|
+
for (const enh of ctrl.controls ?? []) if (shouldIncludeControl(enh.id, includedIDs, excludedIDs, includeAll)) result.controls.push(enh);
|
|
12707
|
+
return result;
|
|
12154
12708
|
}
|
|
12155
|
-
function
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
return
|
|
12709
|
+
function shouldIncludeControl(id, includedIDs, excludedIDs, includeAll) {
|
|
12710
|
+
if (excludedIDs !== null && excludedIDs.has(id)) return false;
|
|
12711
|
+
if (includeAll) return true;
|
|
12712
|
+
return includedIDs !== null && includedIDs.has(id);
|
|
12159
12713
|
}
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12714
|
+
/** Applies set-parameters from the profile's modify section to the resolved catalog. */
|
|
12715
|
+
function applyParameterOverrides(catalog, setParams) {
|
|
12716
|
+
if (setParams.length === 0) return;
|
|
12717
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
12718
|
+
for (const sp of setParams) if (sp["param-id"]) overrides.set(sp["param-id"], sp.values ?? []);
|
|
12719
|
+
for (const group of catalog.groups ?? []) for (const ctrl of group.controls ?? []) {
|
|
12720
|
+
applyParamOverridesToControl(ctrl, overrides);
|
|
12721
|
+
for (const enh of ctrl.controls ?? []) applyParamOverridesToControl(enh, overrides);
|
|
12722
|
+
}
|
|
12723
|
+
for (const ctrl of catalog.controls ?? []) {
|
|
12724
|
+
applyParamOverridesToControl(ctrl, overrides);
|
|
12725
|
+
for (const enh of ctrl.controls ?? []) applyParamOverridesToControl(enh, overrides);
|
|
12164
12726
|
}
|
|
12165
|
-
return /* @__PURE__ */ new Date(0);
|
|
12166
12727
|
}
|
|
12167
|
-
function
|
|
12168
|
-
|
|
12728
|
+
function applyParamOverridesToControl(ctrl, overrides) {
|
|
12729
|
+
if (ctrl.params) for (const param of ctrl.params) {
|
|
12730
|
+
if (!param.id) continue;
|
|
12731
|
+
const values = overrides.get(param.id);
|
|
12732
|
+
if (values) param.label = values.join(", ");
|
|
12733
|
+
}
|
|
12734
|
+
if (ctrl.parts) for (const part of ctrl.parts) replaceParamInsertions(part, overrides);
|
|
12735
|
+
}
|
|
12736
|
+
function replaceParamInsertions(part, overrides) {
|
|
12737
|
+
if (part.prose) part.prose = substituteParams(part.prose, overrides);
|
|
12738
|
+
if (part.parts) for (const child of part.parts) replaceParamInsertions(child, overrides);
|
|
12739
|
+
}
|
|
12740
|
+
/** Replaces OSCAL parameter insertion patterns: {{ insert: param, <param-id> }} */
|
|
12741
|
+
function substituteParams(text, overrides) {
|
|
12742
|
+
let result = text;
|
|
12743
|
+
for (const [paramId, values] of overrides) {
|
|
12744
|
+
const placeholder = `{{ insert: param, ${paramId} }}`;
|
|
12745
|
+
const replacement = values.join(", ");
|
|
12746
|
+
result = result.split(placeholder).join(replacement);
|
|
12747
|
+
}
|
|
12748
|
+
return result;
|
|
12169
12749
|
}
|
|
12170
12750
|
//#endregion
|
|
12171
|
-
export { convertAwsConfigToHdf, convertBurpsuiteToHdf, convertCheckovToHdf, convertCklToHdf, convertCklbToHdf, convertConveyorToHdf, convertCsafVexToHdf, convertCyclonedxToHdf, convertCyclonedxVexToHdf, convertDbprotectToHdf, convertDeptrackToHdf, convertFortifyToHdf, convertGitlabToHdf, convertGosecToHdf, convertGrypeToHdf, convertHdfToCkl, convertHdfToCklb, convertHdfToCsafVex, convertHdfToCsv, convertHdfToCyclonedxVex, convertHdfToEcs, convertHdfToOcsf, convertHdfToOpenVex, convertHdfToOscalPoam, convertHdfToOscalSar, convertHdfToSplunk, convertHdfToXccdf, convertHdfToXml, 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 };
|
|
12751
|
+
export { convertAsffToHdf, convertAwsConfigToHdf, convertBurpsuiteToHdf, convertCheckovToHdf, convertCklToHdf, convertCklbToHdf, convertConveyorToHdf, convertCsafVexToHdf, convertCyclonedxToHdf, convertCyclonedxVexToHdf, convertDbprotectToHdf, convertDeptrackToHdf, convertFortifyToHdf, convertGitlabToHdf, convertGosecToHdf, convertGrypeToHdf, convertHdfToAsff, convertHdfToCkl, convertHdfToCklb, convertHdfToCsafVex, convertHdfToCsv, convertHdfToCyclonedxVex, convertHdfToEcs, convertHdfToOcsf, convertHdfToOpenVex, convertHdfToOscalPoam, convertHdfToOscalSar, convertHdfToSplunk, convertHdfToXccdf, convertHdfToXml, 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 };
|
|
12172
12752
|
|
|
12173
12753
|
//# sourceMappingURL=index.js.map
|