@mitre/hdf-converters 3.4.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +1675 -1128
- 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,335 @@ 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
|
+
/** Summarizes the installed vs patched package for a Trivy CVE finding. */
|
|
1060
|
+
function trivyMessage(f) {
|
|
1061
|
+
const o = f.Resources?.[0]?.Details?.Other;
|
|
1062
|
+
if (!o || !o["CVE ID"]) return "";
|
|
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
|
+
function buildRequirement$17(id, group) {
|
|
1068
|
+
const primary = group[0];
|
|
1069
|
+
const impact = Math.max(...group.map(findingImpact));
|
|
1070
|
+
const descriptions = [{
|
|
1071
|
+
label: "default",
|
|
1072
|
+
data: productOf(primary) === "prowler" ? " " : primary.Description ?? primary.Title ?? ""
|
|
1073
|
+
}];
|
|
1074
|
+
const fix = remediationText(primary);
|
|
1075
|
+
if (fix) descriptions.push({
|
|
1076
|
+
label: "fix",
|
|
1077
|
+
data: fix
|
|
1078
|
+
});
|
|
1079
|
+
const nist = nistTags(group);
|
|
1080
|
+
const tags = nist.length > 0 ? buildNistCciTags(nist, nistToCci(nist)) : {};
|
|
1081
|
+
const results = group.map(buildResult$2);
|
|
1082
|
+
const req = createRequirement(id, primary.Title ?? "", descriptions, impact, results, { tags });
|
|
1083
|
+
const controlType = deriveControlTypeFromTags(nist);
|
|
1084
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
1085
|
+
const refs = [];
|
|
1086
|
+
if (primary.SourceUrl) refs.push({ url: primary.SourceUrl });
|
|
1087
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1088
|
+
for (const v of primary.Vulnerabilities ?? []) for (const u of v.ReferenceUrls ?? []) if (u && !seen.has(u)) {
|
|
1089
|
+
seen.add(u);
|
|
1090
|
+
refs.push({ url: u });
|
|
1091
|
+
}
|
|
1092
|
+
if (refs.length > 0) req.refs = refs;
|
|
1093
|
+
return req;
|
|
1094
|
+
}
|
|
1095
|
+
function buildBaseline$1(name, findings, resultsChecksum) {
|
|
1096
|
+
const order = [];
|
|
1097
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1098
|
+
for (const f of findings) {
|
|
1099
|
+
const id = controlId(f);
|
|
1100
|
+
const existing = groups.get(id);
|
|
1101
|
+
if (existing) existing.push(f);
|
|
1102
|
+
else {
|
|
1103
|
+
order.push(id);
|
|
1104
|
+
groups.set(id, [f]);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return createMinimalBaseline(name, order.map((id) => buildRequirement$17(id, groups.get(id))), { resultsChecksum });
|
|
1108
|
+
}
|
|
1109
|
+
/** Converts an ASFF document to HDF Results JSON. */
|
|
1110
|
+
async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
1111
|
+
if (!input || input.trim().length === 0) throw new Error("asff: empty input");
|
|
1112
|
+
validateInputSize(input, "asff");
|
|
1113
|
+
const findings = parseFindings$1(input);
|
|
1114
|
+
const resultsChecksum = await inputChecksum(input);
|
|
1115
|
+
const order = [];
|
|
1116
|
+
const byBaseline = /* @__PURE__ */ new Map();
|
|
1117
|
+
const accounts = [];
|
|
1118
|
+
const seenAccount = /* @__PURE__ */ new Set();
|
|
1119
|
+
for (const f of findings) {
|
|
1120
|
+
const name = baselineName(f);
|
|
1121
|
+
const existing = byBaseline.get(name);
|
|
1122
|
+
if (existing) existing.push(f);
|
|
1123
|
+
else {
|
|
1124
|
+
order.push(name);
|
|
1125
|
+
byBaseline.set(name, [f]);
|
|
1126
|
+
}
|
|
1127
|
+
if (f.AwsAccountId && !seenAccount.has(f.AwsAccountId)) {
|
|
1128
|
+
seenAccount.add(f.AwsAccountId);
|
|
1129
|
+
accounts.push(f.AwsAccountId);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
let baselines = order.map((name) => buildBaseline$1(name, byBaseline.get(name), resultsChecksum));
|
|
1133
|
+
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 })];
|
|
1134
|
+
const hdf = {
|
|
1135
|
+
baselines,
|
|
1136
|
+
generator: {
|
|
1137
|
+
name: "asff-to-hdf",
|
|
1138
|
+
version: converterVersion
|
|
1139
|
+
},
|
|
1140
|
+
tool: {
|
|
1141
|
+
name: "AWS Security Finding Format",
|
|
1142
|
+
format: "JSON"
|
|
1143
|
+
},
|
|
1144
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1145
|
+
};
|
|
1146
|
+
if (accounts.length > 0) {
|
|
1147
|
+
const refs = baselines.map((b) => b.name);
|
|
1148
|
+
hdf.components = accounts.map((acct) => ({
|
|
1149
|
+
name: acct,
|
|
1150
|
+
type: TargetType.CloudAccount,
|
|
1151
|
+
baselineRefs: refs
|
|
1152
|
+
}));
|
|
1153
|
+
}
|
|
1154
|
+
return serializeHdf(hdf);
|
|
1155
|
+
}
|
|
1156
|
+
//#endregion
|
|
801
1157
|
//#region converters/sarif-to-hdf/typescript/converter.ts
|
|
802
1158
|
const IMPACT_MAPPING$9 = {
|
|
803
1159
|
error: .7,
|
|
@@ -1949,6 +2305,9 @@ function parseCklb(input) {
|
|
|
1949
2305
|
return {
|
|
1950
2306
|
format: "cklb",
|
|
1951
2307
|
cklbVersion: nz(doc.cklb_version),
|
|
2308
|
+
active: doc.active ?? false,
|
|
2309
|
+
hasPath: doc.has_path ?? false,
|
|
2310
|
+
mode: doc.mode ?? 0,
|
|
1952
2311
|
asset,
|
|
1953
2312
|
stigs
|
|
1954
2313
|
};
|
|
@@ -1989,8 +2348,9 @@ function serializeCklb(cl) {
|
|
|
1989
2348
|
const doc = {
|
|
1990
2349
|
title: cklbTitle(cl),
|
|
1991
2350
|
cklb_version: cl.cklbVersion || "1.0",
|
|
1992
|
-
active: false,
|
|
1993
|
-
|
|
2351
|
+
active: cl.active ?? false,
|
|
2352
|
+
...cl.mode ? { mode: cl.mode } : {},
|
|
2353
|
+
has_path: cl.hasPath ?? false,
|
|
1994
2354
|
target_data: {
|
|
1995
2355
|
target_type: cl.asset.assetType || "Computing",
|
|
1996
2356
|
host_name: cl.asset.hostName ?? "",
|
|
@@ -2132,6 +2492,9 @@ function assetToComponent(a) {
|
|
|
2132
2492
|
function rootExtensions(cl) {
|
|
2133
2493
|
const ext = { checklistFormat: cl.format || "ckl" };
|
|
2134
2494
|
if (cl.cklbVersion) ext["cklbVersion"] = cl.cklbVersion;
|
|
2495
|
+
if (cl.active) ext["cklbActive"] = true;
|
|
2496
|
+
if (cl.hasPath) ext["cklbHasPath"] = true;
|
|
2497
|
+
if (cl.mode) ext["cklbMode"] = cl.mode;
|
|
2135
2498
|
const ax = {};
|
|
2136
2499
|
setIf$1(ax, "role", cl.asset.role);
|
|
2137
2500
|
setIf$1(ax, "assetType", cl.asset.assetType);
|
|
@@ -2178,6 +2541,9 @@ function hdfToChecklist(input) {
|
|
|
2178
2541
|
return {
|
|
2179
2542
|
format,
|
|
2180
2543
|
cklbVersion: cklbVersion || void 0,
|
|
2544
|
+
active: boolVal(ext, "cklbActive"),
|
|
2545
|
+
hasPath: boolVal(ext, "cklbHasPath"),
|
|
2546
|
+
mode: numVal(ext, "cklbMode"),
|
|
2181
2547
|
asset,
|
|
2182
2548
|
stigs
|
|
2183
2549
|
};
|
|
@@ -2279,6 +2645,13 @@ function strVal(m, key) {
|
|
|
2279
2645
|
const v = m[key];
|
|
2280
2646
|
return typeof v === "string" ? v : "";
|
|
2281
2647
|
}
|
|
2648
|
+
function boolVal(m, key) {
|
|
2649
|
+
return m[key] === true;
|
|
2650
|
+
}
|
|
2651
|
+
function numVal(m, key) {
|
|
2652
|
+
const v = m[key];
|
|
2653
|
+
return typeof v === "number" ? v : 0;
|
|
2654
|
+
}
|
|
2282
2655
|
function strSlice(m, key) {
|
|
2283
2656
|
const v = m[key];
|
|
2284
2657
|
if (Array.isArray(v)) return v.filter((e) => typeof e === "string");
|
|
@@ -3497,16 +3870,11 @@ function buildResult(r) {
|
|
|
3497
3870
|
const q = r.EvaluationResultIdentifier.EvaluationResultQualifier;
|
|
3498
3871
|
const status = mapComplianceStatus(r.ComplianceType);
|
|
3499
3872
|
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,
|
|
3873
|
+
return createResult(status, buildResultMessage(codeDesc, r.Annotation, status), {
|
|
3505
3874
|
codeDesc,
|
|
3506
|
-
startTime,
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
};
|
|
3875
|
+
startTime: (r.ConfigRuleInvokedTime ? parseTimestamp(r.ConfigRuleInvokedTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z"),
|
|
3876
|
+
runTime: computeRunTime(r.ConfigRuleInvokedTime, r.ResultRecordedTime)
|
|
3877
|
+
});
|
|
3510
3878
|
}
|
|
3511
3879
|
/**
|
|
3512
3880
|
* Synthesizes a single HDF result for a Config rule whose live evaluation
|
|
@@ -3517,11 +3885,10 @@ function buildResult(r) {
|
|
|
3517
3885
|
*/
|
|
3518
3886
|
function buildNotApplicableResult(rule) {
|
|
3519
3887
|
const codeDesc = `AWS Config rule ${rule.ConfigRuleName} evaluated zero in-scope resources in this account/region.`;
|
|
3520
|
-
return {
|
|
3521
|
-
status: ResultStatus.NotApplicable,
|
|
3888
|
+
return createResult(ResultStatus.NotApplicable, void 0, {
|
|
3522
3889
|
codeDesc,
|
|
3523
3890
|
startTime: /* @__PURE__ */ new Date()
|
|
3524
|
-
};
|
|
3891
|
+
});
|
|
3525
3892
|
}
|
|
3526
3893
|
function buildRequirement$15(rule) {
|
|
3527
3894
|
const nist = buildNistTags$3(rule.Source.SourceIdentifier, rule.ConfigRuleName);
|
|
@@ -3618,12 +3985,10 @@ function checkToResult(check, scanTime) {
|
|
|
3618
3985
|
const codeDesc = `Resource: ${check.resource}\nFile: ${check.file_path} (lines ${JSON.stringify(check.file_line_range)})`;
|
|
3619
3986
|
let message;
|
|
3620
3987
|
if (status === ResultStatus.NotReviewed && check.check_result.suppress_comment) message = check.check_result.suppress_comment;
|
|
3621
|
-
return {
|
|
3622
|
-
status,
|
|
3988
|
+
return createResult(status, message, {
|
|
3623
3989
|
codeDesc,
|
|
3624
|
-
startTime: scanTime
|
|
3625
|
-
|
|
3626
|
-
};
|
|
3990
|
+
startTime: scanTime
|
|
3991
|
+
});
|
|
3627
3992
|
}
|
|
3628
3993
|
/**
|
|
3629
3994
|
* Converts a group of checks sharing a check_id into one EvaluatedRequirement.
|
|
@@ -4645,472 +5010,202 @@ function parseBom(input) {
|
|
|
4645
5010
|
}
|
|
4646
5011
|
}
|
|
4647
5012
|
//#endregion
|
|
4648
|
-
//#region
|
|
4649
|
-
const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
4650
|
-
"CVSSv2",
|
|
4651
|
-
"CVSSv3",
|
|
4652
|
-
"CVSSv31",
|
|
4653
|
-
"CVSSv4"
|
|
4654
|
-
]);
|
|
5013
|
+
//#region shared/typescript/exportmap.ts
|
|
4655
5014
|
/**
|
|
4656
|
-
*
|
|
4657
|
-
*
|
|
5015
|
+
* Generic, source-tool-agnostic mapping helpers shared by the HDF export
|
|
5016
|
+
* converters (hdf-to-ecs, hdf-to-splunk, ...).
|
|
5017
|
+
*
|
|
5018
|
+
* The export converters deliberately operate on generically-parsed JSON rather
|
|
5019
|
+
* than typed HDF structs, so their output can be held byte-identical with the
|
|
5020
|
+
* Go implementations. This module centralizes the generic accessors, the status
|
|
5021
|
+
* roll-up, the requirement/document field extraction, and the canonical
|
|
5022
|
+
* (key-sorted, HTML-unescaped) line serialization. Target-specific event shaping
|
|
5023
|
+
* (ECS field names, CIM field names, envelopes) stays in each converter.
|
|
4658
5024
|
*/
|
|
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;
|
|
5025
|
+
function asMap(v) {
|
|
5026
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
4669
5027
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
*/
|
|
4673
|
-
function formatRatingsTag(ratings) {
|
|
4674
|
-
return ratings.map((r) => `${r.source?.name ?? "Unknown"} - ${r.severity ?? "unrated"}`).join(", ");
|
|
5028
|
+
function asArr(v) {
|
|
5029
|
+
return Array.isArray(v) ? v : void 0;
|
|
4675
5030
|
}
|
|
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`;
|
|
5031
|
+
function getStr(m, key) {
|
|
5032
|
+
const v = m?.[key];
|
|
5033
|
+
return typeof v === "string" ? v : "";
|
|
4687
5034
|
}
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
function
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
5035
|
+
function setIf(m, key, val) {
|
|
5036
|
+
if (val !== "") m[key] = val;
|
|
5037
|
+
}
|
|
5038
|
+
function stringSlice(v) {
|
|
5039
|
+
if (typeof v === "string") return [v];
|
|
5040
|
+
if (Array.isArray(v)) return v.filter((e) => typeof e === "string");
|
|
5041
|
+
return [];
|
|
5042
|
+
}
|
|
5043
|
+
/** Most-significant status across a requirement's results[] (lossless). */
|
|
5044
|
+
function worstOfResults(req) {
|
|
5045
|
+
const results = asArr(req.results) ?? [];
|
|
5046
|
+
const precedence = [
|
|
5047
|
+
"failed",
|
|
5048
|
+
"error",
|
|
5049
|
+
"passed",
|
|
5050
|
+
"notReviewed",
|
|
5051
|
+
"notApplicable"
|
|
5052
|
+
];
|
|
5053
|
+
const present = /* @__PURE__ */ new Set();
|
|
5054
|
+
for (const rRaw of results) {
|
|
5055
|
+
const r = asMap(rRaw);
|
|
5056
|
+
if (r) present.add(getStr(r, "status"));
|
|
4696
5057
|
}
|
|
4697
|
-
return
|
|
5058
|
+
for (const s of precedence) if (present.has(s)) return s;
|
|
5059
|
+
return "notReviewed";
|
|
4698
5060
|
}
|
|
4699
5061
|
/**
|
|
4700
|
-
*
|
|
4701
|
-
*
|
|
5062
|
+
* Whether an HDF Result_Status is a failing verdict. Only 'failed' fails;
|
|
5063
|
+
* 'error' is indeterminate (not a compliance failure) and every other value is
|
|
5064
|
+
* non-failing. Single shared definition of "failing" for the suppression axis
|
|
5065
|
+
* and the per-exporter outcome maps.
|
|
4702
5066
|
*/
|
|
4703
|
-
function
|
|
4704
|
-
return
|
|
5067
|
+
function isFailing(status) {
|
|
5068
|
+
return status === "failed";
|
|
4705
5069
|
}
|
|
4706
|
-
/**
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
5070
|
+
/** Resolve the status context for a requirement. */
|
|
5071
|
+
function statusOf(req) {
|
|
5072
|
+
const raw = worstOfResults(req);
|
|
5073
|
+
const effective = getStr(req, "effectiveStatus");
|
|
5074
|
+
const overrides = asArr(req.statusOverrides);
|
|
5075
|
+
const rollup = effective !== "" ? effective : raw;
|
|
5076
|
+
return {
|
|
5077
|
+
raw,
|
|
5078
|
+
effective,
|
|
5079
|
+
rollup,
|
|
5080
|
+
overridden: overrides !== void 0 && overrides.length > 0 || "effectiveStatus" in req,
|
|
5081
|
+
suppressed: isFailing(raw) && !isFailing(rollup)
|
|
5082
|
+
};
|
|
5083
|
+
}
|
|
5084
|
+
function firstComponent(doc) {
|
|
5085
|
+
const comps = asArr(doc.components);
|
|
5086
|
+
if (!comps || comps.length === 0) return void 0;
|
|
5087
|
+
return asMap(comps[0]);
|
|
5088
|
+
}
|
|
5089
|
+
function firstResultStartTime(req, fallback) {
|
|
5090
|
+
const results = asArr(req.results) ?? [];
|
|
5091
|
+
if (results.length > 0) {
|
|
5092
|
+
const st = getStr(asMap(results[0]), "startTime");
|
|
5093
|
+
if (st !== "") return st;
|
|
4722
5094
|
}
|
|
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);
|
|
5095
|
+
return fallback;
|
|
5096
|
+
}
|
|
5097
|
+
function defaultDescription(req) {
|
|
5098
|
+
const descs = asArr(req.descriptions) ?? [];
|
|
5099
|
+
for (const dRaw of descs) {
|
|
5100
|
+
const d = asMap(dRaw);
|
|
5101
|
+
if (d && getStr(d, "label") === "default") return getStr(d, "data");
|
|
4775
5102
|
}
|
|
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
|
-
});
|
|
5103
|
+
return "";
|
|
4803
5104
|
}
|
|
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 });
|
|
5105
|
+
function firstRefURL(req) {
|
|
5106
|
+
const refs = asArr(req.refs) ?? [];
|
|
5107
|
+
for (const rRaw of refs) {
|
|
5108
|
+
const url = getStr(asMap(rRaw), "url");
|
|
5109
|
+
if (url !== "") return url;
|
|
5110
|
+
}
|
|
5111
|
+
return "";
|
|
5112
|
+
}
|
|
5113
|
+
/** Deterministic event id: component | baseline | control. */
|
|
5114
|
+
function eventID(component, baselineName, controlID) {
|
|
5115
|
+
let comp = "";
|
|
5116
|
+
if (component) comp = getStr(component, "componentId") || getStr(component, "name");
|
|
5117
|
+
return [
|
|
5118
|
+
comp,
|
|
5119
|
+
baselineName,
|
|
5120
|
+
controlID
|
|
5121
|
+
].join("|");
|
|
4827
5122
|
}
|
|
4828
5123
|
/**
|
|
4829
|
-
*
|
|
5124
|
+
* Build the lossless hdf.* namespace shared by the export converters: promoted
|
|
5125
|
+
* snake_case scalars plus the full requirement sub-objects preserved verbatim.
|
|
5126
|
+
* `status` is the lossless results roll-up (statusOf().raw); `suppressed` is the
|
|
5127
|
+
* acceptance axis (statusOf().suppressed).
|
|
4830
5128
|
*/
|
|
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
|
|
5129
|
+
function buildHDFBlock(req, baseline, status, overridden, suppressed, generator, tool, converterVersion) {
|
|
5130
|
+
const hdf = {
|
|
5131
|
+
status,
|
|
5132
|
+
overridden,
|
|
5133
|
+
suppressed,
|
|
5134
|
+
exporter_version: converterVersion
|
|
4867
5135
|
};
|
|
5136
|
+
setIf(hdf, "control_id", getStr(req, "id"));
|
|
5137
|
+
setIf(hdf, "baseline", getStr(baseline, "name"));
|
|
5138
|
+
if ("effectiveStatus" in req) hdf.effective_status = req.effectiveStatus;
|
|
5139
|
+
if ("effectiveImpact" in req) hdf.effective_impact = req.effectiveImpact;
|
|
5140
|
+
if ("impact" in req) hdf.impact = req.impact;
|
|
5141
|
+
if ("severity" in req) hdf.severity = req.severity;
|
|
5142
|
+
if ("disposition" in req) hdf.disposition = req.disposition;
|
|
5143
|
+
const tags = asMap(req.tags);
|
|
5144
|
+
if (tags?.nist !== void 0) hdf.nist = tags.nist;
|
|
5145
|
+
if (tags?.cci !== void 0) hdf.cci = tags.cci;
|
|
5146
|
+
for (const [src, dst] of Object.entries({
|
|
5147
|
+
tags: "tags",
|
|
5148
|
+
cvss: "cvss",
|
|
5149
|
+
cwe: "cwe",
|
|
5150
|
+
epss: "epss",
|
|
5151
|
+
kev: "kev",
|
|
5152
|
+
affectedPackages: "affected_packages",
|
|
5153
|
+
descriptions: "descriptions",
|
|
5154
|
+
results: "results",
|
|
5155
|
+
statusOverrides: "status_overrides",
|
|
5156
|
+
poams: "poams",
|
|
5157
|
+
code: "code",
|
|
5158
|
+
refs: "refs"
|
|
5159
|
+
})) if (src in req) hdf[dst] = req[src];
|
|
5160
|
+
if (generator) hdf.generator = generator;
|
|
5161
|
+
if (tool) hdf.tool = tool;
|
|
5162
|
+
return hdf;
|
|
4868
5163
|
}
|
|
4869
5164
|
/**
|
|
4870
|
-
*
|
|
5165
|
+
* A numeric value that must serialize with an explicit decimal point, so a
|
|
5166
|
+
* whole-number renders as `10.0` rather than the integer `10`. Some consumers
|
|
5167
|
+
* type-check strictly (OCSF's `float_t` rejects an integer-shaped token).
|
|
5168
|
+
* JSON.stringify cannot emit `10.0` for a JS number, so the value is carried in
|
|
5169
|
+
* this wrapper (passed through canonicalize untouched) and rendered as a bare
|
|
5170
|
+
* numeric token by stringifyLine. Go's counterpart is encoding/json's json.Number.
|
|
4871
5171
|
*/
|
|
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
|
-
}
|
|
5172
|
+
var RawNumber = class {
|
|
5173
|
+
constructor(token) {
|
|
5174
|
+
this.token = token;
|
|
4879
5175
|
}
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
* Extract array values from tags object and join with semicolons
|
|
4887
|
-
*/
|
|
4888
|
-
function extractArrayFromTags(tags, key) {
|
|
4889
|
-
if (!tags || typeof tags !== "object") return "";
|
|
4890
|
-
const value = tags[key];
|
|
4891
|
-
if (Array.isArray(value)) return value.map((v) => String(v)).join("; ");
|
|
4892
|
-
return "";
|
|
5176
|
+
};
|
|
5177
|
+
/** Wrap a number as a RawNumber whose token always bears a decimal point. */
|
|
5178
|
+
function floatNumber(f) {
|
|
5179
|
+
let s = String(f);
|
|
5180
|
+
if (!/[.eE]/.test(s)) s += ".0";
|
|
5181
|
+
return new RawNumber(s);
|
|
4893
5182
|
}
|
|
5183
|
+
const RAWNUM_MARK = String.fromCharCode(1);
|
|
5184
|
+
const RAWNUM_RE = /"\\u0001(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\\u0001"/g;
|
|
4894
5185
|
/**
|
|
4895
|
-
*
|
|
5186
|
+
* Compare two strings by Unicode code point. This matches Go's `encoding/json`
|
|
5187
|
+
* key ordering, which is a bytewise comparison of the UTF-8 encoding \u2014 and UTF-8
|
|
5188
|
+
* byte order is identical to code-point order. JS's default Array.sort() instead
|
|
5189
|
+
* compares UTF-16 code units, which diverges for supplementary-plane (non-BMP)
|
|
5190
|
+
* characters: a lead surrogate (0xD800\u20130xDBFF) sorts before a BMP char in
|
|
5191
|
+
* [0xE000,0xFFFF] under UTF-16, but a non-BMP code point (\u22650x10000) sorts after
|
|
5192
|
+
* it under UTF-8. Iterating the string yields whole code points (surrogate pairs
|
|
5193
|
+
* combined), so this comparator is correct for the full Unicode range.
|
|
4896
5194
|
*/
|
|
4897
|
-
function
|
|
4898
|
-
|
|
5195
|
+
function byCodePoint(a, b) {
|
|
5196
|
+
const ai = [...a];
|
|
5197
|
+
const bi = [...b];
|
|
5198
|
+
const n = Math.min(ai.length, bi.length);
|
|
5199
|
+
for (let i = 0; i < n; i++) {
|
|
5200
|
+
const ca = ai[i]?.codePointAt(0) ?? 0;
|
|
5201
|
+
const cb = bi[i]?.codePointAt(0) ?? 0;
|
|
5202
|
+
if (ca !== cb) return ca - cb;
|
|
5203
|
+
}
|
|
5204
|
+
return ai.length - bi.length;
|
|
4899
5205
|
}
|
|
4900
5206
|
/**
|
|
4901
|
-
*
|
|
4902
|
-
*
|
|
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.
|
|
5207
|
+
* Recursively sort object keys in Go's map-key order (bytewise UTF-8 / code
|
|
5208
|
+
* point) so the emitted JSON is byte-identical to Go's encoder.
|
|
5114
5209
|
*/
|
|
5115
5210
|
function canonicalize$1(v) {
|
|
5116
5211
|
if (v instanceof RawNumber) return v;
|
|
@@ -5194,37 +5289,306 @@ function epochMillis(s) {
|
|
|
5194
5289
|
return d.getTime();
|
|
5195
5290
|
}
|
|
5196
5291
|
//#endregion
|
|
5197
|
-
//#region converters/
|
|
5292
|
+
//#region converters/cyclonedx-to-hdf/typescript/converter.ts
|
|
5293
|
+
const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
5294
|
+
"CVSSv2",
|
|
5295
|
+
"CVSSv3",
|
|
5296
|
+
"CVSSv31",
|
|
5297
|
+
"CVSSv4"
|
|
5298
|
+
]);
|
|
5198
5299
|
/**
|
|
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.
|
|
5300
|
+
* Computes the maximum impact across all ratings for a vulnerability.
|
|
5301
|
+
* Prefers CVSS score/10 when available, falls back to severityToImpact().
|
|
5216
5302
|
*/
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5303
|
+
function maxImpact(ratings) {
|
|
5304
|
+
if (ratings.length === 0) return .5;
|
|
5305
|
+
let max = 0;
|
|
5306
|
+
for (const rating of ratings) {
|
|
5307
|
+
let impact;
|
|
5308
|
+
if (rating.method && CVSS_METHODS.has(rating.method) && rating.score !== void 0 && rating.score !== null) impact = rating.score / 10;
|
|
5309
|
+
else impact = severityToImpact$1(rating.severity ?? "medium");
|
|
5310
|
+
if (impact > max) max = impact;
|
|
5311
|
+
}
|
|
5312
|
+
return max;
|
|
5220
5313
|
}
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5314
|
+
/**
|
|
5315
|
+
* Formats the ratings as a human-readable tag string.
|
|
5316
|
+
*/
|
|
5317
|
+
function formatRatingsTag(ratings) {
|
|
5318
|
+
return ratings.map((r) => `${r.source?.name ?? "Unknown"} - ${r.severity ?? "unrated"}`).join(", ");
|
|
5319
|
+
}
|
|
5320
|
+
/**
|
|
5321
|
+
* Formats a component reference as a code_desc string.
|
|
5322
|
+
*/
|
|
5323
|
+
function formatCodeDesc$4(componentLookup, ref) {
|
|
5324
|
+
const comp = componentLookup.get(ref);
|
|
5325
|
+
if (!comp) return `Component ${ref} is vulnerable`;
|
|
5326
|
+
let name = "";
|
|
5327
|
+
if (comp.group) name += comp.group + "/";
|
|
5328
|
+
name += comp.name;
|
|
5329
|
+
if (comp.version) name += "@" + comp.version;
|
|
5330
|
+
return `Component ${name} is vulnerable`;
|
|
5331
|
+
}
|
|
5332
|
+
/**
|
|
5333
|
+
* Flattens nested CycloneDX components into a single array.
|
|
5334
|
+
*/
|
|
5335
|
+
function flattenComponents(components) {
|
|
5336
|
+
const result = [];
|
|
5337
|
+
for (const comp of components) {
|
|
5338
|
+
result.push(comp);
|
|
5339
|
+
if (comp.components) result.push(...flattenComponents(comp.components));
|
|
5340
|
+
}
|
|
5341
|
+
return result;
|
|
5342
|
+
}
|
|
5343
|
+
/**
|
|
5344
|
+
* Reports whether any (possibly nested) component is a machine-learning-model,
|
|
5345
|
+
* i.e. the CycloneDX document is an AI-BOM.
|
|
5346
|
+
*/
|
|
5347
|
+
function hasMLModelComponent(components) {
|
|
5348
|
+
return flattenComponents(components).some((comp) => comp.type === "machine-learning-model");
|
|
5349
|
+
}
|
|
5350
|
+
/**
|
|
5351
|
+
* Converts CycloneDX SBOM/VEX JSON to HDF format.
|
|
5352
|
+
*
|
|
5353
|
+
* @param input - CycloneDX JSON string
|
|
5354
|
+
* @returns HDF JSON string
|
|
5355
|
+
*/
|
|
5356
|
+
async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
5357
|
+
if (!input || input.trim().length === 0) throw new Error("cyclonedx: empty input");
|
|
5358
|
+
validateInputSize(input, "cyclonedx");
|
|
5359
|
+
const bom = parseJSON(input);
|
|
5360
|
+
if (!bom || typeof bom !== "object") throw new Error("cyclonedx: invalid JSON");
|
|
5361
|
+
if (bom.bomFormat !== "CycloneDX") throw new Error(`cyclonedx: missing or invalid bomFormat (expected "CycloneDX", got "${bom.bomFormat ?? "undefined"}")`);
|
|
5362
|
+
if ((!bom.components || bom.components.length === 0) && (!bom.vulnerabilities || bom.vulnerabilities.length === 0)) throw new Error("cyclonedx: input has neither components nor vulnerabilities");
|
|
5363
|
+
if (!bom.vulnerabilities || bom.vulnerabilities.length === 0) {
|
|
5364
|
+
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");
|
|
5365
|
+
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>");
|
|
5366
|
+
}
|
|
5367
|
+
const resultsChecksum = await inputChecksum(input);
|
|
5368
|
+
const parsedTimestamp = bom.metadata?.timestamp ? parseTimestamp(bom.metadata.timestamp) ?? void 0 : void 0;
|
|
5369
|
+
const scanTime = parsedTimestamp && !isNaN(parsedTimestamp.getTime()) ? parsedTimestamp : /* @__PURE__ */ new Date();
|
|
5370
|
+
const allComponents = flattenComponents(bom.components ?? []);
|
|
5371
|
+
const componentLookup = /* @__PURE__ */ new Map();
|
|
5372
|
+
for (const comp of allComponents) if (comp["bom-ref"]) componentLookup.set(comp["bom-ref"], comp);
|
|
5373
|
+
const requirements = [];
|
|
5374
|
+
const { items: limitedVulns, truncated: truncatedVulns } = limitArray(bom.vulnerabilities ?? []);
|
|
5375
|
+
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
5376
|
+
if (truncatedVulns) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${(bom.vulnerabilities ?? []).length})`);
|
|
5377
|
+
for (const vuln of limitedVulns) {
|
|
5378
|
+
const ratings = vuln.ratings ?? [];
|
|
5379
|
+
const impact = maxImpact(ratings);
|
|
5380
|
+
const cwes = vuln.cwes ?? [];
|
|
5381
|
+
const nist = mapCWEToNIST(cwes.map((c) => `${c}`), DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
5382
|
+
const tags = {
|
|
5383
|
+
nist,
|
|
5384
|
+
cci: nistToCci(nist)
|
|
5385
|
+
};
|
|
5386
|
+
if (cwes.length > 0) tags["cweid"] = cwes.map((c) => `CWE-${c}`);
|
|
5387
|
+
if (ratings.length > 0) tags["ratings"] = formatRatingsTag(ratings);
|
|
5388
|
+
const descriptions = [];
|
|
5389
|
+
const defaultParts = [];
|
|
5390
|
+
if (vuln.description) defaultParts.push(`Description: ${vuln.description}`);
|
|
5391
|
+
if (vuln.detail) defaultParts.push(`Detail: ${vuln.detail}`);
|
|
5392
|
+
if (defaultParts.length > 0) descriptions.push({
|
|
5393
|
+
label: "default",
|
|
5394
|
+
data: defaultParts.join("\n\n")
|
|
5395
|
+
});
|
|
5396
|
+
else descriptions.push({
|
|
5397
|
+
label: "default",
|
|
5398
|
+
data: vuln.id
|
|
5399
|
+
});
|
|
5400
|
+
const fixParts = [];
|
|
5401
|
+
if (vuln.recommendation) fixParts.push(vuln.recommendation);
|
|
5402
|
+
if (vuln.analysis?.detail) fixParts.push(`Workaround: ${vuln.analysis.detail}`);
|
|
5403
|
+
if (fixParts.length > 0) descriptions.push({
|
|
5404
|
+
label: "fix",
|
|
5405
|
+
data: fixParts.join("\n\n")
|
|
5406
|
+
});
|
|
5407
|
+
const affects = vuln.affects ?? [];
|
|
5408
|
+
const toResult = (codeDesc) => createResult(ResultStatus.Failed, void 0, {
|
|
5409
|
+
codeDesc,
|
|
5410
|
+
startTime: scanTime
|
|
5411
|
+
});
|
|
5412
|
+
const results = affects.length > 0 ? affects.map((affect) => toResult(formatCodeDesc$4(componentLookup, affect.ref))) : [toResult(`Vulnerability ${vuln.id}`)];
|
|
5413
|
+
const title = vuln.source?.name ? `${vuln.id} (${vuln.source.name})` : vuln.id;
|
|
5414
|
+
const req = createRequirement(vuln.id, title, descriptions, impact, results, { tags });
|
|
5415
|
+
const controlType = deriveControlTypeFromTags(nist);
|
|
5416
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
5417
|
+
requirements.push(req);
|
|
5418
|
+
}
|
|
5419
|
+
const baseline = createMinimalBaseline("CycloneDX Scan", requirements, { resultsChecksum });
|
|
5420
|
+
const targetComponent = {
|
|
5421
|
+
name: bom.metadata?.component?.name ?? "",
|
|
5422
|
+
type: TargetType.Application
|
|
5423
|
+
};
|
|
5424
|
+
if (bom.metadata?.component?.version) targetComponent.version = bom.metadata.component.version;
|
|
5425
|
+
const parsedBom = parseBom(input);
|
|
5426
|
+
const bomParts = {
|
|
5427
|
+
bomType: BOMType.Sbom,
|
|
5428
|
+
format: "cyclonedx",
|
|
5429
|
+
uniqueId: parsedBom.normalized.uniqueId,
|
|
5430
|
+
document: canonicalize$1(JSON.parse(input))
|
|
5431
|
+
};
|
|
5432
|
+
if (parsedBom.normalized.packages && parsedBom.normalized.packages.length > 0) bomParts.packages = parsedBom.normalized.packages;
|
|
5433
|
+
const componentBom = buildBom(bomParts);
|
|
5434
|
+
return buildHdfResults({
|
|
5435
|
+
generatorName: "cyclonedx-to-hdf",
|
|
5436
|
+
converterVersion,
|
|
5437
|
+
toolName: "CycloneDX",
|
|
5438
|
+
toolFormat: "JSON",
|
|
5439
|
+
baselines: [baseline],
|
|
5440
|
+
components: [{
|
|
5441
|
+
...targetComponent,
|
|
5442
|
+
boms: [componentBom]
|
|
5443
|
+
}],
|
|
5444
|
+
timestamp: scanTime
|
|
5445
|
+
});
|
|
5446
|
+
}
|
|
5447
|
+
//#endregion
|
|
5448
|
+
//#region converters/hdf-to-csv/typescript/converter.ts
|
|
5449
|
+
/**
|
|
5450
|
+
* Convert HDF JSON to CSV format
|
|
5451
|
+
* @param input HDF JSON string
|
|
5452
|
+
* @returns CSV string with sanitized output
|
|
5453
|
+
*/
|
|
5454
|
+
function convertHdfToCsv(input) {
|
|
5455
|
+
validateInputSize(input, "hdf-to-csv");
|
|
5456
|
+
const hdf = parseHdf(input);
|
|
5457
|
+
if (!hdf || typeof hdf !== "object" || !("baselines" in hdf)) throw new Error("Invalid HDF structure: missing baselines field");
|
|
5458
|
+
if (!Array.isArray(hdf.baselines)) throw new Error("Invalid HDF structure: baselines must be an array");
|
|
5459
|
+
const rows = [];
|
|
5460
|
+
const components = hdf.components || [];
|
|
5461
|
+
const targetList = components.length > 0 ? components.map((t) => ({
|
|
5462
|
+
name: t.name,
|
|
5463
|
+
type: t.type
|
|
5464
|
+
})) : [{
|
|
5465
|
+
name: "",
|
|
5466
|
+
type: ""
|
|
5467
|
+
}];
|
|
5468
|
+
for (const baseline of hdf.baselines) for (const requirement of baseline.requirements) for (const target of targetList) rows.push(createRow(baseline, requirement, target));
|
|
5469
|
+
return buildCsv(rows, { sanitize: true });
|
|
5470
|
+
}
|
|
5471
|
+
/**
|
|
5472
|
+
* Create a CSV row from baseline, requirement, and target data
|
|
5473
|
+
*/
|
|
5474
|
+
function createRow(baseline, requirement, target) {
|
|
5475
|
+
const description = requirement.descriptions.find((d) => d.label === "default")?.data || "";
|
|
5476
|
+
const check = descriptionByLabel(requirement, "check");
|
|
5477
|
+
const fix = descriptionByLabel(requirement, "fix");
|
|
5478
|
+
const rationale = descriptionByLabel(requirement, "rationale");
|
|
5479
|
+
const code = requirement.code ?? "";
|
|
5480
|
+
const references = flattenRefs(requirement.refs);
|
|
5481
|
+
const severity = getSeverity(requirement);
|
|
5482
|
+
const firstResult = requirement.results[0];
|
|
5483
|
+
const status = firstResult?.status || "";
|
|
5484
|
+
const message = firstResult?.message || "";
|
|
5485
|
+
const nistControls = extractArrayFromTags(requirement.tags, "nist");
|
|
5486
|
+
const cciControls = extractArrayFromTags(requirement.tags, "cci");
|
|
5487
|
+
return {
|
|
5488
|
+
"Baseline ID": baseline.name,
|
|
5489
|
+
"Baseline Version": baseline.version || "",
|
|
5490
|
+
"Baseline Title": baseline.title || "",
|
|
5491
|
+
"Target ID": target.name,
|
|
5492
|
+
"Target Type": target.type,
|
|
5493
|
+
"Requirement ID": requirement.id,
|
|
5494
|
+
"Requirement Title": requirement.title || "",
|
|
5495
|
+
"Description": description,
|
|
5496
|
+
"Check": check,
|
|
5497
|
+
"Fix": fix,
|
|
5498
|
+
"Rationale": rationale,
|
|
5499
|
+
"Code": code,
|
|
5500
|
+
"References": references,
|
|
5501
|
+
"Severity": severity,
|
|
5502
|
+
"Impact": requirement.impact,
|
|
5503
|
+
"Status": String(status),
|
|
5504
|
+
"NIST Controls": nistControls,
|
|
5505
|
+
"CCI Controls": cciControls,
|
|
5506
|
+
"Control Type": requirement.controlType ?? "",
|
|
5507
|
+
"Verification Method": requirement.verificationMethod ?? "",
|
|
5508
|
+
"Applicability": requirement.applicability ?? "",
|
|
5509
|
+
"Result Message": message
|
|
5510
|
+
};
|
|
5511
|
+
}
|
|
5512
|
+
/**
|
|
5513
|
+
* Get severity from requirement tags or derive from impact
|
|
5514
|
+
*/
|
|
5515
|
+
function getSeverity(requirement) {
|
|
5516
|
+
if (requirement.tags && typeof requirement.tags === "object") {
|
|
5517
|
+
if ("severity" in requirement.tags) {
|
|
5518
|
+
const sev = requirement.tags.severity;
|
|
5519
|
+
if (typeof sev === "string") return sev;
|
|
5520
|
+
if (Array.isArray(sev) && sev.length > 0) return String(sev[0]);
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
const impact = requirement.impact;
|
|
5524
|
+
if (impact >= .7) return "high";
|
|
5525
|
+
if (impact >= .4) return "medium";
|
|
5526
|
+
return "low";
|
|
5527
|
+
}
|
|
5528
|
+
/**
|
|
5529
|
+
* Extract array values from tags object and join with semicolons
|
|
5530
|
+
*/
|
|
5531
|
+
function extractArrayFromTags(tags, key) {
|
|
5532
|
+
if (!tags || typeof tags !== "object") return "";
|
|
5533
|
+
const value = tags[key];
|
|
5534
|
+
if (Array.isArray(value)) return value.map((v) => String(v)).join("; ");
|
|
5535
|
+
return "";
|
|
5536
|
+
}
|
|
5537
|
+
/**
|
|
5538
|
+
* Get the data of the first description matching a label. Empty when absent.
|
|
5539
|
+
*/
|
|
5540
|
+
function descriptionByLabel(requirement, label) {
|
|
5541
|
+
return requirement.descriptions.find((d) => d.label === label)?.data || "";
|
|
5542
|
+
}
|
|
5543
|
+
/**
|
|
5544
|
+
* Flatten a requirement's refs to one string: each Reference rendered as its
|
|
5545
|
+
* url/uri (or a string `ref`); array-form refs are skipped. Joined with '; ' to
|
|
5546
|
+
* match the NIST/CCI column convention.
|
|
5547
|
+
*/
|
|
5548
|
+
function flattenRefs(refs) {
|
|
5549
|
+
if (!Array.isArray(refs)) return "";
|
|
5550
|
+
const out = [];
|
|
5551
|
+
for (const r of refs) {
|
|
5552
|
+
if (!r || typeof r !== "object") continue;
|
|
5553
|
+
const o = r;
|
|
5554
|
+
if (typeof o.url === "string") out.push(o.url);
|
|
5555
|
+
else if (typeof o.uri === "string") out.push(o.uri);
|
|
5556
|
+
else if (typeof o.ref === "string") out.push(o.ref);
|
|
5557
|
+
}
|
|
5558
|
+
return out.join("; ");
|
|
5559
|
+
}
|
|
5560
|
+
//#endregion
|
|
5561
|
+
//#region converters/hdf-to-ecs/typescript/converter.ts
|
|
5562
|
+
/**
|
|
5563
|
+
* HDF Results -> Elastic Common Schema (ECS) NDJSON exporter.
|
|
5564
|
+
*
|
|
5565
|
+
* One ECS event per Evaluated_Requirement, in the hybrid shape from ADR-0002:
|
|
5566
|
+
* a core-ECS-native projection plus a lossless hdf.* block, hot filter scalars
|
|
5567
|
+
* promoted flat. Output is plain NDJSON (LF-delimited, trailing newline), ECS
|
|
5568
|
+
* 9.4.0.
|
|
5569
|
+
*
|
|
5570
|
+
* Status is raw-primary: event.outcome carries the RAW verdict (a waived failure
|
|
5571
|
+
* is still failure), and hdf.suppressed is the separate acceptance axis. The
|
|
5572
|
+
* canonical "still actionable" query is
|
|
5573
|
+
* event.outcome:"failure" AND hdf.suppressed:false.
|
|
5574
|
+
*
|
|
5575
|
+
* Generic JSON access, the status roll-up, requirement/document field
|
|
5576
|
+
* extraction, and the canonical line serialization are shared with the other
|
|
5577
|
+
* export converters via ../../../shared/typescript/exportmap.js; only
|
|
5578
|
+
* ECS-specific event shaping lives here. Output is held byte-identical with the
|
|
5579
|
+
* Go implementation via key-sorted, HTML-unescaped compact JSON.
|
|
5580
|
+
*/
|
|
5581
|
+
const ECS_VERSION = "9.4.0";
|
|
5582
|
+
function convertHdfToEcs(input, converterVersion = "0.1.0") {
|
|
5583
|
+
return runExport(input, "hdf-to-ecs", (req, baseline, docTimestamp, tool, generator, component) => buildEvent(req, baseline, docTimestamp, tool, generator, component, converterVersion));
|
|
5584
|
+
}
|
|
5585
|
+
function buildEvent(req, baseline, docTimestamp, tool, generator, component, converterVersion) {
|
|
5586
|
+
const st = statusOf(req);
|
|
5587
|
+
const outcome = statusToOutcome(st.raw);
|
|
5588
|
+
const controlID = getStr(req, "id");
|
|
5589
|
+
const baselineName = getStr(baseline, "name");
|
|
5590
|
+
const title = getStr(req, "title");
|
|
5591
|
+
const cvssList = asArr(req.cvss);
|
|
5228
5592
|
const hasCVSS = cvssList !== void 0 && cvssList.length > 0;
|
|
5229
5593
|
const categories = ["configuration"];
|
|
5230
5594
|
if (hasCVSS) categories.push("vulnerability");
|
|
@@ -5383,7 +5747,7 @@ function buildHECEvent(req, baseline, docTimestamp, tool, generator, component,
|
|
|
5383
5747
|
const controlID = getStr(req, "id");
|
|
5384
5748
|
const signature = title !== "" ? title : controlID;
|
|
5385
5749
|
const dest = destHost(component);
|
|
5386
|
-
const sev = severity(req);
|
|
5750
|
+
const sev = severity$1(req);
|
|
5387
5751
|
const [cvss, hasCVSS] = maxCVSS(req);
|
|
5388
5752
|
const cve = firstCVE(asArr(req.cvss) ?? []);
|
|
5389
5753
|
const category = firstCWE(req);
|
|
@@ -5426,7 +5790,7 @@ function destHost(component) {
|
|
|
5426
5790
|
if (!component) return "";
|
|
5427
5791
|
return getStr(component, "fqdn") || getStr(component, "name") || getStr(component, "ipAddress");
|
|
5428
5792
|
}
|
|
5429
|
-
function severity(req) {
|
|
5793
|
+
function severity$1(req) {
|
|
5430
5794
|
if (typeof req.impact === "number") return impactToSeverity(req.impact);
|
|
5431
5795
|
return normalizeSeverity(getStr(req, "severity"));
|
|
5432
5796
|
}
|
|
@@ -5487,9 +5851,9 @@ const ACTIVITY_CREATE = 1;
|
|
|
5487
5851
|
const STATUS_NEW = 1;
|
|
5488
5852
|
const STATUS_SUPPRESSED = 3;
|
|
5489
5853
|
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));
|
|
5854
|
+
return runExport(input, "hdf-to-ocsf", (req, baseline, docTimestamp, tool, generator, component) => buildFinding$1(req, baseline, docTimestamp, tool, generator, component, converterVersion));
|
|
5491
5855
|
}
|
|
5492
|
-
function buildFinding(req, baseline, docTimestamp, tool, generator, component, converterVersion) {
|
|
5856
|
+
function buildFinding$1(req, baseline, docTimestamp, tool, generator, component, converterVersion) {
|
|
5493
5857
|
const st = statusOf(req);
|
|
5494
5858
|
const cvssList = asArr(req.cvss);
|
|
5495
5859
|
const hasCVSS = cvssList !== void 0 && cvssList.length > 0;
|
|
@@ -5697,6 +6061,174 @@ function buildVulnerabilities(cvssList, req) {
|
|
|
5697
6061
|
return [vuln];
|
|
5698
6062
|
}
|
|
5699
6063
|
//#endregion
|
|
6064
|
+
//#region converters/hdf-to-asff/typescript/converter.ts
|
|
6065
|
+
/**
|
|
6066
|
+
* HDF Results -> AWS Security Finding Format (ASFF), the reverse of
|
|
6067
|
+
* asff-to-hdf. Output is the standard {"Findings": [...]} envelope that
|
|
6068
|
+
* BatchImportFindings accepts and asff-to-hdf reads back.
|
|
6069
|
+
*
|
|
6070
|
+
* One finding per requirement (matching hdf-to-ocsf): a requirement's results
|
|
6071
|
+
* roll up to one Compliance.Status via the shared statusOf. The mapping is
|
|
6072
|
+
* deliberately LOSSY and standard-compliant — HDF structure ASFF cannot hold is
|
|
6073
|
+
* dropped, NOT crammed into Types[] (heimdall2's rejected Types-string
|
|
6074
|
+
* encoding). Provenance rides ProductFields (ASFF's official string map).
|
|
6075
|
+
*
|
|
6076
|
+
* ProductArn / AwsAccountId / Region are ASFF-required but absent from HDF: the
|
|
6077
|
+
* account is recovered from a cloudAccount component when present, otherwise a
|
|
6078
|
+
* placeholder is emitted; the push path overrides all three. Output is held
|
|
6079
|
+
* byte-identical with the Go implementation via canonicalize + stringifyLine.
|
|
6080
|
+
*/
|
|
6081
|
+
const ASFF_SCHEMA_VERSION = "2018-10-08";
|
|
6082
|
+
const PLACEHOLDER_REGION = "us-east-1";
|
|
6083
|
+
const PLACEHOLDER_ACCOUNT_ID = "000000000000";
|
|
6084
|
+
const DEFAULT_PARTITION = "aws";
|
|
6085
|
+
const MAX_TITLE = 256;
|
|
6086
|
+
const MAX_DESCRIPTION = 1024;
|
|
6087
|
+
const EPOCH_SENTINEL = "1970-01-01T00:00:00Z";
|
|
6088
|
+
function convertHdfToAsff(input, converterVersion = "0.1.0") {
|
|
6089
|
+
const name = "hdf-to-asff";
|
|
6090
|
+
validateInputSize(input, name);
|
|
6091
|
+
const doc = parseHdf(input);
|
|
6092
|
+
const baselines = asArr(doc.baselines);
|
|
6093
|
+
if (!baselines) throw new Error(`${name}: invalid HDF structure: missing baselines field`);
|
|
6094
|
+
const docTimestamp = getStr(doc, "timestamp");
|
|
6095
|
+
const component = firstComponent(doc);
|
|
6096
|
+
const accountID = recoverAccountID(doc);
|
|
6097
|
+
const productArn = `arn:aws:securityhub:${PLACEHOLDER_REGION}:${accountID}:product/${accountID}/default`;
|
|
6098
|
+
const findings = [];
|
|
6099
|
+
for (const bRaw of baselines) {
|
|
6100
|
+
const baseline = asMap(bRaw);
|
|
6101
|
+
if (!baseline) continue;
|
|
6102
|
+
const baselineName = getStr(baseline, "name");
|
|
6103
|
+
const reqs = asArr(baseline.requirements) ?? [];
|
|
6104
|
+
for (const rRaw of reqs) {
|
|
6105
|
+
const req = asMap(rRaw);
|
|
6106
|
+
if (!req) continue;
|
|
6107
|
+
findings.push(buildFinding(req, baselineName, docTimestamp, component, accountID, productArn, converterVersion));
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
return stringifyLine(canonicalize$1({ Findings: findings })) + "\n";
|
|
6111
|
+
}
|
|
6112
|
+
function recoverAccountID(doc) {
|
|
6113
|
+
for (const cRaw of asArr(doc.components) ?? []) {
|
|
6114
|
+
const c = asMap(cRaw);
|
|
6115
|
+
if (!c || getStr(c, "type") !== "cloudAccount") continue;
|
|
6116
|
+
const id = getStr(c, "accountId") || getStr(c, "name");
|
|
6117
|
+
if (id) return id;
|
|
6118
|
+
}
|
|
6119
|
+
return PLACEHOLDER_ACCOUNT_ID;
|
|
6120
|
+
}
|
|
6121
|
+
function buildFinding(req, baselineName, docTimestamp, component, accountID, productArn, converterVersion) {
|
|
6122
|
+
const controlID = getStr(req, "id");
|
|
6123
|
+
const st = statusOf(req);
|
|
6124
|
+
const title = getStr(req, "title") || controlID;
|
|
6125
|
+
const desc = defaultDescription(req) || title;
|
|
6126
|
+
const cvssList = asArr(req.cvss);
|
|
6127
|
+
const hasCVSS = cvssList !== void 0 && cvssList.length > 0;
|
|
6128
|
+
const ts = canonicalTime(firstResultStartTime(req, docTimestamp));
|
|
6129
|
+
const id = findingID(accountID, baselineName, controlID);
|
|
6130
|
+
const finding = {
|
|
6131
|
+
SchemaVersion: ASFF_SCHEMA_VERSION,
|
|
6132
|
+
Id: id,
|
|
6133
|
+
ProductArn: productArn,
|
|
6134
|
+
GeneratorId: controlID,
|
|
6135
|
+
AwsAccountId: accountID,
|
|
6136
|
+
CreatedAt: ts,
|
|
6137
|
+
UpdatedAt: ts,
|
|
6138
|
+
Title: truncate(title, MAX_TITLE),
|
|
6139
|
+
Description: truncate(desc, MAX_DESCRIPTION),
|
|
6140
|
+
Types: asffTypes(hasCVSS),
|
|
6141
|
+
Severity: severity(req),
|
|
6142
|
+
Resources: resources(component, id),
|
|
6143
|
+
RecordState: "ACTIVE",
|
|
6144
|
+
Compliance: { Status: complianceStatus(st.rollup) }
|
|
6145
|
+
};
|
|
6146
|
+
if (st.suppressed) finding.Workflow = { Status: "SUPPRESSED" };
|
|
6147
|
+
const pf = productFields(baselineName, controlID, converterVersion);
|
|
6148
|
+
if (Object.keys(pf).length > 0) finding.ProductFields = pf;
|
|
6149
|
+
const rem = remediation(req);
|
|
6150
|
+
if (rem) finding.Remediation = rem;
|
|
6151
|
+
return finding;
|
|
6152
|
+
}
|
|
6153
|
+
function findingID(accountID, baselineName, controlID) {
|
|
6154
|
+
return [
|
|
6155
|
+
accountID,
|
|
6156
|
+
PLACEHOLDER_REGION,
|
|
6157
|
+
baselineName,
|
|
6158
|
+
controlID
|
|
6159
|
+
].join("/");
|
|
6160
|
+
}
|
|
6161
|
+
function asffTypes(hasCVSS) {
|
|
6162
|
+
return hasCVSS ? ["Software and Configuration Checks/Vulnerabilities/CVE"] : ["Software and Configuration Checks"];
|
|
6163
|
+
}
|
|
6164
|
+
function complianceStatus(status) {
|
|
6165
|
+
switch (status) {
|
|
6166
|
+
case "passed": return "PASSED";
|
|
6167
|
+
case "failed": return "FAILED";
|
|
6168
|
+
case "notApplicable": return "NOT_AVAILABLE";
|
|
6169
|
+
default: return "WARNING";
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
function severity(req) {
|
|
6173
|
+
let label = "INFORMATIONAL";
|
|
6174
|
+
let normalized = 0;
|
|
6175
|
+
if (typeof req.impact === "number") {
|
|
6176
|
+
label = impactToSeverity(req.impact).toUpperCase();
|
|
6177
|
+
normalized = Math.trunc(req.impact * 100);
|
|
6178
|
+
}
|
|
6179
|
+
return {
|
|
6180
|
+
Label: label,
|
|
6181
|
+
Normalized: normalized
|
|
6182
|
+
};
|
|
6183
|
+
}
|
|
6184
|
+
function resources(component, id) {
|
|
6185
|
+
const res = {
|
|
6186
|
+
Type: "Other",
|
|
6187
|
+
Id: id,
|
|
6188
|
+
Partition: DEFAULT_PARTITION,
|
|
6189
|
+
Region: PLACEHOLDER_REGION
|
|
6190
|
+
};
|
|
6191
|
+
if (component) {
|
|
6192
|
+
const details = {};
|
|
6193
|
+
setIf(details, "Name", getStr(component, "name"));
|
|
6194
|
+
setIf(details, "Type", getStr(component, "type"));
|
|
6195
|
+
setIf(details, "IpAddress", getStr(component, "ipAddress"));
|
|
6196
|
+
setIf(details, "OsName", getStr(component, "osName"));
|
|
6197
|
+
if (Object.keys(details).length > 0) res.Details = { Other: details };
|
|
6198
|
+
}
|
|
6199
|
+
return [res];
|
|
6200
|
+
}
|
|
6201
|
+
function productFields(baselineName, controlID, converterVersion) {
|
|
6202
|
+
const pf = {};
|
|
6203
|
+
setIf(pf, "hdf/baseline", baselineName);
|
|
6204
|
+
setIf(pf, "hdf/control_id", controlID);
|
|
6205
|
+
setIf(pf, "hdf/exporter_version", converterVersion);
|
|
6206
|
+
return pf;
|
|
6207
|
+
}
|
|
6208
|
+
function remediation(req) {
|
|
6209
|
+
let fix = "";
|
|
6210
|
+
for (const dRaw of asArr(req.descriptions) ?? []) {
|
|
6211
|
+
const d = asMap(dRaw);
|
|
6212
|
+
if (d && getStr(d, "label") === "fix") {
|
|
6213
|
+
fix = getStr(d, "data");
|
|
6214
|
+
break;
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
const url = firstRefURL(req);
|
|
6218
|
+
if (fix === "" && url === "") return void 0;
|
|
6219
|
+
const rec = {};
|
|
6220
|
+
setIf(rec, "Text", truncate(fix, MAX_DESCRIPTION));
|
|
6221
|
+
setIf(rec, "Url", url);
|
|
6222
|
+
return { Recommendation: rec };
|
|
6223
|
+
}
|
|
6224
|
+
function canonicalTime(s) {
|
|
6225
|
+
return parseTimestamp(s) ? s : EPOCH_SENTINEL;
|
|
6226
|
+
}
|
|
6227
|
+
function truncate(s, max) {
|
|
6228
|
+
const r = [...s];
|
|
6229
|
+
return r.length <= max ? s : r.slice(0, max).join("");
|
|
6230
|
+
}
|
|
6231
|
+
//#endregion
|
|
5700
6232
|
//#region converters/splunk-to-hdf/typescript/converter.ts
|
|
5701
6233
|
/**
|
|
5702
6234
|
* Map a Splunk result status string to the HDF ResultStatus enum.
|
|
@@ -6562,11 +7094,10 @@ function buildRequirement$10(checkID, findings, hasStatus) {
|
|
|
6562
7094
|
data: formatDesc$1(rep)
|
|
6563
7095
|
}];
|
|
6564
7096
|
const results = findings.map((f) => {
|
|
6565
|
-
return {
|
|
6566
|
-
status: hasStatus ? getStatus$2(f["Result Status"] ?? "") : ResultStatus.Failed,
|
|
7097
|
+
return createResult(hasStatus ? getStatus$2(f["Result Status"] ?? "") : ResultStatus.Failed, void 0, {
|
|
6567
7098
|
codeDesc: f["Details"] ?? "",
|
|
6568
7099
|
startTime: parseDate(f["Date"] ?? "")
|
|
6569
|
-
};
|
|
7100
|
+
});
|
|
6570
7101
|
});
|
|
6571
7102
|
const req = createRequirement(checkID, rep["Check"] ?? "", descriptions, getImpact$6(rep["Risk DV"] ?? ""), results, { tags });
|
|
6572
7103
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
@@ -6832,11 +7363,10 @@ function buildRequirement$9(vuln, packageTypes, distro) {
|
|
|
6832
7363
|
data: vuln.description
|
|
6833
7364
|
}];
|
|
6834
7365
|
const startTime = (vuln.discoveredDate ? parseTimestamp(vuln.discoveredDate) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
6835
|
-
const results = [{
|
|
6836
|
-
status: ResultStatus.Failed,
|
|
7366
|
+
const results = [createResult(ResultStatus.Failed, void 0, {
|
|
6837
7367
|
codeDesc: formatCodeDesc$2(vuln),
|
|
6838
7368
|
startTime
|
|
6839
|
-
}];
|
|
7369
|
+
})];
|
|
6840
7370
|
const req = createRequirement(vuln.id, vuln.id, descriptions, twistlockSeverityToImpact(vuln.severity), results, { tags });
|
|
6841
7371
|
const controlType = deriveControlTypeFromTags(nist);
|
|
6842
7372
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -6963,11 +7493,10 @@ function buildRequirement$8(finding, timestamp) {
|
|
|
6963
7493
|
data: finding.vulnerability.recommendation
|
|
6964
7494
|
});
|
|
6965
7495
|
const codeDesc = finding.vulnerability.recommendation ?? "No recommendation available";
|
|
6966
|
-
const results = [{
|
|
6967
|
-
status: ResultStatus.Failed,
|
|
7496
|
+
const results = [createResult(ResultStatus.Failed, void 0, {
|
|
6968
7497
|
codeDesc,
|
|
6969
7498
|
startTime: (timestamp ? parseTimestamp(timestamp) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
6970
|
-
}];
|
|
7499
|
+
})];
|
|
6971
7500
|
const req = createRequirement(finding.matrix, getTitle$1(finding), descriptions, getImpact$5(finding.vulnerability.severity), results, { tags });
|
|
6972
7501
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
6973
7502
|
const controlType = deriveControlTypeFromTags(nist);
|
|
@@ -8098,8 +8627,7 @@ function buildRequirementFromResult(result, filename) {
|
|
|
8098
8627
|
const startTime = (startTimeStr ? parseTimestamp(startTimeStr) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
8099
8628
|
const score = result.result.score;
|
|
8100
8629
|
const status = determineStatus(score);
|
|
8101
|
-
const toResult = (codeDesc) => ({
|
|
8102
|
-
status,
|
|
8630
|
+
const toResult = (codeDesc) => createResult(status, void 0, {
|
|
8103
8631
|
codeDesc,
|
|
8104
8632
|
startTime
|
|
8105
8633
|
});
|
|
@@ -8344,8 +8872,7 @@ function buildCWERequirement(cat, impact, firstBuildDate) {
|
|
|
8344
8872
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
8345
8873
|
const results = cwes.flatMap((c) => {
|
|
8346
8874
|
const staticflaws = c.staticflaws;
|
|
8347
|
-
return ensureArray(staticflaws?.flaw).map((flaw) => ({
|
|
8348
|
-
status: ResultStatus.Failed,
|
|
8875
|
+
return ensureArray(staticflaws?.flaw).map((flaw) => createResult(ResultStatus.Failed, void 0, {
|
|
8349
8876
|
codeDesc: formatFlawCodeDesc(flaw),
|
|
8350
8877
|
startTime
|
|
8351
8878
|
}));
|
|
@@ -8407,8 +8934,7 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
|
|
|
8407
8934
|
if (cweID) extras.cwe = cweID;
|
|
8408
8935
|
const tags = buildNistCciTags(nist, cciTags, extras);
|
|
8409
8936
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
8410
|
-
const results = components.map((comp) => ({
|
|
8411
|
-
status: ResultStatus.Failed,
|
|
8937
|
+
const results = components.map((comp) => createResult(ResultStatus.Failed, void 0, {
|
|
8412
8938
|
codeDesc: formatSCACodeDesc(comp),
|
|
8413
8939
|
startTime
|
|
8414
8940
|
}));
|
|
@@ -9720,23 +10246,31 @@ function overrideToPOAMItem(override) {
|
|
|
9720
10246
|
const remediations = [];
|
|
9721
10247
|
if (override.milestones) for (const ms of override.milestones) {
|
|
9722
10248
|
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
10249
|
if (ms.status) msProps.push({
|
|
9731
10250
|
name: "milestone-status",
|
|
9732
10251
|
value: String(ms.status)
|
|
9733
10252
|
});
|
|
10253
|
+
let tasks;
|
|
10254
|
+
const d = ms.estimatedCompletion ? toDate(ms.estimatedCompletion) : void 0;
|
|
10255
|
+
if (d) {
|
|
10256
|
+
const eta = formatTimestampSeconds(d);
|
|
10257
|
+
tasks = [{
|
|
10258
|
+
uuid: crypto.randomUUID(),
|
|
10259
|
+
type: "milestone",
|
|
10260
|
+
title: ms.description,
|
|
10261
|
+
timing: { "within-date-range": {
|
|
10262
|
+
start: eta,
|
|
10263
|
+
end: eta
|
|
10264
|
+
} }
|
|
10265
|
+
}];
|
|
10266
|
+
}
|
|
9734
10267
|
remediations.push({
|
|
9735
10268
|
uuid: crypto.randomUUID(),
|
|
9736
10269
|
lifecycle: "planned",
|
|
9737
10270
|
title: ms.description,
|
|
9738
10271
|
description: ms.description,
|
|
9739
|
-
props: msProps.length > 0 ? msProps : void 0
|
|
10272
|
+
props: msProps.length > 0 ? msProps : void 0,
|
|
10273
|
+
tasks
|
|
9740
10274
|
});
|
|
9741
10275
|
}
|
|
9742
10276
|
let riskLog;
|
|
@@ -9748,12 +10282,14 @@ function overrideToPOAMItem(override) {
|
|
|
9748
10282
|
start: formatTimestampSeconds(expiresDate),
|
|
9749
10283
|
"status-change": riskStatus
|
|
9750
10284
|
}] };
|
|
10285
|
+
const deadline = expiresDate && expiresDate.getTime() > 0 ? formatTimestampSeconds(expiresDate) : void 0;
|
|
9751
10286
|
const risk = {
|
|
9752
10287
|
uuid: riskUUID,
|
|
9753
10288
|
title: override.requirementId,
|
|
9754
10289
|
description: override.reason,
|
|
9755
10290
|
statement: override.reason,
|
|
9756
10291
|
status: riskStatus,
|
|
10292
|
+
deadline,
|
|
9757
10293
|
props: riskProps,
|
|
9758
10294
|
remediations: remediations.length > 0 ? remediations : void 0,
|
|
9759
10295
|
"risk-log": riskLog
|
|
@@ -10152,7 +10688,7 @@ function supplierEvidence(sourceURI, description) {
|
|
|
10152
10688
|
* arrives; one year is a defensive default consistent with the
|
|
10153
10689
|
* no-permanent-amendment rule on Standalone_Override. */
|
|
10154
10690
|
const DEFAULT_EXPIRY_HORIZON_MS$2 = 365 * 24 * 60 * 60 * 1e3;
|
|
10155
|
-
async function convertOpenVexToHdf(input, converterVersion) {
|
|
10691
|
+
async function convertOpenVexToHdf(input, converterVersion = "1.0.0") {
|
|
10156
10692
|
validateInputSize(input, "openvex-to-hdf");
|
|
10157
10693
|
const doc = parseJSON(input);
|
|
10158
10694
|
const docTime = (doc.timestamp ? parseTimestamp(doc.timestamp) : null) ?? /* @__PURE__ */ new Date();
|
|
@@ -10244,7 +10780,7 @@ function truncateId(id) {
|
|
|
10244
10780
|
* Spec: https://docs.oasis-open.org/csaf/csaf/v2.0/csaf-v2.0.html
|
|
10245
10781
|
*/
|
|
10246
10782
|
const DEFAULT_EXPIRY_HORIZON_MS$1 = 365 * 24 * 60 * 60 * 1e3;
|
|
10247
|
-
async function convertCsafVexToHdf(input, converterVersion) {
|
|
10783
|
+
async function convertCsafVexToHdf(input, converterVersion = "1.0.0") {
|
|
10248
10784
|
validateInputSize(input, "csaf-vex-to-hdf");
|
|
10249
10785
|
const doc = parseJSON(input);
|
|
10250
10786
|
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 +10993,7 @@ function overlap(ids, scope) {
|
|
|
10457
10993
|
* Spec: https://cyclonedx.org/use-cases/#vulnerability-exploitability
|
|
10458
10994
|
*/
|
|
10459
10995
|
const DEFAULT_EXPIRY_HORIZON_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
10460
|
-
async function convertCyclonedxVexToHdf(input, converterVersion) {
|
|
10996
|
+
async function convertCyclonedxVexToHdf(input, converterVersion = "1.0.0") {
|
|
10461
10997
|
validateInputSize(input, "cyclonedx-vex-to-hdf");
|
|
10462
10998
|
const bom = parseJSON(input);
|
|
10463
10999
|
if (bom.bomFormat !== "CycloneDX") throw new Error(`cyclonedx-vex-to-hdf: bomFormat is ${JSON.stringify(bom.bomFormat)}; only 'CycloneDX' is supported`);
|
|
@@ -11217,245 +11753,91 @@ async function catalogToBaseline(catalog, rawInput) {
|
|
|
11217
11753
|
for (const ctrl of group.controls ?? []) {
|
|
11218
11754
|
const req = controlToBaselineRequirement(ctrl);
|
|
11219
11755
|
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);
|
|
11756
|
+
reqIDs.push(req.id);
|
|
11757
|
+
for (const enh of ctrl.controls ?? []) {
|
|
11758
|
+
const enhReq = controlToBaselineRequirement(enh);
|
|
11759
|
+
requirements.push(enhReq);
|
|
11760
|
+
reqIDs.push(enhReq.id);
|
|
11761
|
+
}
|
|
11762
|
+
}
|
|
11763
|
+
if (reqIDs.length > 0) groups.push({
|
|
11764
|
+
id: group.id ?? "",
|
|
11765
|
+
title: group.title,
|
|
11766
|
+
requirements: reqIDs
|
|
11767
|
+
});
|
|
11383
11768
|
}
|
|
11384
|
-
for (const ctrl of catalog.controls ?? [])
|
|
11385
|
-
const
|
|
11386
|
-
|
|
11769
|
+
for (const ctrl of catalog.controls ?? []) {
|
|
11770
|
+
const req = controlToBaselineRequirement(ctrl);
|
|
11771
|
+
requirements.push(req);
|
|
11772
|
+
for (const enh of ctrl.controls ?? []) {
|
|
11773
|
+
const enhReq = controlToBaselineRequirement(enh);
|
|
11774
|
+
requirements.push(enhReq);
|
|
11775
|
+
}
|
|
11387
11776
|
}
|
|
11388
|
-
return
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11777
|
+
return {
|
|
11778
|
+
name: catalogBaselineName(catalog),
|
|
11779
|
+
title: meta.title,
|
|
11780
|
+
version: meta.version,
|
|
11781
|
+
status: "loaded",
|
|
11782
|
+
integrity,
|
|
11783
|
+
requirements,
|
|
11784
|
+
groups,
|
|
11785
|
+
generator: {
|
|
11786
|
+
name: "oscal-catalog-to-hdf",
|
|
11787
|
+
version: "1.0.0"
|
|
11788
|
+
}
|
|
11398
11789
|
};
|
|
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
11790
|
}
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11791
|
+
/** Converts a single OSCAL Control to an HDF BaselineRequirement. */
|
|
11792
|
+
function controlToBaselineRequirement(ctrl) {
|
|
11793
|
+
const nistTag = controlIdToNistTag(ctrl.id);
|
|
11794
|
+
const descriptions = buildCatalogDescriptions(ctrl);
|
|
11795
|
+
const tags = buildCatalogTags(ctrl);
|
|
11796
|
+
const req = {
|
|
11797
|
+
id: nistTag,
|
|
11409
11798
|
title: ctrl.title,
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
parts: ctrl.parts,
|
|
11414
|
-
controls: []
|
|
11799
|
+
impact: .5,
|
|
11800
|
+
descriptions,
|
|
11801
|
+
tags
|
|
11415
11802
|
};
|
|
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
|
-
}
|
|
11803
|
+
const controlType = deriveControlTypeFromTags([nistTag]);
|
|
11804
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
11805
|
+
if (extractPropValue(ctrl.props, "CORE") === "true") req.applicability = Applicability.Required;
|
|
11806
|
+
return req;
|
|
11437
11807
|
}
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11808
|
+
/** Creates HDF Description entries from control parts. */
|
|
11809
|
+
function buildCatalogDescriptions(ctrl) {
|
|
11810
|
+
const descriptions = [];
|
|
11811
|
+
const statement = flattenPartsByName(ctrl.parts, "statement");
|
|
11812
|
+
descriptions.push({
|
|
11813
|
+
label: "default",
|
|
11814
|
+
data: statement || ""
|
|
11815
|
+
});
|
|
11816
|
+
const guidance = flattenPartsByName(ctrl.parts, "guidance");
|
|
11817
|
+
if (guidance) descriptions.push({
|
|
11818
|
+
label: "rationale",
|
|
11819
|
+
data: guidance
|
|
11820
|
+
});
|
|
11821
|
+
const check = flattenPartsByName(ctrl.parts, "assessment-objective");
|
|
11822
|
+
if (check) descriptions.push({
|
|
11823
|
+
label: "check",
|
|
11824
|
+
data: check
|
|
11825
|
+
});
|
|
11826
|
+
return descriptions;
|
|
11445
11827
|
}
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11828
|
+
/** Builds the tags map for an OSCAL catalog control. */
|
|
11829
|
+
function buildCatalogTags(ctrl) {
|
|
11830
|
+
const tags = {};
|
|
11831
|
+
tags["nist"] = [controlIdToNistTag(ctrl.id)];
|
|
11832
|
+
const label = extractPropValue(ctrl.props, "label");
|
|
11833
|
+
if (label !== void 0) tags["label"] = label;
|
|
11834
|
+
const sortId = extractPropValue(ctrl.props, "sort-id");
|
|
11835
|
+
if (sortId !== void 0) tags["sort-id"] = sortId;
|
|
11836
|
+
return tags;
|
|
11449
11837
|
}
|
|
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;
|
|
11838
|
+
/** Derives a baseline name from catalog metadata. */
|
|
11839
|
+
function catalogBaselineName(catalog) {
|
|
11840
|
+
return toKebabCase(catalog.metadata.title, "oscal-catalog");
|
|
11459
11841
|
}
|
|
11460
11842
|
//#endregion
|
|
11461
11843
|
//#region converters/oscal-to-hdf/typescript/converter-component.ts
|
|
@@ -11527,164 +11909,160 @@ function componentBaselineName(comp, compDef) {
|
|
|
11527
11909
|
return toKebabCase(comp.title || compDef.metadata.title, "oscal-component-definition");
|
|
11528
11910
|
}
|
|
11529
11911
|
//#endregion
|
|
11530
|
-
//#region converters/oscal-to-hdf/typescript/converter-
|
|
11912
|
+
//#region converters/oscal-to-hdf/typescript/converter-poam.ts
|
|
11531
11913
|
/**
|
|
11532
|
-
* OSCAL
|
|
11914
|
+
* OSCAL Plan of Action and Milestones (POA&M) to HDF Amendments converter.
|
|
11533
11915
|
*
|
|
11534
|
-
* Mirrors the Go implementation in converters/oscal-to-hdf/go/
|
|
11916
|
+
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_poam.go.
|
|
11535
11917
|
*/
|
|
11536
11918
|
/**
|
|
11537
|
-
* Converts an OSCAL
|
|
11919
|
+
* Converts an OSCAL POA&M document to HDF Amendments JSON.
|
|
11538
11920
|
*
|
|
11539
|
-
* @param input - Raw JSON string containing an OSCAL
|
|
11540
|
-
* @returns HDF
|
|
11921
|
+
* @param input - Raw JSON string containing an OSCAL POA&M
|
|
11922
|
+
* @returns HDF Amendments JSON string
|
|
11541
11923
|
*/
|
|
11542
|
-
async function
|
|
11543
|
-
validateInputSize(input, "oscal-
|
|
11924
|
+
async function convertOscalPoamToHdf(input) {
|
|
11925
|
+
validateInputSize(input, "oscal-poam");
|
|
11544
11926
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
11545
11927
|
const doc = parseJSON(input);
|
|
11546
|
-
if (!doc["
|
|
11547
|
-
const
|
|
11928
|
+
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')");
|
|
11929
|
+
const poam = doc["plan-of-action-and-milestones"];
|
|
11548
11930
|
const integrity = await inputIntegrity(input);
|
|
11549
|
-
const
|
|
11550
|
-
|
|
11931
|
+
const meta = extractMetadata(poam.metadata);
|
|
11932
|
+
const riskMap = buildRiskMap$1(poam.risks ?? []);
|
|
11933
|
+
const overrides = [];
|
|
11934
|
+
for (const item of poam["poam-items"]) overrides.push(poamItemToOverride(item, riskMap, poam));
|
|
11935
|
+
const systemRef = poam["import-ssp"]?.href || void 0;
|
|
11936
|
+
const appliedBy = extractAppliedBy(poam.metadata);
|
|
11937
|
+
return serializeHdf({
|
|
11938
|
+
name: toKebabCase(poam.metadata.title, "oscal-poam"),
|
|
11939
|
+
overrides,
|
|
11551
11940
|
integrity,
|
|
11552
|
-
|
|
11941
|
+
systemRef,
|
|
11942
|
+
version: meta.version,
|
|
11943
|
+
appliedBy,
|
|
11553
11944
|
generator: {
|
|
11554
|
-
name: "oscal-
|
|
11945
|
+
name: "oscal-poam-to-hdf",
|
|
11555
11946
|
version: "1.0.0"
|
|
11556
11947
|
}
|
|
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);
|
|
11948
|
+
});
|
|
11582
11949
|
}
|
|
11583
|
-
function
|
|
11584
|
-
const
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
return "oscal-ssp";
|
|
11950
|
+
function buildRiskMap$1(risks) {
|
|
11951
|
+
const m = /* @__PURE__ */ new Map();
|
|
11952
|
+
for (const risk of risks) m.set(risk.uuid, risk);
|
|
11953
|
+
return m;
|
|
11588
11954
|
}
|
|
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
|
-
}
|
|
11955
|
+
function poamItemToOverride(item, riskMap, poam) {
|
|
11956
|
+
const requirementId = extractRequirementIdFromPOAMItem(item, riskMap);
|
|
11957
|
+
return {
|
|
11958
|
+
type: OverrideType.Poam,
|
|
11959
|
+
requirementId,
|
|
11960
|
+
reason: poamItemReason(item),
|
|
11961
|
+
status: poamItemStatus(item, riskMap),
|
|
11962
|
+
appliedBy: poamItemAppliedBy(poam),
|
|
11963
|
+
appliedAt: poamItemAppliedAt(poam, requirementId),
|
|
11964
|
+
expiresAt: poamItemExpiresAt(item, riskMap, requirementId),
|
|
11965
|
+
milestones: extractMilestones(item, riskMap)
|
|
11966
|
+
};
|
|
11607
11967
|
}
|
|
11608
|
-
function
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11968
|
+
function extractRequirementIdFromPOAMItem(item, riskMap) {
|
|
11969
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
11970
|
+
const riskUuid = rr["risk-uuid"];
|
|
11971
|
+
if (riskUuid) {
|
|
11972
|
+
const risk = riskMap.get(riskUuid);
|
|
11973
|
+
if (risk) {
|
|
11974
|
+
const controlId = extractPropValue(risk.props, "impacted-control-id");
|
|
11975
|
+
if (controlId) return controlIdToNistTag(controlId);
|
|
11976
|
+
}
|
|
11977
|
+
}
|
|
11617
11978
|
}
|
|
11979
|
+
const poamId = extractPropValue(item.props, "POAM-ID");
|
|
11980
|
+
if (poamId) return poamId;
|
|
11981
|
+
if (item.title) return item.title;
|
|
11982
|
+
return "unknown";
|
|
11618
11983
|
}
|
|
11619
|
-
function
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
case "low": return 1;
|
|
11624
|
-
default: return 0;
|
|
11625
|
-
}
|
|
11984
|
+
function poamItemReason(item) {
|
|
11985
|
+
if (item.description) return item.description;
|
|
11986
|
+
if (item.title) return item.title;
|
|
11987
|
+
return "POA&M item";
|
|
11626
11988
|
}
|
|
11627
|
-
function
|
|
11628
|
-
const
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11989
|
+
function poamItemStatus(item, riskMap) {
|
|
11990
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
11991
|
+
const riskUuid = rr["risk-uuid"];
|
|
11992
|
+
if (riskUuid) {
|
|
11993
|
+
const risk = riskMap.get(riskUuid);
|
|
11994
|
+
if (risk) {
|
|
11995
|
+
const status = oscalStatusToHdf(risk.status);
|
|
11996
|
+
if (status === "passed") return "passed";
|
|
11997
|
+
if (status === "failed") return "failed";
|
|
11998
|
+
}
|
|
11999
|
+
}
|
|
11635
12000
|
}
|
|
12001
|
+
return "failed";
|
|
11636
12002
|
}
|
|
11637
|
-
function
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
12003
|
+
function poamItemAppliedBy(poam) {
|
|
12004
|
+
const rps = poam.metadata["responsible-parties"];
|
|
12005
|
+
if (rps) {
|
|
12006
|
+
for (const rp of rps) if (rp["role-id"] === "prepared-by" && rp["party-uuids"].length > 0) return {
|
|
12007
|
+
type: "simple",
|
|
12008
|
+
identifier: rp["party-uuids"][0]
|
|
12009
|
+
};
|
|
12010
|
+
if (rps.length > 0 && rps[0]["party-uuids"].length > 0) return {
|
|
12011
|
+
type: "simple",
|
|
12012
|
+
identifier: rps[0]["party-uuids"][0]
|
|
12013
|
+
};
|
|
11644
12014
|
}
|
|
12015
|
+
return {
|
|
12016
|
+
type: "system",
|
|
12017
|
+
identifier: "oscal-poam-converter"
|
|
12018
|
+
};
|
|
11645
12019
|
}
|
|
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;
|
|
12020
|
+
function poamItemAppliedAt(poam, requirementId) {
|
|
12021
|
+
const t = poam.metadata["last-modified"] && parseTimestamp(String(poam.metadata["last-modified"]));
|
|
12022
|
+
if (t) return t;
|
|
12023
|
+
throw new Error(`poam-item "${requirementId}": no usable metadata.last-modified for appliedAt`);
|
|
11655
12024
|
}
|
|
11656
|
-
function
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
12025
|
+
function poamItemExpiresAt(item, riskMap, requirementId) {
|
|
12026
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
12027
|
+
const riskUuid = rr["risk-uuid"];
|
|
12028
|
+
if (!riskUuid) continue;
|
|
12029
|
+
const risk = riskMap.get(riskUuid);
|
|
12030
|
+
if (!risk?.deadline) continue;
|
|
12031
|
+
const t = parseTimestamp(String(risk.deadline));
|
|
12032
|
+
if (t) return t;
|
|
11661
12033
|
}
|
|
11662
|
-
|
|
12034
|
+
throw new Error(`poam-item "${requirementId}": no related risk carries a usable deadline; a POA&M requires a time commitment`);
|
|
11663
12035
|
}
|
|
11664
|
-
function
|
|
11665
|
-
const
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
12036
|
+
function extractMilestones(item, riskMap) {
|
|
12037
|
+
const milestones = [];
|
|
12038
|
+
for (const rr of item["related-risks"] ?? []) {
|
|
12039
|
+
const riskUuid = rr["risk-uuid"];
|
|
12040
|
+
if (!riskUuid) continue;
|
|
12041
|
+
const risk = riskMap.get(riskUuid);
|
|
12042
|
+
if (!risk) continue;
|
|
12043
|
+
for (const rem of risk.remediations ?? []) {
|
|
12044
|
+
if (rem.lifecycle !== "planned") continue;
|
|
12045
|
+
for (const task of rem.tasks ?? []) {
|
|
12046
|
+
const end = task.timing?.["within-date-range"]?.end;
|
|
12047
|
+
const estimatedCompletion = end && parseTimestamp(String(end));
|
|
12048
|
+
if (!estimatedCompletion) continue;
|
|
12049
|
+
milestones.push({
|
|
12050
|
+
description: task.description ? `${task.title}: ${task.description}` : task.title,
|
|
12051
|
+
estimatedCompletion,
|
|
12052
|
+
status: "pending"
|
|
12053
|
+
});
|
|
12054
|
+
}
|
|
12055
|
+
}
|
|
11675
12056
|
}
|
|
11676
|
-
return
|
|
12057
|
+
return milestones;
|
|
11677
12058
|
}
|
|
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;
|
|
12059
|
+
function extractAppliedBy(meta) {
|
|
12060
|
+
const rps = meta["responsible-parties"];
|
|
12061
|
+
if (rps) {
|
|
12062
|
+
for (const rp of rps) if (rp["role-id"] === "prepared-by" && rp["party-uuids"].length > 0) return {
|
|
12063
|
+
type: "simple",
|
|
12064
|
+
identifier: rp["party-uuids"][0]
|
|
12065
|
+
};
|
|
11688
12066
|
}
|
|
11689
12067
|
}
|
|
11690
12068
|
//#endregion
|
|
@@ -11790,181 +12168,32 @@ function buildTargetSelector(ap) {
|
|
|
11790
12168
|
const selector = {};
|
|
11791
12169
|
for (const subject of subjects) {
|
|
11792
12170
|
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;
|
|
12171
|
+
const key = "subject-type";
|
|
12172
|
+
if (selector[key] !== void 0) selector[key] += "," + subject.type;
|
|
12173
|
+
else selector[key] = subject.type;
|
|
12174
|
+
}
|
|
12175
|
+
if (subject["include-all"] !== void 0) selector["include-" + subject.type] = "all";
|
|
11932
12176
|
}
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
function poamItemExpiresAt() {
|
|
11936
|
-
const d = /* @__PURE__ */ new Date();
|
|
11937
|
-
d.setFullYear(d.getFullYear() + 1);
|
|
11938
|
-
return d;
|
|
12177
|
+
if (Object.keys(selector).length === 0) return null;
|
|
12178
|
+
return selector;
|
|
11939
12179
|
}
|
|
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
|
-
}
|
|
12180
|
+
function determinePlanType(ap) {
|
|
12181
|
+
const aType = extractPropValue(ap.metadata.props, "assessment-type");
|
|
12182
|
+
if (aType) switch (aType.toLowerCase()) {
|
|
12183
|
+
case "automated": return PlanType.Automated;
|
|
12184
|
+
case "manual": return PlanType.Manual;
|
|
11957
12185
|
}
|
|
11958
|
-
return
|
|
12186
|
+
if (ap.tasks && ap.tasks.length > 0) return PlanType.Hybrid;
|
|
11959
12187
|
}
|
|
11960
|
-
function
|
|
11961
|
-
const
|
|
11962
|
-
if (
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
};
|
|
12188
|
+
function buildPlanDescription(ap) {
|
|
12189
|
+
const parts = [];
|
|
12190
|
+
if (ap.metadata.remarks) parts.push(ap.metadata.remarks);
|
|
12191
|
+
if (ap["terms-and-conditions"]?.parts && ap["terms-and-conditions"].parts.length > 0) {
|
|
12192
|
+
const terms = flattenParts(ap["terms-and-conditions"].parts);
|
|
12193
|
+
if (terms) parts.push("Terms and Conditions: " + terms);
|
|
11967
12194
|
}
|
|
12195
|
+
if (parts.length === 0) return void 0;
|
|
12196
|
+
return parts.join("\n\n");
|
|
11968
12197
|
}
|
|
11969
12198
|
//#endregion
|
|
11970
12199
|
//#region converters/oscal-to-hdf/typescript/converter-sar.ts
|
|
@@ -12075,99 +12304,417 @@ function extractControlIdFromFinding(f) {
|
|
|
12075
12304
|
if (idx > 0) controlId = controlId.slice(0, idx);
|
|
12076
12305
|
return controlId;
|
|
12077
12306
|
}
|
|
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;
|
|
12307
|
+
function mapFindingStatus(f) {
|
|
12308
|
+
const status = oscalStatusToHdf(f.target.status.state);
|
|
12309
|
+
if (status === "passed") return ResultStatus.Passed;
|
|
12310
|
+
if (status === "failed") return ResultStatus.Failed;
|
|
12311
|
+
return ResultStatus.NotReviewed;
|
|
12312
|
+
}
|
|
12313
|
+
function buildCodeDesc(f, obsMap) {
|
|
12314
|
+
const parts = [];
|
|
12315
|
+
for (const ref of f["related-observations"] ?? []) {
|
|
12316
|
+
const obsUuid = ref["observation-uuid"];
|
|
12317
|
+
if (!obsUuid) continue;
|
|
12318
|
+
const obs = obsMap.get(obsUuid);
|
|
12319
|
+
if (!obs) continue;
|
|
12320
|
+
if (obs.methods && obs.methods.length > 0) parts.push("Methods: " + obs.methods.join(", "));
|
|
12321
|
+
for (const subj of obs.subjects ?? []) {
|
|
12322
|
+
let subjDesc = subj.type;
|
|
12323
|
+
if (subj.title) subjDesc = subj.title + " (" + subj.type + ")";
|
|
12324
|
+
parts.push("Subject: " + subjDesc);
|
|
12325
|
+
}
|
|
12326
|
+
}
|
|
12327
|
+
if (parts.length === 0) return f.title;
|
|
12328
|
+
return parts.join("; ");
|
|
12329
|
+
}
|
|
12330
|
+
function buildRiskMessage(f, riskMap) {
|
|
12331
|
+
const messages = [];
|
|
12332
|
+
for (const ref of f["related-risks"] ?? []) {
|
|
12333
|
+
const riskUuid = ref["risk-uuid"];
|
|
12334
|
+
if (!riskUuid) continue;
|
|
12335
|
+
const risk = riskMap.get(riskUuid);
|
|
12336
|
+
if (!risk) continue;
|
|
12337
|
+
let msg = risk.title;
|
|
12338
|
+
if (risk.description) msg += ": " + risk.description;
|
|
12339
|
+
messages.push(msg);
|
|
12340
|
+
}
|
|
12341
|
+
return messages.join("\n");
|
|
12342
|
+
}
|
|
12343
|
+
function sarFindingsImpact(findings, riskMap) {
|
|
12344
|
+
let highestImpact = -1;
|
|
12345
|
+
for (const f of findings) for (const ref of f["related-risks"] ?? []) {
|
|
12346
|
+
const riskUuid = ref["risk-uuid"];
|
|
12347
|
+
if (!riskUuid) continue;
|
|
12348
|
+
const risk = riskMap.get(riskUuid);
|
|
12349
|
+
if (!risk) continue;
|
|
12350
|
+
const impact = extractRiskSeverity(risk.characterizations, -1);
|
|
12351
|
+
if (impact > highestImpact) highestImpact = impact;
|
|
12352
|
+
}
|
|
12353
|
+
if (highestImpact < 0) return .5;
|
|
12354
|
+
return highestImpact;
|
|
12355
|
+
}
|
|
12356
|
+
function sarBuildDescriptions(findings, obsMap) {
|
|
12357
|
+
const descriptions = [];
|
|
12358
|
+
const findingDescs = [];
|
|
12359
|
+
for (const f of findings) if (f.description) findingDescs.push(f.description);
|
|
12360
|
+
descriptions.push({
|
|
12361
|
+
label: "default",
|
|
12362
|
+
data: findingDescs.join("\n") || ""
|
|
12363
|
+
});
|
|
12364
|
+
const obsDescs = [];
|
|
12365
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12366
|
+
for (const f of findings) for (const ref of f["related-observations"] ?? []) {
|
|
12367
|
+
const obsUuid = ref["observation-uuid"];
|
|
12368
|
+
if (!obsUuid || seen.has(obsUuid)) continue;
|
|
12369
|
+
seen.add(obsUuid);
|
|
12370
|
+
const obs = obsMap.get(obsUuid);
|
|
12371
|
+
if (obs?.description) obsDescs.push(obs.description);
|
|
12372
|
+
}
|
|
12373
|
+
if (obsDescs.length > 0) descriptions.push({
|
|
12374
|
+
label: "rationale",
|
|
12375
|
+
data: obsDescs.join("\n")
|
|
12376
|
+
});
|
|
12377
|
+
return descriptions;
|
|
12378
|
+
}
|
|
12379
|
+
function buildObservationMap(observations) {
|
|
12380
|
+
const m = /* @__PURE__ */ new Map();
|
|
12381
|
+
for (const obs of observations) m.set(obs.uuid, obs);
|
|
12382
|
+
return m;
|
|
12383
|
+
}
|
|
12384
|
+
function buildRiskMap(risks) {
|
|
12385
|
+
const m = /* @__PURE__ */ new Map();
|
|
12386
|
+
for (const risk of risks) m.set(risk.uuid, risk);
|
|
12387
|
+
return m;
|
|
12388
|
+
}
|
|
12389
|
+
function parseResultStartTime(result) {
|
|
12390
|
+
if (result.start) {
|
|
12391
|
+
const t = parseTimestamp(String(result.start));
|
|
12392
|
+
if (t) return t;
|
|
12393
|
+
}
|
|
12394
|
+
return /* @__PURE__ */ new Date(0);
|
|
12395
|
+
}
|
|
12396
|
+
function sarBaselineName(result, sar) {
|
|
12397
|
+
return toKebabCase(result.title || sar.metadata.title, "oscal-assessment-results");
|
|
12398
|
+
}
|
|
12399
|
+
//#endregion
|
|
12400
|
+
//#region converters/oscal-to-hdf/typescript/converter-ssp.ts
|
|
12401
|
+
/**
|
|
12402
|
+
* OSCAL System Security Plan (SSP) to HDF System converter.
|
|
12403
|
+
*
|
|
12404
|
+
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_ssp.go.
|
|
12405
|
+
*/
|
|
12406
|
+
/**
|
|
12407
|
+
* Converts an OSCAL System Security Plan document to HDF System JSON.
|
|
12408
|
+
*
|
|
12409
|
+
* @param input - Raw JSON string containing an OSCAL SSP
|
|
12410
|
+
* @returns HDF System JSON string
|
|
12411
|
+
*/
|
|
12412
|
+
async function convertOscalSspToHdf(input) {
|
|
12413
|
+
validateInputSize(input, "oscal-ssp");
|
|
12414
|
+
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
12415
|
+
const doc = parseJSON(input);
|
|
12416
|
+
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')");
|
|
12417
|
+
const ssp = doc["system-security-plan"];
|
|
12418
|
+
const integrity = await inputIntegrity(input);
|
|
12419
|
+
const system = {
|
|
12420
|
+
name: sspSystemName(ssp),
|
|
12421
|
+
integrity,
|
|
12422
|
+
components: [],
|
|
12423
|
+
generator: {
|
|
12424
|
+
name: "oscal-ssp-to-hdf",
|
|
12425
|
+
version: "1.0.0"
|
|
12426
|
+
}
|
|
12427
|
+
};
|
|
12428
|
+
const sc = ssp["system-characteristics"];
|
|
12429
|
+
if (sc) {
|
|
12430
|
+
if (sc.description) {
|
|
12431
|
+
let desc = sc.description;
|
|
12432
|
+
if (sc["authorization-boundary"]?.description) desc += "\n\nAuthorization Boundary: " + sc["authorization-boundary"].description;
|
|
12433
|
+
system.description = desc;
|
|
12434
|
+
}
|
|
12435
|
+
const sil = sc["security-impact-level"];
|
|
12436
|
+
if (sil) system.categorizationLevel = sspCategorizationLevel(sil) ?? void 0;
|
|
12437
|
+
else if (sc["security-sensitivity-level"]) system.categorizationLevel = mapSensitivityToCategorizationLevel(sc["security-sensitivity-level"]) ?? void 0;
|
|
12438
|
+
if (sc.status) system.authorizationStatus = sspAuthorizationStatus(sc.status.state) ?? void 0;
|
|
12439
|
+
if (sc["authorization-boundary"]?.description) system.boundaryDescription = sc["authorization-boundary"].description;
|
|
12440
|
+
const systemIds = sc["system-ids"];
|
|
12441
|
+
if (systemIds && systemIds.length > 0) {
|
|
12442
|
+
system.identifier = systemIds[0].id;
|
|
12443
|
+
if (systemIds[0]["identifier-type"]) system.identifierScheme = systemIds[0]["identifier-type"];
|
|
12444
|
+
}
|
|
12445
|
+
}
|
|
12446
|
+
const meta = extractMetadata(ssp.metadata);
|
|
12447
|
+
if (meta.version) system.version = meta.version;
|
|
12448
|
+
const componentControls = buildComponentControlMap(ssp["control-implementation"]);
|
|
12449
|
+
const si = ssp["system-implementation"];
|
|
12450
|
+
if (si?.components) for (const comp of si.components) system.components.push(sspComponentToHDFComponent(comp, componentControls));
|
|
12451
|
+
return JSON.stringify(system, null, 2);
|
|
12452
|
+
}
|
|
12453
|
+
function sspSystemName(ssp) {
|
|
12454
|
+
const sc = ssp["system-characteristics"];
|
|
12455
|
+
if (sc?.["system-name"]) return sc["system-name"];
|
|
12456
|
+
if (ssp.metadata.title) return ssp.metadata.title;
|
|
12457
|
+
return "oscal-ssp";
|
|
12458
|
+
}
|
|
12459
|
+
function sspCategorizationLevel(sil) {
|
|
12460
|
+
const levels = [
|
|
12461
|
+
sil["security-objective-confidentiality"] ?? "",
|
|
12462
|
+
sil["security-objective-integrity"] ?? "",
|
|
12463
|
+
sil["security-objective-availability"] ?? ""
|
|
12464
|
+
];
|
|
12465
|
+
let highest = "";
|
|
12466
|
+
for (const l of levels) {
|
|
12467
|
+
const normalized = normalizeFIPSLevel(l);
|
|
12468
|
+
if (fipsLevelRank(normalized) > fipsLevelRank(highest)) highest = normalized;
|
|
12469
|
+
}
|
|
12470
|
+
if (!highest) return null;
|
|
12471
|
+
switch (highest) {
|
|
12472
|
+
case "high": return CategorizationLevel.High;
|
|
12473
|
+
case "moderate": return CategorizationLevel.Moderate;
|
|
12474
|
+
case "low": return CategorizationLevel.Low;
|
|
12475
|
+
default: return null;
|
|
12476
|
+
}
|
|
12477
|
+
}
|
|
12478
|
+
function normalizeFIPSLevel(level) {
|
|
12479
|
+
let lower = level.toLowerCase();
|
|
12480
|
+
lower = lower.replace(/^fips-199-/, "");
|
|
12481
|
+
switch (lower) {
|
|
12482
|
+
case "high": return "high";
|
|
12483
|
+
case "moderate":
|
|
12484
|
+
case "medium": return "moderate";
|
|
12485
|
+
case "low": return "low";
|
|
12486
|
+
default: return "";
|
|
12487
|
+
}
|
|
12488
|
+
}
|
|
12489
|
+
function fipsLevelRank(level) {
|
|
12490
|
+
switch (level) {
|
|
12491
|
+
case "high": return 3;
|
|
12492
|
+
case "moderate": return 2;
|
|
12493
|
+
case "low": return 1;
|
|
12494
|
+
default: return 0;
|
|
12495
|
+
}
|
|
12496
|
+
}
|
|
12497
|
+
function mapSensitivityToCategorizationLevel(level) {
|
|
12498
|
+
const normalized = normalizeFIPSLevel(level);
|
|
12499
|
+
if (!normalized) return null;
|
|
12500
|
+
switch (normalized) {
|
|
12501
|
+
case "high": return CategorizationLevel.High;
|
|
12502
|
+
case "moderate": return CategorizationLevel.Moderate;
|
|
12503
|
+
case "low": return CategorizationLevel.Low;
|
|
12504
|
+
default: return null;
|
|
12505
|
+
}
|
|
12506
|
+
}
|
|
12507
|
+
function sspAuthorizationStatus(state) {
|
|
12508
|
+
switch (state.toLowerCase()) {
|
|
12509
|
+
case "operational": return AuthorizationStatus.Authorized;
|
|
12510
|
+
case "under-development": return AuthorizationStatus.PendingAuthorization;
|
|
12511
|
+
case "disposition": return AuthorizationStatus.Revoked;
|
|
12512
|
+
case "other": return AuthorizationStatus.NotYetRequested;
|
|
12513
|
+
default: return null;
|
|
12514
|
+
}
|
|
12515
|
+
}
|
|
12516
|
+
function buildComponentControlMap(ci) {
|
|
12517
|
+
const result = /* @__PURE__ */ new Map();
|
|
12518
|
+
if (!ci) return result;
|
|
12519
|
+
for (const ir of ci["implemented-requirements"] ?? []) {
|
|
12520
|
+
const controlId = ir["control-id"];
|
|
12521
|
+
for (const bc of ir["by-components"] ?? []) addComponentControl(result, bc["component-uuid"], controlId);
|
|
12522
|
+
for (const stmt of ir.statements ?? []) for (const bc of stmt["by-components"] ?? []) addComponentControl(result, bc["component-uuid"], controlId);
|
|
12523
|
+
}
|
|
12524
|
+
return result;
|
|
12525
|
+
}
|
|
12526
|
+
function addComponentControl(m, compUUID, controlId) {
|
|
12527
|
+
let controls = m.get(compUUID);
|
|
12528
|
+
if (!controls) {
|
|
12529
|
+
controls = /* @__PURE__ */ new Set();
|
|
12530
|
+
m.set(compUUID, controls);
|
|
12531
|
+
}
|
|
12532
|
+
controls.add(controlId);
|
|
12083
12533
|
}
|
|
12084
|
-
function
|
|
12085
|
-
const
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12534
|
+
function sspComponentToHDFComponent(sc, componentControls) {
|
|
12535
|
+
const comp = {
|
|
12536
|
+
name: sc.title,
|
|
12537
|
+
type: mapOSCALComponentType(sc.type)
|
|
12538
|
+
};
|
|
12539
|
+
if (sc.description) comp.description = sc.description;
|
|
12540
|
+
const controls = componentControls.get(sc.uuid);
|
|
12541
|
+
if (controls && controls.size > 0) {
|
|
12542
|
+
const refs = [];
|
|
12543
|
+
for (const controlId of controls) refs.push(controlIdToNistTag(controlId));
|
|
12544
|
+
if (refs.length > 0) {
|
|
12545
|
+
refs.sort();
|
|
12546
|
+
comp.baselineRefs = refs;
|
|
12096
12547
|
}
|
|
12097
12548
|
}
|
|
12098
|
-
|
|
12099
|
-
return parts.join("; ");
|
|
12549
|
+
return comp;
|
|
12100
12550
|
}
|
|
12101
|
-
function
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12551
|
+
function mapOSCALComponentType(oscalType) {
|
|
12552
|
+
switch (oscalType.toLowerCase()) {
|
|
12553
|
+
case "software":
|
|
12554
|
+
case "this-system": return TargetType.Application;
|
|
12555
|
+
case "service": return TargetType.Application;
|
|
12556
|
+
case "hardware": return TargetType.Host;
|
|
12557
|
+
case "network": return TargetType.Network;
|
|
12558
|
+
case "database": return TargetType.Database;
|
|
12559
|
+
case "storage": return TargetType.Artifact;
|
|
12560
|
+
default: return TargetType.Application;
|
|
12111
12561
|
}
|
|
12112
|
-
return messages.join("\n");
|
|
12113
12562
|
}
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12563
|
+
//#endregion
|
|
12564
|
+
//#region converters/oscal-to-hdf/typescript/converter-profile.ts
|
|
12565
|
+
/**
|
|
12566
|
+
* OSCAL Profile to HDF Baseline converter.
|
|
12567
|
+
*
|
|
12568
|
+
* Mirrors the Go implementation in converters/oscal-to-hdf/go/converter_profile.go.
|
|
12569
|
+
*
|
|
12570
|
+
* This implements the "simple resolver" -- it handles:
|
|
12571
|
+
* - A single catalog import
|
|
12572
|
+
* - include-controls with with-ids filtering
|
|
12573
|
+
* - set-parameters from modify section
|
|
12574
|
+
* - merge as-is (preserve catalog structure)
|
|
12575
|
+
*
|
|
12576
|
+
* It does NOT handle:
|
|
12577
|
+
* - Multiple imports
|
|
12578
|
+
* - Nested profile imports
|
|
12579
|
+
* - alter directives
|
|
12580
|
+
* - Complex merge strategies
|
|
12581
|
+
*/
|
|
12582
|
+
/**
|
|
12583
|
+
* Converts an OSCAL Profile against a provided catalog to HDF Baseline JSON.
|
|
12584
|
+
*
|
|
12585
|
+
* @param profileInput - Raw JSON string containing an OSCAL profile
|
|
12586
|
+
* @param catalogInput - Raw JSON string containing an OSCAL catalog
|
|
12587
|
+
* @returns HDF Baseline JSON string
|
|
12588
|
+
*/
|
|
12589
|
+
async function convertOscalProfileToHdf(profileInput, catalogInput) {
|
|
12590
|
+
validateInputSize(profileInput, "oscal-profile");
|
|
12591
|
+
if (!profileInput || profileInput.trim().length === 0) throw new Error("empty profile input");
|
|
12592
|
+
if (!catalogInput || catalogInput.trim().length === 0) throw new Error("empty catalog input");
|
|
12593
|
+
const profileDoc = parseJSON(profileInput);
|
|
12594
|
+
if (!profileDoc.profile) throw new Error("oscal-profile: input is not a profile document (root key is not 'profile')");
|
|
12595
|
+
const profile = profileDoc.profile;
|
|
12596
|
+
const catalogDoc = parseJSON(catalogInput);
|
|
12597
|
+
if (!catalogDoc.catalog) throw new Error("oscal-profile: catalog input is not a catalog document (root key is not 'catalog')");
|
|
12598
|
+
const catalog = catalogDoc.catalog;
|
|
12599
|
+
if (!profile.imports || profile.imports.length === 0) throw new Error("oscal-profile: profile has no imports");
|
|
12600
|
+
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`);
|
|
12601
|
+
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`);
|
|
12602
|
+
const imp = profile.imports[0];
|
|
12603
|
+
const resolvedCatalog = filterCatalog(catalog, collectIncludedIDs(imp), collectExcludedIDs(imp));
|
|
12604
|
+
if (profile.modify?.["set-parameters"]) applyParameterOverrides(resolvedCatalog, profile.modify["set-parameters"]);
|
|
12605
|
+
resolvedCatalog.metadata = profile.metadata;
|
|
12606
|
+
resolvedCatalog.uuid = profile.uuid;
|
|
12607
|
+
const baseline = await catalogToBaseline(resolvedCatalog, profileInput);
|
|
12608
|
+
baseline.integrity = await inputIntegrity(profileInput);
|
|
12609
|
+
return JSON.stringify(baseline, null, 2);
|
|
12610
|
+
}
|
|
12611
|
+
/** Extracts all control IDs from an import's include-controls. Returns null if include all. */
|
|
12612
|
+
function collectIncludedIDs(imp) {
|
|
12613
|
+
const includeControls = imp["include-controls"];
|
|
12614
|
+
if (!includeControls || includeControls.length === 0) return null;
|
|
12615
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12616
|
+
for (const ic of includeControls) for (const id of ic["with-ids"] ?? []) ids.add(id);
|
|
12617
|
+
return ids;
|
|
12618
|
+
}
|
|
12619
|
+
/** Extracts all control IDs from an import's exclude-controls. */
|
|
12620
|
+
function collectExcludedIDs(imp) {
|
|
12621
|
+
const excludeControls = imp["exclude-controls"];
|
|
12622
|
+
if (!excludeControls || excludeControls.length === 0) return null;
|
|
12623
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12624
|
+
for (const ec of excludeControls) for (const id of ec["with-ids"] ?? []) ids.add(id);
|
|
12625
|
+
return ids;
|
|
12626
|
+
}
|
|
12627
|
+
/** Creates a new catalog containing only the controls matching filters. */
|
|
12628
|
+
function filterCatalog(catalog, includedIDs, excludedIDs) {
|
|
12629
|
+
const includeAll = includedIDs === null;
|
|
12630
|
+
const result = {
|
|
12631
|
+
uuid: catalog.uuid,
|
|
12632
|
+
metadata: catalog.metadata,
|
|
12633
|
+
"back-matter": catalog["back-matter"],
|
|
12634
|
+
groups: [],
|
|
12635
|
+
controls: []
|
|
12636
|
+
};
|
|
12637
|
+
for (const group of catalog.groups ?? []) {
|
|
12638
|
+
const filtered = filterGroupControls(group, includedIDs, excludedIDs, includeAll);
|
|
12639
|
+
if (filtered.controls && filtered.controls.length > 0) result.groups.push(filtered);
|
|
12123
12640
|
}
|
|
12124
|
-
|
|
12125
|
-
|
|
12641
|
+
for (const ctrl of catalog.controls ?? []) if (shouldIncludeControl(ctrl.id, includedIDs, excludedIDs, includeAll)) {
|
|
12642
|
+
const filteredCtrl = filterControlEnhancements(ctrl, includedIDs, excludedIDs, includeAll);
|
|
12643
|
+
result.controls.push(filteredCtrl);
|
|
12644
|
+
}
|
|
12645
|
+
return result;
|
|
12126
12646
|
}
|
|
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);
|
|
12647
|
+
function filterGroupControls(group, includedIDs, excludedIDs, includeAll) {
|
|
12648
|
+
const filtered = {
|
|
12649
|
+
id: group.id,
|
|
12650
|
+
class: group.class,
|
|
12651
|
+
title: group.title,
|
|
12652
|
+
props: group.props,
|
|
12653
|
+
parts: group.parts,
|
|
12654
|
+
controls: []
|
|
12655
|
+
};
|
|
12656
|
+
for (const ctrl of group.controls ?? []) if (shouldIncludeControl(ctrl.id, includedIDs, excludedIDs, includeAll)) {
|
|
12657
|
+
const filteredCtrl = filterControlEnhancements(ctrl, includedIDs, excludedIDs, includeAll);
|
|
12658
|
+
filtered.controls.push(filteredCtrl);
|
|
12143
12659
|
}
|
|
12144
|
-
|
|
12145
|
-
label: "rationale",
|
|
12146
|
-
data: obsDescs.join("\n")
|
|
12147
|
-
});
|
|
12148
|
-
return descriptions;
|
|
12660
|
+
return filtered;
|
|
12149
12661
|
}
|
|
12150
|
-
function
|
|
12151
|
-
const
|
|
12152
|
-
|
|
12153
|
-
|
|
12662
|
+
function filterControlEnhancements(ctrl, includedIDs, excludedIDs, includeAll) {
|
|
12663
|
+
const result = {
|
|
12664
|
+
id: ctrl.id,
|
|
12665
|
+
class: ctrl.class,
|
|
12666
|
+
title: ctrl.title,
|
|
12667
|
+
params: ctrl.params,
|
|
12668
|
+
props: ctrl.props,
|
|
12669
|
+
links: ctrl.links,
|
|
12670
|
+
parts: ctrl.parts,
|
|
12671
|
+
controls: []
|
|
12672
|
+
};
|
|
12673
|
+
for (const enh of ctrl.controls ?? []) if (shouldIncludeControl(enh.id, includedIDs, excludedIDs, includeAll)) result.controls.push(enh);
|
|
12674
|
+
return result;
|
|
12154
12675
|
}
|
|
12155
|
-
function
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
return
|
|
12676
|
+
function shouldIncludeControl(id, includedIDs, excludedIDs, includeAll) {
|
|
12677
|
+
if (excludedIDs !== null && excludedIDs.has(id)) return false;
|
|
12678
|
+
if (includeAll) return true;
|
|
12679
|
+
return includedIDs !== null && includedIDs.has(id);
|
|
12159
12680
|
}
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12681
|
+
/** Applies set-parameters from the profile's modify section to the resolved catalog. */
|
|
12682
|
+
function applyParameterOverrides(catalog, setParams) {
|
|
12683
|
+
if (setParams.length === 0) return;
|
|
12684
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
12685
|
+
for (const sp of setParams) if (sp["param-id"]) overrides.set(sp["param-id"], sp.values ?? []);
|
|
12686
|
+
for (const group of catalog.groups ?? []) for (const ctrl of group.controls ?? []) {
|
|
12687
|
+
applyParamOverridesToControl(ctrl, overrides);
|
|
12688
|
+
for (const enh of ctrl.controls ?? []) applyParamOverridesToControl(enh, overrides);
|
|
12689
|
+
}
|
|
12690
|
+
for (const ctrl of catalog.controls ?? []) {
|
|
12691
|
+
applyParamOverridesToControl(ctrl, overrides);
|
|
12692
|
+
for (const enh of ctrl.controls ?? []) applyParamOverridesToControl(enh, overrides);
|
|
12164
12693
|
}
|
|
12165
|
-
return /* @__PURE__ */ new Date(0);
|
|
12166
12694
|
}
|
|
12167
|
-
function
|
|
12168
|
-
|
|
12695
|
+
function applyParamOverridesToControl(ctrl, overrides) {
|
|
12696
|
+
if (ctrl.params) for (const param of ctrl.params) {
|
|
12697
|
+
if (!param.id) continue;
|
|
12698
|
+
const values = overrides.get(param.id);
|
|
12699
|
+
if (values) param.label = values.join(", ");
|
|
12700
|
+
}
|
|
12701
|
+
if (ctrl.parts) for (const part of ctrl.parts) replaceParamInsertions(part, overrides);
|
|
12702
|
+
}
|
|
12703
|
+
function replaceParamInsertions(part, overrides) {
|
|
12704
|
+
if (part.prose) part.prose = substituteParams(part.prose, overrides);
|
|
12705
|
+
if (part.parts) for (const child of part.parts) replaceParamInsertions(child, overrides);
|
|
12706
|
+
}
|
|
12707
|
+
/** Replaces OSCAL parameter insertion patterns: {{ insert: param, <param-id> }} */
|
|
12708
|
+
function substituteParams(text, overrides) {
|
|
12709
|
+
let result = text;
|
|
12710
|
+
for (const [paramId, values] of overrides) {
|
|
12711
|
+
const placeholder = `{{ insert: param, ${paramId} }}`;
|
|
12712
|
+
const replacement = values.join(", ");
|
|
12713
|
+
result = result.split(placeholder).join(replacement);
|
|
12714
|
+
}
|
|
12715
|
+
return result;
|
|
12169
12716
|
}
|
|
12170
12717
|
//#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 };
|
|
12718
|
+
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
12719
|
|
|
12173
12720
|
//# sourceMappingURL=index.js.map
|