@mitre/hdf-converters 3.6.0-rc.2 → 3.6.0-rc.4
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 +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +887 -489
- package/dist/index.js.map +1 -1
- package/package.json +8 -9
package/dist/index.js
CHANGED
|
@@ -1,8 +1,150 @@
|
|
|
1
1
|
import { n as detectFormat, r as detectConverter, t as registerAllFingerprints } from "./register-all-CDfYvwQF.js";
|
|
2
2
|
import { flattenOverlays } from "@mitre/hdf-parsers";
|
|
3
|
-
import { buildCsv, buildXml, computeCvssScore, computeEffectiveStatus, cvssScoreToSeverity, formatTimestamp, formatTimestampSeconds, governingStatusOverride, impactToSeverity, isUnratedSeverity, normalizeHdfTimestamps, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, severityToImpact, severityToImpactWithAliases, sha256, stripHtml, stripHtml as stripHTML, trimUtcFraction, worstStatus } from "@mitre/hdf-utilities";
|
|
4
|
-
import { Applicability, AuthorizationStatus, CVSSSeverity, CategorizationLevel, ControlType, Ecosystem, EvidenceType, HashAlgorithm, IdentityType, Justification, MilestoneStatus, OverrideType, PlanType, ResultStatus, Severity, TargetType, VerificationMethodEnum, Version, createDescription, createMinimalBaseline, createRequirement, createResult, severityToImpact as severityToImpact$1 } from "@mitre/hdf-schema";
|
|
3
|
+
import { DEFAULT_MAX_INPUT_SIZE, buildCsv, buildXml, canonicalJson, computeCvssScore, computeEffectiveStatus, cvssScoreToSeverity, encodeBase64Utf8, formatJsonNumber, formatTimestamp, formatTimestampSeconds, governingStatusOverride, impactToSeverity, isUnratedSeverity, normalizeHdfTimestamps, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, severityToImpact, severityToImpactWithAliases, sha256, stripHtml, stripHtml as stripHTML, trimUtcFraction, validateInputSize, worstStatus, xmlSafeText } from "@mitre/hdf-utilities";
|
|
4
|
+
import { Applicability, AuthorizationStatus, CVSSSeverity, CategorizationLevel, ControlType, Ecosystem, EvidenceType, HashAlgorithm, IdentityType, Justification, MilestoneStatus, OverrideType, PlanType, ResultStatus, Severity, TargetType, VerificationMethodEnum, Version, affectedPackageSchema, amendmentsSchema, bomSchema, commonSchema, comparisonSchema, componentSchema, createDescription, createMinimalBaseline, createRequirement, createResult, cvssSchema, dataFlowSchema, epssSchema, eventsSchema, extensionsSchema, hdfAmendmentsSchema, hdfBaselineSchema, hdfComparisonSchema, hdfEvidencePackageSchema, hdfPlanSchema, hdfRequirementChangeEventSchema, hdfResultsSchema, hdfSystemSchema, kevSchema, parameterSchema, planSchema, platformSchema, resultSchema, runnerSchema, severityToImpact as severityToImpact$1, statisticsSchema, systemSchema, targetSchema } from "@mitre/hdf-schema";
|
|
5
5
|
import { DEFAULT_COMPONENT_MANAGEMENT_NIST_TAGS, DEFAULT_CONFIG_MANAGEMENT_NIST_TAGS, DEFAULT_REMEDIATION_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS as DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1, awsConfigMappedRevisions, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getAwsConfigNistControlsBySubstring, getCCINistMappings, getCurrentNistRevision, getCweNistControl, getHipcheckNistControls, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistToCci } from "@mitre/hdf-mappings";
|
|
6
|
+
import Ajv from "ajv";
|
|
7
|
+
import addFormats from "ajv-formats";
|
|
8
|
+
//#region ../hdf-validators/dist/index.js
|
|
9
|
+
/**
|
|
10
|
+
* Create and configure Ajv instance with all HDF schemas
|
|
11
|
+
*/
|
|
12
|
+
function createValidator() {
|
|
13
|
+
const ajv = new Ajv({
|
|
14
|
+
allErrors: true,
|
|
15
|
+
verbose: true,
|
|
16
|
+
strict: false,
|
|
17
|
+
validateFormats: true,
|
|
18
|
+
validateSchema: false
|
|
19
|
+
});
|
|
20
|
+
addFormats(ajv);
|
|
21
|
+
const registeredDateTime = ajv.formats["date-time"];
|
|
22
|
+
ajv.addFormat("date-time", {
|
|
23
|
+
type: "string",
|
|
24
|
+
validate: (v) => /^\d{4}-\d{2}-\d{2}[Tt]/.test(v) && registeredDateTime.validate(v)
|
|
25
|
+
});
|
|
26
|
+
ajv.addSchema(commonSchema);
|
|
27
|
+
ajv.addSchema(platformSchema);
|
|
28
|
+
ajv.addSchema(resultSchema);
|
|
29
|
+
ajv.addSchema(runnerSchema);
|
|
30
|
+
ajv.addSchema(statisticsSchema);
|
|
31
|
+
ajv.addSchema(targetSchema);
|
|
32
|
+
ajv.addSchema(parameterSchema);
|
|
33
|
+
ajv.addSchema(cvssSchema);
|
|
34
|
+
ajv.addSchema(epssSchema);
|
|
35
|
+
ajv.addSchema(kevSchema);
|
|
36
|
+
ajv.addSchema(affectedPackageSchema);
|
|
37
|
+
ajv.addSchema(bomSchema);
|
|
38
|
+
ajv.addSchema(amendmentsSchema);
|
|
39
|
+
ajv.addSchema(extensionsSchema);
|
|
40
|
+
ajv.addSchema(systemSchema);
|
|
41
|
+
ajv.addSchema(planSchema);
|
|
42
|
+
ajv.addSchema(comparisonSchema);
|
|
43
|
+
ajv.addSchema(eventsSchema);
|
|
44
|
+
ajv.addSchema(componentSchema);
|
|
45
|
+
ajv.addSchema(dataFlowSchema);
|
|
46
|
+
ajv.addSchema(hdfResultsSchema);
|
|
47
|
+
ajv.addSchema(hdfBaselineSchema);
|
|
48
|
+
ajv.addSchema(hdfComparisonSchema);
|
|
49
|
+
ajv.addSchema(hdfSystemSchema);
|
|
50
|
+
ajv.addSchema(hdfPlanSchema);
|
|
51
|
+
ajv.addSchema(hdfAmendmentsSchema);
|
|
52
|
+
ajv.addSchema(hdfRequirementChangeEventSchema);
|
|
53
|
+
ajv.addSchema(hdfEvidencePackageSchema);
|
|
54
|
+
return ajv;
|
|
55
|
+
}
|
|
56
|
+
const ajv = createValidator();
|
|
57
|
+
let resultsValidator = null;
|
|
58
|
+
function getResultsValidator() {
|
|
59
|
+
if (!resultsValidator) resultsValidator = ajv.compile(hdfResultsSchema);
|
|
60
|
+
return resultsValidator;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert Ajv errors to ValidationError format
|
|
64
|
+
*/
|
|
65
|
+
function formatErrors(errors) {
|
|
66
|
+
if (!errors || errors.length === 0) return [];
|
|
67
|
+
return errors.map((err) => {
|
|
68
|
+
let field = err.instancePath.replace(/^\//, "").replace(/\//g, ".");
|
|
69
|
+
if (!field && err.schemaPath) {
|
|
70
|
+
const pathParts = err.schemaPath.split("/");
|
|
71
|
+
field = pathParts[pathParts.length - 1] || "(root)";
|
|
72
|
+
}
|
|
73
|
+
let message = err.message || "validation failed";
|
|
74
|
+
if (err.params) {
|
|
75
|
+
if ("missingProperty" in err.params) {
|
|
76
|
+
field = field ? `${field}.${err.params.missingProperty}` : err.params.missingProperty;
|
|
77
|
+
message = "is required";
|
|
78
|
+
} else if ("additionalProperty" in err.params) {
|
|
79
|
+
field = field ? `${field}.${err.params.additionalProperty}` : err.params.additionalProperty;
|
|
80
|
+
message = "is not allowed";
|
|
81
|
+
} else if ("limit" in err.params) message = `${message} (limit: ${err.params.limit})`;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
field: field || "(root)",
|
|
85
|
+
keyword: err.keyword,
|
|
86
|
+
...err.keyword === "format" && err.params && "format" in err.params ? { format: String(err.params.format) } : {},
|
|
87
|
+
message,
|
|
88
|
+
value: err.data
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create ValidationResult from validator output
|
|
94
|
+
*/
|
|
95
|
+
function createResult$1(validator, data) {
|
|
96
|
+
const valid = validator(data);
|
|
97
|
+
const errors = formatErrors(validator.errors);
|
|
98
|
+
return {
|
|
99
|
+
valid: valid === true,
|
|
100
|
+
errors,
|
|
101
|
+
getErrorMessage() {
|
|
102
|
+
if (this.valid) return "";
|
|
103
|
+
return this.errors.map((e) => {
|
|
104
|
+
if (e.field === "(root)") return e.message;
|
|
105
|
+
return `${e.field}: ${e.message}`;
|
|
106
|
+
}).join("; ");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate HDF Results document against schema
|
|
112
|
+
*/
|
|
113
|
+
function validateResults(data) {
|
|
114
|
+
return createResult$1(getResultsValidator(), data);
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region shared/typescript/amendmentchain.ts
|
|
118
|
+
/**
|
|
119
|
+
* Stamps `previousChecksum` across amendment overrides in document order,
|
|
120
|
+
* linking each to the one before it and leaving the first unlinked.
|
|
121
|
+
*
|
|
122
|
+
* Every route that authors amendments calls this, so whether a document carries
|
|
123
|
+
* tamper-evidence does not depend on which one wrote it. The checksum is the
|
|
124
|
+
* canonical JSON form shared with the Go converters and with `hdf amend verify`,
|
|
125
|
+
* so a chain written here verifies everywhere.
|
|
126
|
+
*
|
|
127
|
+
* The override is put through serializeHdf first, so what gets hashed is the
|
|
128
|
+
* shape the document will actually carry. The TS converters hold
|
|
129
|
+
* `appliedAt`/`expiresAt` as `Date` objects, which stringify with a `.000`
|
|
130
|
+
* fraction that trimmed-UTC HDF output never carries; hashing them raw would
|
|
131
|
+
* make every TS-written chain disagree with its own serialized document, and
|
|
132
|
+
* with Go.
|
|
133
|
+
*
|
|
134
|
+
* Mutates the array in place, mirroring the Go helper.
|
|
135
|
+
*/
|
|
136
|
+
async function chainOverrides(overrides) {
|
|
137
|
+
let previous;
|
|
138
|
+
for (const override of overrides) {
|
|
139
|
+
if (previous === void 0) delete override.previousChecksum;
|
|
140
|
+
else override.previousChecksum = previous;
|
|
141
|
+
previous = {
|
|
142
|
+
algorithm: HashAlgorithm.Sha256,
|
|
143
|
+
value: await sha256(canonicalJson(JSON.parse(serializeHdf(override, 0))))
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
6
148
|
//#region shared/typescript/converterutil.ts
|
|
7
149
|
/**
|
|
8
150
|
* Shared utilities for TypeScript HDF converters.
|
|
@@ -145,7 +287,7 @@ function mapCWEToNIST(cweIDs, fallback) {
|
|
|
145
287
|
}
|
|
146
288
|
return controls.size > 0 ? [...controls].sort() : fallback;
|
|
147
289
|
}
|
|
148
|
-
/** Matches CWE identifiers like "CWE-79", "CWE 89", "cwe22". */
|
|
290
|
+
/** Matches CWE identifiers like "CWE-79", "CWE 89", "cwe22"; group 1 is the number. */
|
|
149
291
|
const CWE_PATTERN$1 = /CWE[- ]?(\d+)/gi;
|
|
150
292
|
/**
|
|
151
293
|
* Extract all numeric CWE IDs from text.
|
|
@@ -161,23 +303,44 @@ function extractCWEIDs(text) {
|
|
|
161
303
|
ids.sort();
|
|
162
304
|
return ids;
|
|
163
305
|
}
|
|
164
|
-
/** Default maximum input size for converters (50MB) */
|
|
165
|
-
const DEFAULT_MAX_INPUT_SIZE = 50 * 1024 * 1024;
|
|
166
306
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
|
|
176
|
-
|
|
307
|
+
* Return the first candidate whose content is not empty or whitespace-only,
|
|
308
|
+
* as-is (the emptiness test trims, the returned value does not). Returns '' when
|
|
309
|
+
* every candidate is empty or whitespace. The caller supplies its own documented
|
|
310
|
+
* fallback as the last candidate so the substituted value is meaningful per
|
|
311
|
+
* field — this helper never invents a placeholder like "N/A". Use it where a
|
|
312
|
+
* target schema requires a non-empty (often minLength:1) string and a source
|
|
313
|
+
* value may be absent; it is a selection helper, not a normalizer, and never
|
|
314
|
+
* decides to OMIT a field (that stays the caller's call). Go parity: FirstNonEmpty.
|
|
315
|
+
*/
|
|
316
|
+
function firstNonEmpty(...candidates) {
|
|
317
|
+
for (const c of candidates) if (c != null && c.trim() !== "") return c;
|
|
318
|
+
return "";
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The converter-facing face of the @mitre/hdf-utilities size guard: same
|
|
322
|
+
* limit, same UTF-8 byte-length measure as Go's ValidateJSONSize, with the
|
|
323
|
+
* converter name prefixed onto the error. A non-positive maxSize means "use
|
|
324
|
+
* the default" (an explicitly-passed 0 never rejects all non-empty input).
|
|
325
|
+
*/
|
|
326
|
+
function validateInputSize$1(input, converterName, maxSize = DEFAULT_MAX_INPUT_SIZE) {
|
|
327
|
+
const limit = maxSize > 0 ? maxSize : DEFAULT_MAX_INPUT_SIZE;
|
|
328
|
+
try {
|
|
329
|
+
validateInputSize(input, limit);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
throw new Error(`${converterName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** The schema's Severity enum keyed by its lowercase token. */
|
|
335
|
+
const HDF_SEVERITIES = new Map(Object.values(Severity).map((s) => [s, s]));
|
|
336
|
+
/**
|
|
337
|
+
* Map a raw severity token onto the schema's Severity enum, case-insensitively.
|
|
338
|
+
* The one place the enum vocabulary is spelled out for converters that must not
|
|
339
|
+
* cast an off-vocabulary source value into the typed field. Whitespace is not
|
|
340
|
+
* trimmed; callers own that normalization. Go parity: shared.ParseSeverity.
|
|
177
341
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
if (input.length > maxSize) throw new Error(`${converterName}: input exceeds maximum allowed size of ${maxSize} characters`);
|
|
342
|
+
function parseSeverity(s) {
|
|
343
|
+
return HDF_SEVERITIES.get(s.toLowerCase());
|
|
181
344
|
}
|
|
182
345
|
/**
|
|
183
346
|
* Parse an HDF document. The single ingest point for every HDF-consuming
|
|
@@ -202,6 +365,15 @@ function requireHdfResults(input, converterName) {
|
|
|
202
365
|
return requireHdfStructure(input, converterName, "baselines");
|
|
203
366
|
}
|
|
204
367
|
/**
|
|
368
|
+
* The HDF Amendments counterpart of requireHdfResults, keyed on the top-level
|
|
369
|
+
* `overrides` array instead of `baselines`.
|
|
370
|
+
*/
|
|
371
|
+
function requireHdfAmendments(input, converterName) {
|
|
372
|
+
const r = requireHdfStructure(input, converterName, "overrides");
|
|
373
|
+
if (r.items.length === 0) throw new Error(`${converterName}: invalid HDF structure: missing overrides field`);
|
|
374
|
+
return r;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
205
377
|
* Reject malformed HDF input (empty, oversized, non-JSON, top-level array/null,
|
|
206
378
|
* or a missing/wrong-typed structural key) before a converter zero-fills it into
|
|
207
379
|
* a typed document. A parsed JSON `null` (or array, or primitive) is NOT an HDF
|
|
@@ -210,12 +382,16 @@ function requireHdfResults(input, converterName) {
|
|
|
210
382
|
*/
|
|
211
383
|
function requireHdfStructure(input, converterName, field) {
|
|
212
384
|
if (input.length === 0) throw new Error(`${converterName}: empty input`);
|
|
213
|
-
validateInputSize(input, converterName);
|
|
385
|
+
validateInputSize$1(input, converterName);
|
|
214
386
|
const parsed = parseHdf(input);
|
|
215
387
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${converterName}: invalid HDF structure: missing ${field} field`);
|
|
216
388
|
const doc = parsed;
|
|
217
389
|
const items = doc[field];
|
|
218
390
|
if (!Array.isArray(items)) throw new Error(`${converterName}: invalid HDF structure: missing ${field} field`);
|
|
391
|
+
if (field === "baselines") {
|
|
392
|
+
const violations = typeViolations(validateResults(doc));
|
|
393
|
+
if (violations.length > 0) throw new Error(`${converterName}: input is not valid HDF: ${violations.slice(0, 5).join("; ")}`);
|
|
394
|
+
}
|
|
219
395
|
return {
|
|
220
396
|
doc,
|
|
221
397
|
items
|
|
@@ -298,7 +474,7 @@ function ecosystemFromPurlType(type) {
|
|
|
298
474
|
* - purl alone
|
|
299
475
|
* - cpe alone
|
|
300
476
|
*/
|
|
301
|
-
function buildAffectedPackage$
|
|
477
|
+
function buildAffectedPackage$1(opts) {
|
|
302
478
|
const pkg = {};
|
|
303
479
|
if (opts.purl) pkg.purl = opts.purl;
|
|
304
480
|
if (opts.cpe) pkg.cpe = opts.cpe;
|
|
@@ -484,6 +660,36 @@ function deriveVerificationMethod(code) {
|
|
|
484
660
|
if (code === void 0 || code === null || code === "") return void 0;
|
|
485
661
|
return VerificationMethodEnum.Automated;
|
|
486
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* Renames the HDF severity bands into the OSCAL risk characterization facet
|
|
665
|
+
* vocabulary (medium is 'moderate' there, informational is 'info'). Returns ''
|
|
666
|
+
* for anything outside the HDF vocabulary so callers omit the facet rather than
|
|
667
|
+
* emit a value OSCAL does not define. Go peer: shared.OSCALSeverityFromHDF.
|
|
668
|
+
*/
|
|
669
|
+
const OSCAL_SEVERITY_BY_HDF_BAND = {
|
|
670
|
+
critical: "critical",
|
|
671
|
+
high: "high",
|
|
672
|
+
medium: "moderate",
|
|
673
|
+
low: "low",
|
|
674
|
+
informational: "info"
|
|
675
|
+
};
|
|
676
|
+
function oscalSeverityFromHdf(severity) {
|
|
677
|
+
const band = (severity ?? "").toLowerCase();
|
|
678
|
+
return Object.prototype.hasOwnProperty.call(OSCAL_SEVERITY_BY_HDF_BAND, band) ? OSCAL_SEVERITY_BY_HDF_BAND[band] : "";
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* The expiresAt an override takes when the source tool records no expiration:
|
|
682
|
+
* one calendar year after appliedAt, computed in UTC. A calendar year (not 365
|
|
683
|
+
* days) keeps the date on the same month/day across leap years, and doing the
|
|
684
|
+
* rollover in UTC keeps the result off the host timezone. Go peer:
|
|
685
|
+
* shared.DefaultOverrideExpiry.
|
|
686
|
+
*/
|
|
687
|
+
function defaultOverrideExpiry(appliedAt) {
|
|
688
|
+
const expiresAt = /* @__PURE__ */ new Date();
|
|
689
|
+
expiresAt.setTime(appliedAt.getTime());
|
|
690
|
+
expiresAt.setUTCFullYear(expiresAt.getUTCFullYear() + 1);
|
|
691
|
+
return expiresAt;
|
|
692
|
+
}
|
|
487
693
|
function buildNoFindingsRequirement(id, codeDesc, startTime) {
|
|
488
694
|
return {
|
|
489
695
|
id,
|
|
@@ -511,6 +717,24 @@ const UNRATED_SEVERITY_VALUE = "unrated";
|
|
|
511
717
|
function markUnratedSeverity(tags, severity) {
|
|
512
718
|
if (isUnratedSeverity(severity)) tags[UNRATED_SEVERITY_TAG] = UNRATED_SEVERITY_VALUE;
|
|
513
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* The schema violations Go's typed decode would also reject.
|
|
722
|
+
*
|
|
723
|
+
* encoding/json refuses exactly two things: a JSON value whose type does not fit
|
|
724
|
+
* the target field, and a string that will not parse into a time.Time. Every
|
|
725
|
+
* other schema rule — required, enum, minItems, uuid and uri formats — decodes
|
|
726
|
+
* cleanly into the generated structs, so rejecting it here would put the two
|
|
727
|
+
* languages back out of step in the opposite direction.
|
|
728
|
+
*
|
|
729
|
+
* null is exempt for the same reason. Go unmarshals a JSON null into a slice,
|
|
730
|
+
* map or pointer as nil and into anything else as a no-op, never as an error, so
|
|
731
|
+
* a null cvss entry or a null results array reaches the converter there and must
|
|
732
|
+
* reach it here too — several parity goldens pin exactly that.
|
|
733
|
+
*/
|
|
734
|
+
function typeViolations(result) {
|
|
735
|
+
if (result.valid) return [];
|
|
736
|
+
return (result.errors ?? []).filter((e) => e.value !== null).filter((e) => e.keyword === "type" || e.keyword === "format" && e.format === "date-time").map((e) => `${e.field ?? ""} ${e.message ?? ""}`.trim());
|
|
737
|
+
}
|
|
514
738
|
//#endregion
|
|
515
739
|
//#region converters/legacyhdf-to-hdf/typescript/converter.ts
|
|
516
740
|
/**
|
|
@@ -523,22 +747,13 @@ function markUnratedSeverity(tags, severity) {
|
|
|
523
747
|
* - Results: snake_case → camelCase for all fields
|
|
524
748
|
* - Overlay flattening: merge overlay/wrapper baselines so every requirement has results
|
|
525
749
|
*/
|
|
526
|
-
/** Valid severity values per HDF v2.0 schema (matches the Go converter). */
|
|
527
|
-
const VALID_SEVERITIES$1 = /* @__PURE__ */ new Set([
|
|
528
|
-
"critical",
|
|
529
|
-
"high",
|
|
530
|
-
"medium",
|
|
531
|
-
"low",
|
|
532
|
-
"informational"
|
|
533
|
-
]);
|
|
534
750
|
/**
|
|
535
751
|
* Convert tags.severity string to a valid severity value.
|
|
536
752
|
* Returns null if the value is not a recognized severity.
|
|
537
753
|
*/
|
|
538
754
|
function tagSeverityToSeverity(tagSeverity) {
|
|
539
755
|
if (typeof tagSeverity !== "string") return null;
|
|
540
|
-
|
|
541
|
-
return VALID_SEVERITIES$1.has(normalized) ? normalized : null;
|
|
756
|
+
return parseSeverity(tagSeverity.trim()) ?? null;
|
|
542
757
|
}
|
|
543
758
|
/**
|
|
544
759
|
* Normalize status values from v1.0 to v2.0 format.
|
|
@@ -856,7 +1071,7 @@ function documentTimestamp(v1Data) {
|
|
|
856
1071
|
return latest ? formatTimestamp(latest) : void 0;
|
|
857
1072
|
}
|
|
858
1073
|
function convertLegacyHdf(v1Data, converterVersion = "1.0.0") {
|
|
859
|
-
validateInputSize(JSON.stringify(v1Data), "legacyhdf-to-hdf");
|
|
1074
|
+
validateInputSize$1(JSON.stringify(v1Data), "legacyhdf-to-hdf");
|
|
860
1075
|
const v2 = {
|
|
861
1076
|
baselines: (v1Data.profiles || []).map(convertProfile),
|
|
862
1077
|
statistics: v1Data.statistics || {}
|
|
@@ -1327,7 +1542,7 @@ function buildBaseline$1(name, findings, resultsChecksum) {
|
|
|
1327
1542
|
/** Converts an ASFF document to HDF Results JSON. */
|
|
1328
1543
|
async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
1329
1544
|
if (!input || input.trim().length === 0) throw new Error("asff: empty input");
|
|
1330
|
-
validateInputSize(input, "asff");
|
|
1545
|
+
validateInputSize$1(input, "asff");
|
|
1331
1546
|
const findings = parseFindings$1(input);
|
|
1332
1547
|
const resultsChecksum = await inputChecksum(input);
|
|
1333
1548
|
const order = [];
|
|
@@ -1376,7 +1591,7 @@ const SARIF_ALIASES = {
|
|
|
1376
1591
|
note: .3
|
|
1377
1592
|
};
|
|
1378
1593
|
async function convertSarifToHdf(input, converterVersion = "1.0.0") {
|
|
1379
|
-
validateInputSize(input, "sarif");
|
|
1594
|
+
validateInputSize$1(input, "sarif");
|
|
1380
1595
|
const resultsChecksum = await inputChecksum(input);
|
|
1381
1596
|
const sarif = parseJSON(input);
|
|
1382
1597
|
if (!sarif || typeof sarif !== "object") throw new Error("Invalid SARIF structure: not a valid JSON object");
|
|
@@ -1514,7 +1729,7 @@ function packageFromSarifProperties(props) {
|
|
|
1514
1729
|
if (props.purl) ecosystem = ecosystemFromPurlType(parsePurl(props.purl)?.type);
|
|
1515
1730
|
else if (props.ecosystem) ecosystem = ecosystemFromPurlType(props.ecosystem);
|
|
1516
1731
|
else if (name && version) ecosystem = Ecosystem.Generic;
|
|
1517
|
-
return buildAffectedPackage$
|
|
1732
|
+
return buildAffectedPackage$1({
|
|
1518
1733
|
name,
|
|
1519
1734
|
version,
|
|
1520
1735
|
ecosystem,
|
|
@@ -1650,9 +1865,7 @@ function buildSuppressionOverride(result, timestamp) {
|
|
|
1650
1865
|
const isFalsePositive = accepted.some((s) => justificationIndicatesFalsePositive(s.justification));
|
|
1651
1866
|
const type = isFalsePositive ? OverrideType.FalsePositive : OverrideType.Waiver;
|
|
1652
1867
|
const effective = isFalsePositive ? ResultStatus.NotApplicable : ResultStatus.Passed;
|
|
1653
|
-
const expiresAt =
|
|
1654
|
-
expiresAt.setTime(timestamp.getTime());
|
|
1655
|
-
expiresAt.setUTCFullYear(expiresAt.getUTCFullYear() + 1);
|
|
1868
|
+
const expiresAt = defaultOverrideExpiry(timestamp);
|
|
1656
1869
|
return {
|
|
1657
1870
|
override: {
|
|
1658
1871
|
type,
|
|
@@ -1725,11 +1938,10 @@ function buildTags$4(result, rule, resolvedLevel, cweIds, nistControls, cciContr
|
|
|
1725
1938
|
function extractSourceLocation$1(location) {
|
|
1726
1939
|
const uri = location.physicalLocation?.artifactLocation?.uri;
|
|
1727
1940
|
const line = location.physicalLocation?.region?.startLine;
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
};
|
|
1941
|
+
const sourceLocation = {};
|
|
1942
|
+
if (uri) sourceLocation.ref = uri;
|
|
1943
|
+
if (line) sourceLocation.line = line;
|
|
1944
|
+
return sourceLocation.ref !== void 0 || sourceLocation.line !== void 0 ? sourceLocation : void 0;
|
|
1733
1945
|
}
|
|
1734
1946
|
function createHDFResult(location, status, backtrace, suppressionMessage) {
|
|
1735
1947
|
const uri = location.physicalLocation?.artifactLocation?.uri || "";
|
|
@@ -1769,7 +1981,7 @@ const ARRAY_TAGS$2 = [
|
|
|
1769
1981
|
*/
|
|
1770
1982
|
async function convertJunitToHdf(input, converterVersion = "1.0.0") {
|
|
1771
1983
|
if (!input || !input.trim()) throw new Error("Empty input");
|
|
1772
|
-
validateInputSize(input, "junit");
|
|
1984
|
+
validateInputSize$1(input, "junit");
|
|
1773
1985
|
const { suites, name } = parseJUnitXML(input);
|
|
1774
1986
|
const scanTime = resolveScanTime(suites);
|
|
1775
1987
|
const requirements = buildRequirements(suites, scanTime);
|
|
@@ -1790,7 +2002,7 @@ function hostComponents(suites) {
|
|
|
1790
2002
|
const hosts = [];
|
|
1791
2003
|
const seen = /* @__PURE__ */ new Set();
|
|
1792
2004
|
for (const suite of suites) {
|
|
1793
|
-
const hostname = suite
|
|
2005
|
+
const hostname = suite["@_hostname"]?.trim();
|
|
1794
2006
|
if (!hostname || seen.has(hostname)) continue;
|
|
1795
2007
|
seen.add(hostname);
|
|
1796
2008
|
hosts.push({
|
|
@@ -1802,22 +2014,31 @@ function hostComponents(suites) {
|
|
|
1802
2014
|
return hosts;
|
|
1803
2015
|
}
|
|
1804
2016
|
function resolveScanTime(suites) {
|
|
1805
|
-
for (const suite of suites) if (suite
|
|
1806
|
-
const parsed = parseTimestamp(suite
|
|
2017
|
+
for (const suite of suites) if (suite["@_timestamp"]) {
|
|
2018
|
+
const parsed = parseTimestamp(suite["@_timestamp"]);
|
|
1807
2019
|
if (parsed) return parsed;
|
|
1808
2020
|
}
|
|
1809
2021
|
return /* @__PURE__ */ new Date();
|
|
1810
2022
|
}
|
|
1811
2023
|
function parseJUnitXML(input) {
|
|
1812
|
-
const parsed = parseXmlWithArrays(input, ARRAY_TAGS$2);
|
|
1813
|
-
if (parsed.testsuites)
|
|
1814
|
-
suites
|
|
1815
|
-
name
|
|
1816
|
-
|
|
2024
|
+
const parsed = parseXmlWithArrays(input, ARRAY_TAGS$2, { attributeNamePrefix: "@_" });
|
|
2025
|
+
if (parsed.testsuites) {
|
|
2026
|
+
const suites = [...parsed.testsuites.testsuite ?? []];
|
|
2027
|
+
const name = parsed.testsuites["@_name"] ? decodeXmlEntities$6(parsed.testsuites["@_name"]) : "JUnit Test Results";
|
|
2028
|
+
const looseCases = parsed.testsuites.testcase ?? [];
|
|
2029
|
+
if (looseCases.length > 0) suites.push({
|
|
2030
|
+
"@_name": name,
|
|
2031
|
+
testcase: looseCases
|
|
2032
|
+
});
|
|
2033
|
+
return {
|
|
2034
|
+
suites,
|
|
2035
|
+
name
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
1817
2038
|
if (parsed.testsuite) {
|
|
1818
2039
|
const suite = parsed.testsuite;
|
|
1819
2040
|
const suites = Array.isArray(suite) ? suite : [suite];
|
|
1820
|
-
const suiteName = suites[0]?.
|
|
2041
|
+
const suiteName = suites[0]?.["@_name"];
|
|
1821
2042
|
return {
|
|
1822
2043
|
suites,
|
|
1823
2044
|
name: suiteName ? decodeXmlEntities$6(suiteName) : "JUnit Test Results"
|
|
@@ -1848,14 +2069,14 @@ function testCaseToRequirement(tc, scanTime) {
|
|
|
1848
2069
|
startTime: scanTime
|
|
1849
2070
|
};
|
|
1850
2071
|
if (message !== void 0) result.message = message;
|
|
1851
|
-
if (tc
|
|
1852
|
-
const parsed = parseFloat(tc
|
|
2072
|
+
if (tc["@_time"]) {
|
|
2073
|
+
const parsed = parseFloat(tc["@_time"]);
|
|
1853
2074
|
if (!isNaN(parsed)) result.runTime = parsed;
|
|
1854
2075
|
}
|
|
1855
|
-
const name = decodeXmlEntities$6(tc
|
|
2076
|
+
const name = decodeXmlEntities$6(tc["@_name"]);
|
|
1856
2077
|
const descriptions = [{
|
|
1857
2078
|
label: "default",
|
|
1858
|
-
data: `JUnit test: ${name} in ${tc
|
|
2079
|
+
data: `JUnit test: ${name} in ${tc["@_classname"] ? decodeXmlEntities$6(tc["@_classname"]) : "unknown"}`
|
|
1859
2080
|
}];
|
|
1860
2081
|
const { systemOut, systemErr } = collectSystemStreams(tc);
|
|
1861
2082
|
if (systemOut) descriptions.push({
|
|
@@ -1894,19 +2115,19 @@ function collectSystemStreams(tc) {
|
|
|
1894
2115
|
};
|
|
1895
2116
|
}
|
|
1896
2117
|
function buildID(tc) {
|
|
1897
|
-
if (tc
|
|
1898
|
-
return decodeXmlEntities$6(tc
|
|
2118
|
+
if (tc["@_classname"]) return `${decodeXmlEntities$6(tc["@_classname"])}.${decodeXmlEntities$6(tc["@_name"])}`;
|
|
2119
|
+
return decodeXmlEntities$6(tc["@_name"]);
|
|
1899
2120
|
}
|
|
1900
2121
|
function resolveStatus(tc) {
|
|
1901
2122
|
if (tc.failure) {
|
|
1902
|
-
const msg = buildFailureMessage(tc.failure
|
|
2123
|
+
const msg = buildFailureMessage(tc.failure["@_message"] ?? "", tc.failure["@_type"] ?? "", tc.failure["#text"] ?? "");
|
|
1903
2124
|
return {
|
|
1904
2125
|
status: ResultStatus.Failed,
|
|
1905
2126
|
message: msg
|
|
1906
2127
|
};
|
|
1907
2128
|
}
|
|
1908
2129
|
if (tc.error) {
|
|
1909
|
-
const msg = buildFailureMessage(tc.error
|
|
2130
|
+
const msg = buildFailureMessage(tc.error["@_message"] ?? "", tc.error["@_type"] ?? "", tc.error["#text"] ?? "");
|
|
1910
2131
|
return {
|
|
1911
2132
|
status: ResultStatus.Error,
|
|
1912
2133
|
message: msg
|
|
@@ -1914,9 +2135,9 @@ function resolveStatus(tc) {
|
|
|
1914
2135
|
}
|
|
1915
2136
|
if (tc.skipped !== void 0) {
|
|
1916
2137
|
const skipped = typeof tc.skipped === "object" ? tc.skipped : null;
|
|
1917
|
-
if (skipped?.
|
|
2138
|
+
if (skipped?.["@_message"]) return {
|
|
1918
2139
|
status: ResultStatus.NotReviewed,
|
|
1919
|
-
message: `Skipped: ${decodeXmlEntities$6(skipped
|
|
2140
|
+
message: `Skipped: ${decodeXmlEntities$6(skipped["@_message"])}`
|
|
1920
2141
|
};
|
|
1921
2142
|
return {
|
|
1922
2143
|
status: ResultStatus.NotReviewed,
|
|
@@ -1933,12 +2154,12 @@ function buildFailureMessage(message, typeName, body) {
|
|
|
1933
2154
|
return result;
|
|
1934
2155
|
}
|
|
1935
2156
|
function buildCodeDesc$10(tc) {
|
|
1936
|
-
if (tc
|
|
1937
|
-
return decodeXmlEntities$6(tc
|
|
2157
|
+
if (tc["@_classname"]) return `${decodeXmlEntities$6(tc["@_classname"])} :: ${decodeXmlEntities$6(tc["@_name"])}`;
|
|
2158
|
+
return decodeXmlEntities$6(tc["@_name"]);
|
|
1938
2159
|
}
|
|
1939
2160
|
function noFindingsTarget(baselineName, suites) {
|
|
1940
2161
|
if (baselineName && baselineName !== "JUnit Test Results") return baselineName;
|
|
1941
|
-
for (const s of suites) if (s
|
|
2162
|
+
for (const s of suites) if (s["@_name"]) return decodeXmlEntities$6(s["@_name"]);
|
|
1942
2163
|
return "JUnit test suite";
|
|
1943
2164
|
}
|
|
1944
2165
|
//#endregion
|
|
@@ -2036,7 +2257,7 @@ function parseCpeVersion(testSystem) {
|
|
|
2036
2257
|
}
|
|
2037
2258
|
async function convertXccdfResultsToHdf(input, converterVersion = "1.0.0") {
|
|
2038
2259
|
if (!input || !input.trim()) throw new Error("Empty input");
|
|
2039
|
-
validateInputSize(input, "xccdf-results");
|
|
2260
|
+
validateInputSize$1(input, "xccdf-results");
|
|
2040
2261
|
const parsed = parseXmlWithArrays(input, ARRAY_TAGS$1, { trimValues: false });
|
|
2041
2262
|
const arfParsed = parsed;
|
|
2042
2263
|
if (arfParsed["asset-report-collection"]) return convertArfCollection(arfParsed["asset-report-collection"], input, converterVersion);
|
|
@@ -2960,13 +3181,6 @@ function cklbTitle(cl) {
|
|
|
2960
3181
|
//#endregion
|
|
2961
3182
|
//#region shared/typescript/checklist/to-hdf.ts
|
|
2962
3183
|
const CONVERTER_VERSION = "1.0.0";
|
|
2963
|
-
const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
2964
|
-
"critical",
|
|
2965
|
-
"high",
|
|
2966
|
-
"medium",
|
|
2967
|
-
"low",
|
|
2968
|
-
"informational"
|
|
2969
|
-
]);
|
|
2970
3184
|
/**
|
|
2971
3185
|
* Map the format-neutral Checklist model to an HDF Results object.
|
|
2972
3186
|
* controlType is derived per-Vuln from CCI->NIST; verificationMethod and
|
|
@@ -3038,7 +3252,8 @@ function vulnToRequirement(v, scanTime) {
|
|
|
3038
3252
|
if (v.legacyIDs && v.legacyIDs.length) tags["legacy_ids"] = v.legacyIDs;
|
|
3039
3253
|
if (v.extra && Object.keys(v.extra).length > 0) tags["cklMetadata"] = { ...v.extra };
|
|
3040
3254
|
const req = createRequirement(v.vulnNum, v.ruleTitle ?? v.vulnNum, descriptions, impact, [result], { tags });
|
|
3041
|
-
|
|
3255
|
+
const hdfSeverity = parseSeverity(severity);
|
|
3256
|
+
if (hdfSeverity !== void 0) req.severity = hdfSeverity;
|
|
3042
3257
|
const controlType = deriveControlTypeFromTags(nistTags);
|
|
3043
3258
|
if (controlType !== void 0) req.controlType = controlType;
|
|
3044
3259
|
return req;
|
|
@@ -3105,9 +3320,9 @@ function stamp(v) {
|
|
|
3105
3320
|
/** Maps a requirement onto the canonical effective-status input shape. */
|
|
3106
3321
|
function requirementStatusInput(req) {
|
|
3107
3322
|
return {
|
|
3108
|
-
impact: req.impact,
|
|
3109
|
-
resultStatuses: (req.results ?? []).map((r) => String(r.status)),
|
|
3110
|
-
overrides: (req.statusOverrides ?? []).map((o) => ({
|
|
3323
|
+
impact: req.impact ?? 0,
|
|
3324
|
+
resultStatuses: (req.results ?? []).filter((r) => r != null).map((r) => String(r.status)),
|
|
3325
|
+
overrides: (req.statusOverrides ?? []).filter((o) => o != null).map((o) => ({
|
|
3111
3326
|
status: o.status ? String(o.status) : void 0,
|
|
3112
3327
|
appliedAt: stamp(o.appliedAt),
|
|
3113
3328
|
expiresAt: stamp(o.expiresAt)
|
|
@@ -3127,8 +3342,8 @@ function requirementEffectiveStatus(req) {
|
|
|
3127
3342
|
* fields are synthesized best-effort so any HDF yields a valid checklist.
|
|
3128
3343
|
*/
|
|
3129
3344
|
function hdfToChecklist(input) {
|
|
3130
|
-
const hdf =
|
|
3131
|
-
if (
|
|
3345
|
+
const hdf = requireHdfResults(input, "hdf to checklist").doc;
|
|
3346
|
+
if (hdf.baselines.length === 0) throw new Error("hdf to checklist: HDF has no baselines");
|
|
3132
3347
|
const ext = hdf.extensions ?? {};
|
|
3133
3348
|
const format = strVal(ext, "checklistFormat") || "ckl";
|
|
3134
3349
|
const cklbVersion = strVal(ext, "cklbVersion");
|
|
@@ -3255,7 +3470,7 @@ function formatOverride(o) {
|
|
|
3255
3470
|
}
|
|
3256
3471
|
function overrideSeverity(req) {
|
|
3257
3472
|
for (const o of req.statusOverrides ?? []) if (o.impact) return {
|
|
3258
|
-
severity:
|
|
3473
|
+
severity: cklSeverityFromImpact(o.impact.value) || "low",
|
|
3259
3474
|
justification: o.reason ?? ""
|
|
3260
3475
|
};
|
|
3261
3476
|
return {
|
|
@@ -3263,20 +3478,20 @@ function overrideSeverity(req) {
|
|
|
3263
3478
|
justification: ""
|
|
3264
3479
|
};
|
|
3265
3480
|
}
|
|
3266
|
-
function
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3481
|
+
function cklSeverityFromImpact(impact) {
|
|
3482
|
+
switch (impactToSeverity(impact)) {
|
|
3483
|
+
case "critical":
|
|
3484
|
+
case "high": return "high";
|
|
3485
|
+
case "medium": return "medium";
|
|
3486
|
+
case "low": return "low";
|
|
3487
|
+
default: return "";
|
|
3488
|
+
}
|
|
3270
3489
|
}
|
|
3271
3490
|
function resolveSeverity(req, tags) {
|
|
3272
3491
|
const tagSev = strVal(tags, "severity");
|
|
3273
3492
|
if (tagSev) return tagSev;
|
|
3274
3493
|
if (req.severity) return String(req.severity).toLowerCase();
|
|
3275
|
-
|
|
3276
|
-
if (i >= .7) return "high";
|
|
3277
|
-
if (i >= .4) return "medium";
|
|
3278
|
-
if (i > 0) return "low";
|
|
3279
|
-
return "";
|
|
3494
|
+
return cklSeverityFromImpact(req.impact ?? 0);
|
|
3280
3495
|
}
|
|
3281
3496
|
function resolveCcis(tags) {
|
|
3282
3497
|
const direct = strSlice(tags, "cci");
|
|
@@ -3326,7 +3541,7 @@ function strSlice(m, key) {
|
|
|
3326
3541
|
* skill Step 4d).
|
|
3327
3542
|
*/
|
|
3328
3543
|
async function convertCklToHdf(input) {
|
|
3329
|
-
validateInputSize(input, "ckl-to-hdf");
|
|
3544
|
+
validateInputSize$1(input, "ckl-to-hdf");
|
|
3330
3545
|
const resultsChecksum = await inputChecksum(input);
|
|
3331
3546
|
const checklist = parseCkl(input);
|
|
3332
3547
|
return JSON.stringify(checklistToHdf(checklist, resultsChecksum, "ckl-to-hdf"), null, 2);
|
|
@@ -3343,7 +3558,7 @@ async function convertCklToHdf(input) {
|
|
|
3343
3558
|
* skill Step 4d).
|
|
3344
3559
|
*/
|
|
3345
3560
|
async function convertCklbToHdf(input) {
|
|
3346
|
-
validateInputSize(input, "cklb-to-hdf");
|
|
3561
|
+
validateInputSize$1(input, "cklb-to-hdf");
|
|
3347
3562
|
const resultsChecksum = await inputChecksum(input);
|
|
3348
3563
|
const checklist = parseCklb(input);
|
|
3349
3564
|
return JSON.stringify(checklistToHdf(checklist, resultsChecksum, "cklb-to-hdf"), null, 2);
|
|
@@ -3572,7 +3787,7 @@ function buildRequirement$18(query, startTime) {
|
|
|
3572
3787
|
* @returns HDF JSON string
|
|
3573
3788
|
*/
|
|
3574
3789
|
async function convertKicsToHdf(input, converterVersion = "1.0.0") {
|
|
3575
|
-
validateInputSize(input, "kics");
|
|
3790
|
+
validateInputSize$1(input, "kics");
|
|
3576
3791
|
if (!input || input.trim().length === 0) throw new Error("kics: empty input");
|
|
3577
3792
|
const report = parseJSON(input);
|
|
3578
3793
|
if (!Array.isArray(report.queries) || typeof report.kics_version !== "string") throw new Error("kics: input does not look like a KICS report");
|
|
@@ -3635,10 +3850,11 @@ function lineOf(position) {
|
|
|
3635
3850
|
}
|
|
3636
3851
|
/**
|
|
3637
3852
|
* Semgrep emits CWEs in prose form -- 'CWE-89: Improper Neutralization of ...'
|
|
3638
|
-
* -- while the mapping keys on the bare number.
|
|
3853
|
+
* -- while the mapping keys on the bare number. Read entry by entry so source
|
|
3854
|
+
* order is kept, matching the Go twin.
|
|
3639
3855
|
*/
|
|
3640
3856
|
function extractCweIds(metadata) {
|
|
3641
|
-
return normalizeToArray(metadata.cwe).
|
|
3857
|
+
return normalizeToArray(metadata.cwe).flatMap((entry) => extractCWEIDs(entry)).map((id) => Number.parseInt(id, 10)).filter((id) => Number.isFinite(id));
|
|
3642
3858
|
}
|
|
3643
3859
|
/**
|
|
3644
3860
|
* The parsed CWE ids in the CWE-N catalog form the schema's cwe[] field asks
|
|
@@ -3658,6 +3874,15 @@ function impactFor(result) {
|
|
|
3658
3874
|
return severityToImpactWithAliases(severity, SEMGREP_SEVERITY_ALIASES, DEFAULT_IMPACT);
|
|
3659
3875
|
}
|
|
3660
3876
|
/**
|
|
3877
|
+
* Publishes the band the impact already implies, so the two fields cannot
|
|
3878
|
+
* disagree. An absent or redacted severity stays unset: the unrated tag is what
|
|
3879
|
+
* tells a consumer the 0.5 was a default, and a "medium" here would hide it.
|
|
3880
|
+
*/
|
|
3881
|
+
function severityFor(result) {
|
|
3882
|
+
if (!isPresent(result.extra?.severity)) return;
|
|
3883
|
+
return impactToSeverity(impactFor(result));
|
|
3884
|
+
}
|
|
3885
|
+
/**
|
|
3661
3886
|
* Semgrep rule ids are dotted paths whose final segment is the rule name. The
|
|
3662
3887
|
* JSON output carries no human-readable rule title anywhere -- unlike the SARIF
|
|
3663
3888
|
* output, whose rule objects have `name` and `shortDescription` -- so one is
|
|
@@ -3794,6 +4019,8 @@ function buildRequirement$17(checkId, results, startTime) {
|
|
|
3794
4019
|
...refs.length > 0 ? { refs } : {},
|
|
3795
4020
|
...sourceLocation ? { sourceLocation } : {}
|
|
3796
4021
|
});
|
|
4022
|
+
const severity = severityFor(representative);
|
|
4023
|
+
if (severity !== void 0) requirement.severity = severity;
|
|
3797
4024
|
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3798
4025
|
const controlType = deriveControlTypeFromTags(tags.nist);
|
|
3799
4026
|
if (controlType !== void 0) requirement.controlType = controlType;
|
|
@@ -3870,7 +4097,7 @@ function buildCoverageRequirement(report, ruleCount, startTime) {
|
|
|
3870
4097
|
* @returns HDF JSON string
|
|
3871
4098
|
*/
|
|
3872
4099
|
async function convertSemgrepToHdf(input, converterVersion = "1.0.0") {
|
|
3873
|
-
validateInputSize(input, "semgrep");
|
|
4100
|
+
validateInputSize$1(input, "semgrep");
|
|
3874
4101
|
if (!input || input.trim().length === 0) throw new Error("semgrep: empty input");
|
|
3875
4102
|
registerAllFingerprints();
|
|
3876
4103
|
const detected = detectConverter(input);
|
|
@@ -4106,7 +4333,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
4106
4333
|
const version = rep.version;
|
|
4107
4334
|
if (name && version) {
|
|
4108
4335
|
const ecosystem = ecosystemFromSnykPackageManager(packageManager);
|
|
4109
|
-
const pkg = buildAffectedPackage$
|
|
4336
|
+
const pkg = buildAffectedPackage$1({
|
|
4110
4337
|
name,
|
|
4111
4338
|
version,
|
|
4112
4339
|
ecosystem,
|
|
@@ -4153,7 +4380,7 @@ function convertSingleProject(report, resultsChecksum, scanTime) {
|
|
|
4153
4380
|
*/
|
|
4154
4381
|
async function convertSnykToHdf(input, converterVersion = "1.0.0") {
|
|
4155
4382
|
if (!input || input.trim().length === 0) throw new Error("snyk: empty input");
|
|
4156
|
-
validateInputSize(input, "snyk");
|
|
4383
|
+
validateInputSize$1(input, "snyk");
|
|
4157
4384
|
registerAllFingerprints();
|
|
4158
4385
|
const detected = detectConverter(input);
|
|
4159
4386
|
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
@@ -4261,31 +4488,27 @@ function buildCvssEntries$7(vuln) {
|
|
|
4261
4488
|
}
|
|
4262
4489
|
function mapGrypeTypeToEcosystem(grypeType) {
|
|
4263
4490
|
switch ((grypeType ?? "").toLowerCase()) {
|
|
4264
|
-
case "rpm": return Ecosystem.RPM;
|
|
4265
|
-
case "deb": return Ecosystem.Deb;
|
|
4266
|
-
case "npm": return Ecosystem.Npm;
|
|
4267
4491
|
case "python": return Ecosystem.Pypi;
|
|
4268
|
-
case "gem": return Ecosystem.Gem;
|
|
4269
4492
|
case "go-module": return Ecosystem.Go;
|
|
4270
4493
|
case "java-archive":
|
|
4271
4494
|
case "jenkins-plugin": return Ecosystem.Maven;
|
|
4272
4495
|
case "dotnet": return Ecosystem.Nuget;
|
|
4273
4496
|
case "rust-crate": return Ecosystem.Cargo;
|
|
4274
|
-
default: return
|
|
4497
|
+
default: return ecosystemFromPurlType(grypeType);
|
|
4275
4498
|
}
|
|
4276
4499
|
}
|
|
4277
4500
|
function buildAffectedPackages(match) {
|
|
4278
4501
|
const artifact = match.artifact;
|
|
4279
|
-
const
|
|
4502
|
+
const fix = match.vulnerability.fix;
|
|
4503
|
+
const pkg = buildAffectedPackage$1({
|
|
4280
4504
|
name: artifact.name,
|
|
4281
4505
|
version: artifact.version,
|
|
4282
|
-
ecosystem: mapGrypeTypeToEcosystem(artifact.type)
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
return [pkg];
|
|
4506
|
+
ecosystem: mapGrypeTypeToEcosystem(artifact.type),
|
|
4507
|
+
cpe: artifact.cpes?.[0],
|
|
4508
|
+
purl: artifact.purl,
|
|
4509
|
+
fixedInVersion: fix?.state === "fixed" ? fix.versions?.[0] : void 0
|
|
4510
|
+
});
|
|
4511
|
+
return pkg ? [pkg] : [];
|
|
4289
4512
|
}
|
|
4290
4513
|
const CWE_ID_PATTERN$2 = /^CWE-[1-9]\d*$/;
|
|
4291
4514
|
function extractCwe(raw) {
|
|
@@ -4398,7 +4621,7 @@ function buildComponent$2(report, targetName) {
|
|
|
4398
4621
|
return component;
|
|
4399
4622
|
}
|
|
4400
4623
|
async function convertGrypeToHdf(input, converterVersion = "1.0.0") {
|
|
4401
|
-
validateInputSize(input, "grype");
|
|
4624
|
+
validateInputSize$1(input, "grype");
|
|
4402
4625
|
const resultsChecksum = await inputChecksum(input);
|
|
4403
4626
|
const grypeData = parseJSON(input);
|
|
4404
4627
|
const requirements = [];
|
|
@@ -4472,9 +4695,7 @@ function buildWaiverOverride(ar) {
|
|
|
4472
4695
|
let reason = ar.decision_details || ar.name || "";
|
|
4473
4696
|
if (!reason) reason = "Risk accepted in DefectDojo";
|
|
4474
4697
|
const appliedAt = (ar.created ? parseTimestamp(ar.created) : null) ?? /* @__PURE__ */ new Date();
|
|
4475
|
-
const oneYearOut =
|
|
4476
|
-
oneYearOut.setTime(appliedAt.getTime());
|
|
4477
|
-
oneYearOut.setFullYear(oneYearOut.getFullYear() + 1);
|
|
4698
|
+
const oneYearOut = defaultOverrideExpiry(appliedAt);
|
|
4478
4699
|
const expiresAt = (ar.expiration_date ? parseTimestamp(ar.expiration_date) : null) ?? oneYearOut;
|
|
4479
4700
|
return {
|
|
4480
4701
|
type: OverrideType.Waiver,
|
|
@@ -4506,9 +4727,7 @@ function falsePositiveReviewer(f) {
|
|
|
4506
4727
|
function buildFalsePositiveOverride(f) {
|
|
4507
4728
|
const reason = f.mitigation || "Marked as false positive in DefectDojo";
|
|
4508
4729
|
const appliedAt = (f.mitigated ? parseTimestamp(f.mitigated) : null) ?? parseFindingDate(f.date) ?? /* @__PURE__ */ new Date();
|
|
4509
|
-
const oneYearOut =
|
|
4510
|
-
oneYearOut.setTime(appliedAt.getTime());
|
|
4511
|
-
oneYearOut.setUTCFullYear(oneYearOut.getUTCFullYear() + 1);
|
|
4730
|
+
const oneYearOut = defaultOverrideExpiry(appliedAt);
|
|
4512
4731
|
return {
|
|
4513
4732
|
type: OverrideType.FalsePositive,
|
|
4514
4733
|
status: ResultStatus.NotApplicable,
|
|
@@ -4641,7 +4860,7 @@ function scannerName(f) {
|
|
|
4641
4860
|
return f.related_fields?.test?.test_type?.name || "DefectDojo";
|
|
4642
4861
|
}
|
|
4643
4862
|
async function convertDefectDojoToHdf(input, converterVersion = "0.1.0") {
|
|
4644
|
-
validateInputSize(input, "defectdojo");
|
|
4863
|
+
validateInputSize$1(input, "defectdojo");
|
|
4645
4864
|
const resultsChecksum = await inputChecksum(input);
|
|
4646
4865
|
const parsed = parseJSON(input);
|
|
4647
4866
|
const findings = Array.isArray(parsed) ? parsed : parsed.results ?? [];
|
|
@@ -4720,7 +4939,7 @@ function decodeEntitiesDeep(value) {
|
|
|
4720
4939
|
* Convert Nessus XML scan results to HDF format
|
|
4721
4940
|
*/
|
|
4722
4941
|
async function convertNessusToHdf(nessusXml, converterVersion = "1.0.0") {
|
|
4723
|
-
validateInputSize(nessusXml, "nessus");
|
|
4942
|
+
validateInputSize$1(nessusXml, "nessus");
|
|
4724
4943
|
const resultsChecksum = await inputChecksum(nessusXml);
|
|
4725
4944
|
const parsed = decodeEntitiesDeep(parseXmlWithArrays(nessusXml, [
|
|
4726
4945
|
"preference",
|
|
@@ -5181,7 +5400,7 @@ function foldNistControls(cweIds, owaspIds) {
|
|
|
5181
5400
|
* @returns HDF JSON string
|
|
5182
5401
|
*/
|
|
5183
5402
|
async function convertSonarqubeToHdf(input, converterVersion = "1.0.0") {
|
|
5184
|
-
validateInputSize(input, "sonarqube");
|
|
5403
|
+
validateInputSize$1(input, "sonarqube");
|
|
5185
5404
|
const resultsChecksum = await inputChecksum(input);
|
|
5186
5405
|
const sonarData = parseJSON(input);
|
|
5187
5406
|
if (!sonarData || typeof sonarData !== "object") throw new Error("Invalid SonarQube structure: not a valid JSON object");
|
|
@@ -5519,7 +5738,7 @@ function buildRequirement$15(rule) {
|
|
|
5519
5738
|
* @returns HDF JSON string
|
|
5520
5739
|
*/
|
|
5521
5740
|
async function convertAwsConfigToHdf(input, converterVersion = "1.0.0") {
|
|
5522
|
-
validateInputSize(input, "aws-config");
|
|
5741
|
+
validateInputSize$1(input, "aws-config");
|
|
5523
5742
|
const resultsChecksum = await inputChecksum(input);
|
|
5524
5743
|
const data = parseJSON(input);
|
|
5525
5744
|
if (!data || typeof data !== "object") throw new Error("Invalid AWS Config export: not a valid JSON object");
|
|
@@ -5657,7 +5876,7 @@ function parseInput(input) {
|
|
|
5657
5876
|
* @returns HDF JSON string
|
|
5658
5877
|
*/
|
|
5659
5878
|
async function convertCheckovToHdf(input, converterVersion = "1.0.0") {
|
|
5660
|
-
validateInputSize(input, "checkov");
|
|
5879
|
+
validateInputSize$1(input, "checkov");
|
|
5661
5880
|
registerAllFingerprints();
|
|
5662
5881
|
const detected = detectConverter(input);
|
|
5663
5882
|
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
@@ -5842,7 +6061,7 @@ function buildGosecExtensions(report) {
|
|
|
5842
6061
|
* @returns HDF JSON string
|
|
5843
6062
|
*/
|
|
5844
6063
|
async function convertGosecToHdf(input, converterVersion = "1.0.0") {
|
|
5845
|
-
validateInputSize(input, "gosec");
|
|
6064
|
+
validateInputSize$1(input, "gosec");
|
|
5846
6065
|
registerAllFingerprints();
|
|
5847
6066
|
const detected = detectConverter(input);
|
|
5848
6067
|
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
@@ -5916,7 +6135,7 @@ function convertVulnToRequirement(vuln) {
|
|
|
5916
6135
|
return req;
|
|
5917
6136
|
}
|
|
5918
6137
|
async function convertNiktoToHdf(input, converterVersion = "unknown") {
|
|
5919
|
-
validateInputSize(input, "nikto");
|
|
6138
|
+
validateInputSize$1(input, "nikto");
|
|
5920
6139
|
const resultsChecksum = await inputChecksum(input);
|
|
5921
6140
|
const niktoData = parseJSON(input);
|
|
5922
6141
|
const vulnGroups = /* @__PURE__ */ new Map();
|
|
@@ -6116,7 +6335,7 @@ function parseZapTimestamp(s) {
|
|
|
6116
6335
|
return parseTimestamp(s) ?? void 0;
|
|
6117
6336
|
}
|
|
6118
6337
|
async function convertZapToHdf(input, converterVersion = "1.0.0") {
|
|
6119
|
-
validateInputSize(input, "zap");
|
|
6338
|
+
validateInputSize$1(input, "zap");
|
|
6120
6339
|
registerAllFingerprints();
|
|
6121
6340
|
const detected = detectConverter(input);
|
|
6122
6341
|
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
@@ -6655,7 +6874,7 @@ function buildBom(parts) {
|
|
|
6655
6874
|
* oversized or undetectable input.
|
|
6656
6875
|
*/
|
|
6657
6876
|
function parseBom(input) {
|
|
6658
|
-
validateInputSize(input, CONVERTER);
|
|
6877
|
+
validateInputSize$1(input, CONVERTER);
|
|
6659
6878
|
const obj = parseJSON(input);
|
|
6660
6879
|
const detected = detectFormat(obj);
|
|
6661
6880
|
if (!detected) throw new Error("bom-parser: could not detect a supported BOM format (expected CycloneDX or SPDX JSON)");
|
|
@@ -7273,9 +7492,7 @@ function analysisOverride(vuln, fallback) {
|
|
|
7273
7492
|
default: return;
|
|
7274
7493
|
}
|
|
7275
7494
|
const appliedAt = analysisAppliedAt(vuln, fallback);
|
|
7276
|
-
const expiresAt =
|
|
7277
|
-
expiresAt.setTime(appliedAt.getTime());
|
|
7278
|
-
expiresAt.setUTCFullYear(expiresAt.getUTCFullYear() + 1);
|
|
7495
|
+
const expiresAt = defaultOverrideExpiry(appliedAt);
|
|
7279
7496
|
const override = {
|
|
7280
7497
|
type: disposition,
|
|
7281
7498
|
status: effectiveStatus,
|
|
@@ -7303,7 +7520,7 @@ function analysisOverride(vuln, fallback) {
|
|
|
7303
7520
|
*/
|
|
7304
7521
|
async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
7305
7522
|
if (!input || input.trim().length === 0) throw new Error("cyclonedx: empty input");
|
|
7306
|
-
validateInputSize(input, "cyclonedx");
|
|
7523
|
+
validateInputSize$1(input, "cyclonedx");
|
|
7307
7524
|
const bom = parseJSON(input);
|
|
7308
7525
|
if (!bom || typeof bom !== "object") throw new Error("cyclonedx: invalid JSON");
|
|
7309
7526
|
if (bom.bomFormat !== "CycloneDX") throw new Error(`cyclonedx: missing or invalid bomFormat (expected "CycloneDX", got "${bom.bomFormat ?? "undefined"}")`);
|
|
@@ -7413,10 +7630,8 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
|
7413
7630
|
* @returns CSV string with sanitized output
|
|
7414
7631
|
*/
|
|
7415
7632
|
function convertHdfToCsv(input) {
|
|
7416
|
-
|
|
7417
|
-
const hdf =
|
|
7418
|
-
if (!hdf || typeof hdf !== "object" || !("baselines" in hdf)) throw new Error("Invalid HDF structure: missing baselines field");
|
|
7419
|
-
if (!Array.isArray(hdf.baselines)) throw new Error("Invalid HDF structure: baselines must be an array");
|
|
7633
|
+
const { doc: hdfDoc } = requireHdfResults(input, "hdf-to-csv");
|
|
7634
|
+
const hdf = hdfDoc;
|
|
7420
7635
|
const rows = [];
|
|
7421
7636
|
const components = hdf.components || [];
|
|
7422
7637
|
const targetList = components.length > 0 ? components.map((t) => ({
|
|
@@ -7430,35 +7645,64 @@ function convertHdfToCsv(input) {
|
|
|
7430
7645
|
fqdn: "",
|
|
7431
7646
|
ipAddress: ""
|
|
7432
7647
|
}];
|
|
7433
|
-
for (const baseline of hdf.baselines)
|
|
7648
|
+
for (const baseline of hdf.baselines) {
|
|
7649
|
+
if (!Array.isArray(baseline.requirements) || baseline.requirements.length === 0) throw new Error(`hdf-to-csv: baseline "${text(baseline.name)}" has no requirements`);
|
|
7650
|
+
for (const requirement of baseline.requirements) for (const target of targetList) rows.push(createRow(baseline, requirement, target));
|
|
7651
|
+
}
|
|
7434
7652
|
return buildCsv(rows, { sanitize: true });
|
|
7435
7653
|
}
|
|
7436
7654
|
/**
|
|
7655
|
+
* A schema-required number as a number. Go decodes an absent one to the zero
|
|
7656
|
+
* value and cannot tell it apart from a real zero — impact 0 legitimately means
|
|
7657
|
+
* Not Applicable — so distinguishing them would need a schema validation pass
|
|
7658
|
+
* the converters deliberately do not do. Rendering 0 here matches what Go emits,
|
|
7659
|
+
* where reading .toFixed off an absent value threw a raw TypeError instead.
|
|
7660
|
+
*/
|
|
7661
|
+
function numeric(value) {
|
|
7662
|
+
return typeof value === "number" ? value : 0;
|
|
7663
|
+
}
|
|
7664
|
+
/**
|
|
7665
|
+
* A schema-required string as text. Nested-invalid input reaches the converter
|
|
7666
|
+
* with these absent, and an absent value reached sanitizeCsvValue's String() as
|
|
7667
|
+
* the literal "undefined" — where Go, formatting a zero-value string, wrote
|
|
7668
|
+
* nothing. Every schema-required STRING cell goes through here; the numeric
|
|
7669
|
+
* ones go through numeric(), which serves the same purpose for a different type.
|
|
7670
|
+
*/
|
|
7671
|
+
function text(value) {
|
|
7672
|
+
return typeof value === "string" ? value : "";
|
|
7673
|
+
}
|
|
7674
|
+
/** The requirement id as text; also names the requirement in the no-results error. */
|
|
7675
|
+
function requirementIdText$1(id) {
|
|
7676
|
+
return text(id);
|
|
7677
|
+
}
|
|
7678
|
+
/**
|
|
7437
7679
|
* Create a CSV row from baseline, requirement, and target data
|
|
7438
7680
|
*/
|
|
7439
7681
|
function createRow(baseline, requirement, target) {
|
|
7440
|
-
const
|
|
7682
|
+
const [firstResult] = Array.isArray(requirement.results) ? requirement.results : [];
|
|
7683
|
+
if (!firstResult) throw new Error(`hdf-to-csv: requirement "${requirementIdText$1(requirement.id)}" has no results`);
|
|
7684
|
+
if (!Array.isArray(requirement.descriptions) || requirement.descriptions.length === 0) throw new Error(`hdf-to-csv: requirement "${requirementIdText$1(requirement.id)}" has no descriptions`);
|
|
7685
|
+
const description = descriptionByLabel(requirement, "default");
|
|
7441
7686
|
const check = descriptionByLabel(requirement, "check");
|
|
7442
7687
|
const fix = descriptionByLabel(requirement, "fix");
|
|
7443
7688
|
const rationale = descriptionByLabel(requirement, "rationale");
|
|
7444
7689
|
const code = requirement.code ?? "";
|
|
7445
7690
|
const references = flattenRefs(requirement.refs);
|
|
7446
7691
|
const severity = getSeverity(requirement);
|
|
7447
|
-
const
|
|
7448
|
-
const
|
|
7449
|
-
const message = firstResult?.message || "";
|
|
7692
|
+
const status = firstResult.status;
|
|
7693
|
+
const message = firstResult.message ?? "";
|
|
7450
7694
|
const nistControls = extractArrayFromTags(requirement.tags, "nist");
|
|
7451
7695
|
const cciControls = extractArrayFromTags(requirement.tags, "cci");
|
|
7452
7696
|
const effectiveStatus = requirementEffectiveStatus(requirement);
|
|
7453
|
-
const effectiveImpact = (requirement.effectiveImpact ?? requirement.impact).toFixed(
|
|
7697
|
+
const effectiveImpact = numeric(requirement.effectiveImpact ?? requirement.impact).toFixed(2);
|
|
7454
7698
|
const disposition = requirement.disposition ? String(requirement.disposition) : "";
|
|
7455
7699
|
return {
|
|
7456
|
-
"Baseline ID": baseline.name,
|
|
7700
|
+
"Baseline ID": text(baseline.name),
|
|
7457
7701
|
"Baseline Version": baseline.version || "",
|
|
7458
7702
|
"Baseline Title": baseline.title || "",
|
|
7459
|
-
"Target ID": target.name,
|
|
7460
|
-
"Target Type": target.type,
|
|
7461
|
-
"Requirement ID": requirement.id,
|
|
7703
|
+
"Target ID": text(target.name),
|
|
7704
|
+
"Target Type": text(target.type),
|
|
7705
|
+
"Requirement ID": requirementIdText$1(requirement.id),
|
|
7462
7706
|
"Requirement Title": requirement.title || "",
|
|
7463
7707
|
"Description": description,
|
|
7464
7708
|
"Check": check,
|
|
@@ -7467,8 +7711,8 @@ function createRow(baseline, requirement, target) {
|
|
|
7467
7711
|
"Code": code,
|
|
7468
7712
|
"References": references,
|
|
7469
7713
|
"Severity": severity,
|
|
7470
|
-
"Impact": requirement.impact.toFixed(
|
|
7471
|
-
"Status":
|
|
7714
|
+
"Impact": numeric(requirement.impact).toFixed(2),
|
|
7715
|
+
"Status": text(status),
|
|
7472
7716
|
"NIST Controls": nistControls,
|
|
7473
7717
|
"CCI Controls": cciControls,
|
|
7474
7718
|
"Control Type": requirement.controlType ?? "",
|
|
@@ -7482,8 +7726,8 @@ function createRow(baseline, requirement, target) {
|
|
|
7482
7726
|
"Applied By": joinOverrides(requirement.statusOverrides, (o) => o.appliedBy?.identifier ?? ""),
|
|
7483
7727
|
"Expires At": joinOverrides(requirement.statusOverrides, (o) => formatExpires(o.expiresAt)),
|
|
7484
7728
|
"CVSS": cvssScores(requirement.cvss),
|
|
7485
|
-
"CWE":
|
|
7486
|
-
"EPSS": requirement.epss ? requirement.epss.score.toFixed(5) : "",
|
|
7729
|
+
"CWE": (requirement.cwe ?? []).join("; "),
|
|
7730
|
+
"EPSS": requirement.epss ? numeric(requirement.epss.score).toFixed(5) : "",
|
|
7487
7731
|
"KEV": requirement.kev ? requirement.kev.inKev ? "true" : "false" : "",
|
|
7488
7732
|
"Target FQDN": target.fqdn,
|
|
7489
7733
|
"Target IP": target.ipAddress
|
|
@@ -7497,6 +7741,7 @@ function joinOverrides(overrides, pick) {
|
|
|
7497
7741
|
if (!Array.isArray(overrides)) return "";
|
|
7498
7742
|
const out = [];
|
|
7499
7743
|
for (const o of overrides) {
|
|
7744
|
+
if (o === null || o === void 0) continue;
|
|
7500
7745
|
const v = pick(o);
|
|
7501
7746
|
if (v) out.push(v);
|
|
7502
7747
|
}
|
|
@@ -7512,14 +7757,17 @@ function formatExpires(value) {
|
|
|
7512
7757
|
}
|
|
7513
7758
|
/**
|
|
7514
7759
|
* Render each CVSS entry's score (computed when present, else base) to one
|
|
7515
|
-
* decimal
|
|
7760
|
+
* decimal — the precision the CVSS spec defines and the source carries — joined
|
|
7761
|
+
* with '; ' to preserve multi-CVE findings. The Go peer rounds through a shared
|
|
7762
|
+
* helper matching toFixed, so a .x5 score renders the same digits in both.
|
|
7516
7763
|
*/
|
|
7517
7764
|
function cvssScores(entries) {
|
|
7518
7765
|
if (!Array.isArray(entries)) return "";
|
|
7519
7766
|
const out = [];
|
|
7520
7767
|
for (const c of entries) {
|
|
7768
|
+
if (c === null || c === void 0) continue;
|
|
7521
7769
|
const score = c.computedScore ?? c.baseScore;
|
|
7522
|
-
if (score
|
|
7770
|
+
if (typeof score === "number") out.push(score.toFixed(1));
|
|
7523
7771
|
}
|
|
7524
7772
|
return out.join("; ");
|
|
7525
7773
|
}
|
|
@@ -7531,13 +7779,10 @@ function getSeverity(requirement) {
|
|
|
7531
7779
|
if ("severity" in requirement.tags) {
|
|
7532
7780
|
const sev = requirement.tags.severity;
|
|
7533
7781
|
if (typeof sev === "string") return sev;
|
|
7534
|
-
if (Array.isArray(sev) && sev
|
|
7782
|
+
if (Array.isArray(sev) && typeof sev[0] === "string") return sev[0];
|
|
7535
7783
|
}
|
|
7536
7784
|
}
|
|
7537
|
-
|
|
7538
|
-
if (impact >= .7) return "high";
|
|
7539
|
-
if (impact >= .4) return "medium";
|
|
7540
|
-
return "low";
|
|
7785
|
+
return impactToSeverity(requirement.impact);
|
|
7541
7786
|
}
|
|
7542
7787
|
/**
|
|
7543
7788
|
* Extract array values from tags object and join with semicolons
|
|
@@ -7545,18 +7790,20 @@ function getSeverity(requirement) {
|
|
|
7545
7790
|
function extractArrayFromTags(tags, key) {
|
|
7546
7791
|
if (!tags || typeof tags !== "object") return "";
|
|
7547
7792
|
const value = tags[key];
|
|
7548
|
-
if (Array.isArray(value)) return value.
|
|
7793
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string").join("; ");
|
|
7549
7794
|
return "";
|
|
7550
7795
|
}
|
|
7551
7796
|
/**
|
|
7552
7797
|
* Get the data of the first description matching a label. Empty when absent.
|
|
7553
7798
|
*/
|
|
7554
7799
|
function descriptionByLabel(requirement, label) {
|
|
7555
|
-
return requirement.descriptions.find((d) => d.label === label)?.data || "";
|
|
7800
|
+
return requirement.descriptions.find((d) => d !== null && d !== void 0 && d.label === label)?.data || "";
|
|
7556
7801
|
}
|
|
7557
7802
|
/**
|
|
7558
7803
|
* Flatten a requirement's refs to one string: each Reference rendered as its
|
|
7559
|
-
* url/uri (or a string `ref`); array-form refs are skipped
|
|
7804
|
+
* url/uri (or a string `ref`); array-form refs are skipped here, though a document
|
|
7805
|
+
* containing one never reaches this function in Go, whose typed decode rejects
|
|
7806
|
+
* it outright — a reject-versus-degrade split tracked on its own card. Joined with '; ' to
|
|
7560
7807
|
* match the NIST/CCI column convention.
|
|
7561
7808
|
*/
|
|
7562
7809
|
function flattenRefs(refs) {
|
|
@@ -8234,7 +8481,7 @@ function buildEvidences(req) {
|
|
|
8234
8481
|
return out;
|
|
8235
8482
|
}
|
|
8236
8483
|
function floatMetric(f) {
|
|
8237
|
-
return
|
|
8484
|
+
return formatJsonNumber(f);
|
|
8238
8485
|
}
|
|
8239
8486
|
//#endregion
|
|
8240
8487
|
//#region converters/hdf-to-asff/typescript/converter.ts
|
|
@@ -8263,7 +8510,7 @@ const MAX_DESCRIPTION = 1024;
|
|
|
8263
8510
|
const EPOCH_SENTINEL = "1970-01-01T00:00:00Z";
|
|
8264
8511
|
function convertHdfToAsff(input, converterVersion = "0.1.0") {
|
|
8265
8512
|
const name = "hdf-to-asff";
|
|
8266
|
-
validateInputSize(input, name);
|
|
8513
|
+
validateInputSize$1(input, name);
|
|
8267
8514
|
const doc = parseHdf(input);
|
|
8268
8515
|
const baselines = asArr(doc.baselines);
|
|
8269
8516
|
if (!baselines) throw new Error(`${name}: invalid HDF structure: missing baselines field`);
|
|
@@ -8539,7 +8786,7 @@ function convertGroups(groups) {
|
|
|
8539
8786
|
* @returns HDF Results JSON string
|
|
8540
8787
|
*/
|
|
8541
8788
|
async function convertSplunkToHdf(input, converterVersion = "1.0.0") {
|
|
8542
|
-
validateInputSize(input, "splunk");
|
|
8789
|
+
validateInputSize$1(input, "splunk");
|
|
8543
8790
|
const resultsChecksum = await inputChecksum(input);
|
|
8544
8791
|
const events = parseJSON(input);
|
|
8545
8792
|
if (!Array.isArray(events) || events.length === 0) throw new Error("No Splunk events found in input");
|
|
@@ -8655,9 +8902,24 @@ function convertStatistics(stats) {
|
|
|
8655
8902
|
* The document is walked as a plain JSON tree and every key is emitted in
|
|
8656
8903
|
* source-JSON order, so the output can never silently lag a schema addition the
|
|
8657
8904
|
* way the previous hand-maintained struct mirror did (it dropped ~30 post-v3.2
|
|
8658
|
-
* fields).
|
|
8659
|
-
*
|
|
8660
|
-
*
|
|
8905
|
+
* fields). The Go converter walks the same normalized JSON in the same order and
|
|
8906
|
+
* emits identical output after the shared XML golden normalization for every
|
|
8907
|
+
* shape this repo's fixtures and parity tests cover.
|
|
8908
|
+
*
|
|
8909
|
+
* Equality is NOT guaranteed in general. The largest seam is that JSON.parse
|
|
8910
|
+
* discards information before this builder ever runs, so anything it drops is
|
|
8911
|
+
* unreachable from the serializer; shared/xml-divergence-cases.json is the
|
|
8912
|
+
* census of THAT seam, read and pinned by both languages -- a duplicate key
|
|
8913
|
+
* keeps only the last value, an integer-like key is hoisted ahead of its
|
|
8914
|
+
* siblings, and a number too large for a double arrives as Infinity with its
|
|
8915
|
+
* token text gone. Closing any of them means parsing untrusted input with a
|
|
8916
|
+
* parser that preserves order, duplicates and the number token, a worse trade.
|
|
8917
|
+
*
|
|
8918
|
+
* That table is NOT the whole list, and at least one divergence has a different
|
|
8919
|
+
* cause: the underlying builder caps nesting depth where the Go peer does not,
|
|
8920
|
+
* so a deep enough document converts there and throws here (pinned by the
|
|
8921
|
+
* nesting-depth tests in both languages). A shape outside the fixture corpus
|
|
8922
|
+
* needs checking against the peer rather than assuming it is covered.
|
|
8661
8923
|
*/
|
|
8662
8924
|
/**
|
|
8663
8925
|
* Maps a container (plural) key to the element name emitted for each of its
|
|
@@ -8688,13 +8950,33 @@ function singularFor(key) {
|
|
|
8688
8950
|
return PLURAL_TO_SINGULAR[key] ?? "item";
|
|
8689
8951
|
}
|
|
8690
8952
|
/**
|
|
8691
|
-
*
|
|
8692
|
-
*
|
|
8953
|
+
* Encode a JSON key to a legal XML element name, reporting whether it had to
|
|
8954
|
+
* change. HDF leaves object keys unconstrained — tags carry vendor-namespaced
|
|
8955
|
+
* keys like "sonarqube/hash", and additionalProperties lets a converter park
|
|
8956
|
+
* native fields anywhere — while XML constrains element names to Name, so an
|
|
8957
|
+
* unencoded key produced a document no parser could read.
|
|
8958
|
+
*
|
|
8959
|
+
* The kept set is ASCII — [A-Za-z0-9._-] — not the far wider set XML Name
|
|
8960
|
+
* allows, so the two languages agree by construction rather than by way of two
|
|
8961
|
+
* Unicode tables that can drift apart. It costs nothing here: no tag key in this
|
|
8962
|
+
* package's converter fixtures is non-ASCII. ':' is deliberately not kept —
|
|
8963
|
+
* it is a legal Name character but would invent an undeclared namespace prefix.
|
|
8964
|
+
*
|
|
8965
|
+
* Mirrored by xmlElementName in the Go converter and pinned to a shared table.
|
|
8693
8966
|
*/
|
|
8694
|
-
function
|
|
8695
|
-
|
|
8967
|
+
function xmlElementName(key) {
|
|
8968
|
+
const out = [...key].map((ch) => /[A-Za-z0-9.\-_]/.test(ch) ? ch : "_").join("");
|
|
8969
|
+
const [first = ""] = [...out];
|
|
8970
|
+
const name = /[A-Za-z_]/.test(first) ? out : `_${out}`;
|
|
8971
|
+
return [name, name !== key];
|
|
8696
8972
|
}
|
|
8697
8973
|
/**
|
|
8974
|
+
* The attribute holding the original key of a rewritten element. The prefix is
|
|
8975
|
+
* what separates an attribute from a child element in the builder; it cannot
|
|
8976
|
+
* collide with a child, because any key spelled this way is itself rewritten.
|
|
8977
|
+
*/
|
|
8978
|
+
const NAME_ATTR = "@_name";
|
|
8979
|
+
/**
|
|
8698
8980
|
* True for a JSON scalar (string, number, bool) or a null/undefined placeholder.
|
|
8699
8981
|
* Nested objects and arrays are not scalar, which selects the wrapped
|
|
8700
8982
|
* object-array rendering.
|
|
@@ -8702,37 +8984,48 @@ function wrapScalar(value) {
|
|
|
8702
8984
|
function isScalar(value) {
|
|
8703
8985
|
return value === null || value === void 0 || typeof value !== "object";
|
|
8704
8986
|
}
|
|
8987
|
+
function present(value) {
|
|
8988
|
+
return value !== null && value !== void 0;
|
|
8989
|
+
}
|
|
8705
8990
|
/**
|
|
8706
|
-
*
|
|
8707
|
-
*
|
|
8708
|
-
*
|
|
8991
|
+
* Render one JSON key as the sibling nodes it becomes, mirroring the Go
|
|
8992
|
+
* writeValue case for case. A list rather than a single node because a scalar
|
|
8993
|
+
* array repeats its key unwrapped, and because two keys can encode to one
|
|
8994
|
+
* element name — preserveOrder keeps each at its own position, which a plain
|
|
8995
|
+
* object keyed by element name could not.
|
|
8709
8996
|
*/
|
|
8710
|
-
function
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
if (
|
|
8714
|
-
|
|
8997
|
+
function nodesFor(key, value) {
|
|
8998
|
+
const [name, rewritten] = xmlElementName(key);
|
|
8999
|
+
const attrs = rewritten ? { ":@": { [NAME_ATTR]: xmlSafeText(key) } } : {};
|
|
9000
|
+
if (Array.isArray(value)) {
|
|
9001
|
+
if (value.length === 0) return [{
|
|
9002
|
+
[name]: [],
|
|
9003
|
+
...attrs
|
|
9004
|
+
}];
|
|
9005
|
+
if (value.every(isScalar)) return value.filter(present).flatMap((item) => nodesFor(key, item));
|
|
9006
|
+
const child = singularFor(key);
|
|
9007
|
+
return [{
|
|
9008
|
+
[name]: value.filter(present).flatMap((item) => nodesFor(child, item)),
|
|
9009
|
+
...attrs
|
|
9010
|
+
}];
|
|
9011
|
+
}
|
|
9012
|
+
if (typeof value === "object") return [{
|
|
9013
|
+
[name]: buildNodes(value),
|
|
9014
|
+
...attrs
|
|
9015
|
+
}];
|
|
9016
|
+
const text = typeof value === "string" ? xmlSafeText(value) : typeof value === "number" ? formatJsonNumber(value) : value;
|
|
9017
|
+
return [{
|
|
9018
|
+
[name]: [{ "#text": text }],
|
|
9019
|
+
...attrs
|
|
9020
|
+
}];
|
|
8715
9021
|
}
|
|
8716
9022
|
/**
|
|
8717
|
-
* Walk a JSON object into
|
|
8718
|
-
* source order. Null-valued keys are omitted
|
|
8719
|
-
* semantics
|
|
8720
|
-
* object array becomes a wrapper whose per-item child name comes from the
|
|
8721
|
-
* plural->singular map; an empty array becomes an empty wrapper element.
|
|
9023
|
+
* Walk a JSON object into fast-xml-parser preserveOrder nodes, emitting keys in
|
|
9024
|
+
* source order. Null-valued keys are omitted, matching the old omitempty
|
|
9025
|
+
* semantics and the Go peer.
|
|
8722
9026
|
*/
|
|
8723
|
-
function
|
|
8724
|
-
|
|
8725
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
8726
|
-
if (value === null || value === void 0) continue;
|
|
8727
|
-
if (Array.isArray(value)) {
|
|
8728
|
-
const items = value.filter((v) => v !== null && v !== void 0);
|
|
8729
|
-
if (value.length === 0) out[key] = {};
|
|
8730
|
-
else if (value.every(isScalar)) out[key] = items.map((v) => wrapScalar(v));
|
|
8731
|
-
else out[key] = { [singularFor(key)]: items.map(toXmlValue) };
|
|
8732
|
-
} else if (typeof value === "object") out[key] = buildObject(value);
|
|
8733
|
-
else out[key] = wrapScalar(value);
|
|
8734
|
-
}
|
|
8735
|
-
return out;
|
|
9027
|
+
function buildNodes(obj) {
|
|
9028
|
+
return Object.entries(obj).flatMap(([key, value]) => present(value) ? nodesFor(key, value) : []);
|
|
8736
9029
|
}
|
|
8737
9030
|
/**
|
|
8738
9031
|
* Convert HDF Results JSON to XML.
|
|
@@ -8741,11 +9034,14 @@ function buildObject(obj) {
|
|
|
8741
9034
|
* @returns XML string
|
|
8742
9035
|
*/
|
|
8743
9036
|
function convertHdfToXml(input) {
|
|
8744
|
-
validateInputSize(input, "hdf-to-xml");
|
|
9037
|
+
validateInputSize$1(input, "hdf-to-xml");
|
|
8745
9038
|
const hdf = parseHdf(input);
|
|
8746
9039
|
if (!hdf || typeof hdf !== "object" || Array.isArray(hdf) || !("baselines" in hdf)) throw new Error("Invalid HDF structure: missing baselines field");
|
|
8747
9040
|
if (!Array.isArray(hdf.baselines)) throw new Error("Invalid HDF structure: baselines must be an array");
|
|
8748
|
-
return buildXml({ HdfResults:
|
|
9041
|
+
return `${buildXml([{ HdfResults: buildNodes(hdf) }], {
|
|
9042
|
+
preserveOrder: true,
|
|
9043
|
+
attributeNamePrefix: "@_"
|
|
9044
|
+
}).trim()}\n`;
|
|
8749
9045
|
}
|
|
8750
9046
|
//#endregion
|
|
8751
9047
|
//#region converters/gitlab-to-hdf/typescript/converter.ts
|
|
@@ -8868,7 +9164,7 @@ function buildCodeDesc$5(scanType, location) {
|
|
|
8868
9164
|
}
|
|
8869
9165
|
}
|
|
8870
9166
|
async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
8871
|
-
validateInputSize(input, "gitlab");
|
|
9167
|
+
validateInputSize$1(input, "gitlab");
|
|
8872
9168
|
const resultsChecksum = await inputChecksum(input);
|
|
8873
9169
|
const report = parseJSON(input);
|
|
8874
9170
|
const scanType = report.scan?.type ?? "sast";
|
|
@@ -8970,25 +9266,14 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
8970
9266
|
const BASELINE_NAME = "Trivy Scan";
|
|
8971
9267
|
const STATIC_CCI = nistToCci(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
8972
9268
|
const CWE_ID_PATTERN$1 = /^CWE-[1-9]\d*$/;
|
|
8973
|
-
const
|
|
8974
|
-
critical: .9,
|
|
8975
|
-
high: .7,
|
|
8976
|
-
medium: .5,
|
|
8977
|
-
low: .3,
|
|
8978
|
-
unknown: .5,
|
|
8979
|
-
none: 0,
|
|
8980
|
-
info: 0
|
|
8981
|
-
};
|
|
8982
|
-
function severityToImpact$2(severity) {
|
|
8983
|
-
return IMPACT_MAP[(severity || "").toLowerCase()] ?? .5;
|
|
8984
|
-
}
|
|
9269
|
+
const SEVERITY_ALIASES = { critical: .9 };
|
|
8985
9270
|
/**
|
|
8986
9271
|
* Route Trivy output to HDF Results: native Trivy JSON (SchemaVersion 2) is
|
|
8987
9272
|
* parsed here; SARIF / CycloneDX / ASFF / GitLab are delegated to their
|
|
8988
9273
|
* converters. TypeScript peer of shared/go/../trivy-to-hdf converter.go.
|
|
8989
9274
|
*/
|
|
8990
9275
|
async function convertTrivyToHdf(input, converterVersion = "1.0.0") {
|
|
8991
|
-
validateInputSize(input, "trivy");
|
|
9276
|
+
validateInputSize$1(input, "trivy");
|
|
8992
9277
|
if (!input?.trim()) throw new Error("trivy: empty input");
|
|
8993
9278
|
const probe = parseJSON(input);
|
|
8994
9279
|
if (typeof probe !== "object" || probe === null || Array.isArray(probe)) throw new Error("trivy: input is not a JSON object");
|
|
@@ -9031,7 +9316,7 @@ async function convertNative(input, converterVersion) {
|
|
|
9031
9316
|
function convertVuln(v, res, startTime) {
|
|
9032
9317
|
const descriptions = [{
|
|
9033
9318
|
label: "default",
|
|
9034
|
-
data: firstNonEmpty(v.Description, v.Title, v.VulnerabilityID)
|
|
9319
|
+
data: firstNonEmpty(v.Description ?? "", v.Title ?? "", v.VulnerabilityID ?? "")
|
|
9035
9320
|
}];
|
|
9036
9321
|
if (v.FixedVersion) descriptions.push({
|
|
9037
9322
|
label: "fix",
|
|
@@ -9049,7 +9334,7 @@ function convertVuln(v, res, startTime) {
|
|
|
9049
9334
|
id: `Trivy/${v.VulnerabilityID ?? ""}`,
|
|
9050
9335
|
title: `Trivy found ${v.VulnerabilityID ?? ""} in ${pkgLabel(v.PkgName, v.InstalledVersion)}`,
|
|
9051
9336
|
descriptions,
|
|
9052
|
-
impact:
|
|
9337
|
+
impact: severityToImpactWithAliases(v.Severity, SEVERITY_ALIASES, .5),
|
|
9053
9338
|
tags,
|
|
9054
9339
|
cwe: filterCwes(v.CweIDs),
|
|
9055
9340
|
cvss: buildCvssEntries$4(v.CVSS),
|
|
@@ -9061,18 +9346,18 @@ function convertVuln(v, res, startTime) {
|
|
|
9061
9346
|
status: ResultStatus.Failed,
|
|
9062
9347
|
codeDesc: buildVulnCodeDesc(v, res),
|
|
9063
9348
|
startTime,
|
|
9064
|
-
message: `Severity: ${firstNonEmpty(v.Severity, "UNKNOWN")}`
|
|
9349
|
+
message: `Severity: ${firstNonEmpty(v.Severity ?? "", "UNKNOWN")}`
|
|
9065
9350
|
}]
|
|
9066
9351
|
};
|
|
9067
|
-
const ap =
|
|
9068
|
-
if (ap
|
|
9352
|
+
const ap = buildTrivyAffectedPackage(v);
|
|
9353
|
+
if (ap) req.affectedPackages = [ap];
|
|
9069
9354
|
if (v.PkgPath) req.sourceLocation = { ref: v.PkgPath };
|
|
9070
9355
|
return req;
|
|
9071
9356
|
}
|
|
9072
9357
|
function convertMisconf(m, res, startTime) {
|
|
9073
9358
|
const descriptions = [{
|
|
9074
9359
|
label: "default",
|
|
9075
|
-
data: firstNonEmpty(m.Description, m.Message, m.Title)
|
|
9360
|
+
data: firstNonEmpty(m.Description ?? "", m.Message ?? "", m.Title ?? "")
|
|
9076
9361
|
}];
|
|
9077
9362
|
if (m.Resolution) descriptions.push({
|
|
9078
9363
|
label: "fix",
|
|
@@ -9084,10 +9369,10 @@ function convertMisconf(m, res, startTime) {
|
|
|
9084
9369
|
});
|
|
9085
9370
|
markUnratedSeverity(tags, m.Severity);
|
|
9086
9371
|
const req = {
|
|
9087
|
-
id: `Trivy/${firstNonEmpty(m.ID, m.AVDID)}`,
|
|
9372
|
+
id: `Trivy/${firstNonEmpty(m.ID ?? "", m.AVDID ?? "")}`,
|
|
9088
9373
|
title: m.Title,
|
|
9089
9374
|
descriptions,
|
|
9090
|
-
impact:
|
|
9375
|
+
impact: severityToImpactWithAliases(m.Severity, SEVERITY_ALIASES, .5),
|
|
9091
9376
|
tags,
|
|
9092
9377
|
refs: buildRefs$5([m.PrimaryURL, ...m.References ?? []]),
|
|
9093
9378
|
code: JSON.stringify(m, null, 2),
|
|
@@ -9095,7 +9380,7 @@ function convertMisconf(m, res, startTime) {
|
|
|
9095
9380
|
verificationMethod: VerificationMethodEnum.Automated,
|
|
9096
9381
|
results: [{
|
|
9097
9382
|
status: misconfStatus(m.Status),
|
|
9098
|
-
codeDesc: firstNonEmpty(m.Message, m.Title),
|
|
9383
|
+
codeDesc: firstNonEmpty(m.Message ?? "", m.Title ?? ""),
|
|
9099
9384
|
startTime
|
|
9100
9385
|
}]
|
|
9101
9386
|
};
|
|
@@ -9117,9 +9402,9 @@ function convertSecret(s, res, startTime) {
|
|
|
9117
9402
|
title: s.Title,
|
|
9118
9403
|
descriptions: [{
|
|
9119
9404
|
label: "default",
|
|
9120
|
-
data: `${firstNonEmpty(s.Title, s.RuleID)} detected in ${res.Target ?? ""} (value redacted by Trivy).`
|
|
9405
|
+
data: `${firstNonEmpty(s.Title ?? "", s.RuleID ?? "")} detected in ${res.Target ?? ""} (value redacted by Trivy).`
|
|
9121
9406
|
}],
|
|
9122
|
-
impact:
|
|
9407
|
+
impact: severityToImpactWithAliases(s.Severity, SEVERITY_ALIASES, .5),
|
|
9123
9408
|
tags,
|
|
9124
9409
|
code: JSON.stringify(s, null, 2),
|
|
9125
9410
|
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
@@ -9151,7 +9436,7 @@ function convertLicense(l, res, startTime) {
|
|
|
9151
9436
|
label: "default",
|
|
9152
9437
|
data: `Package ${l.PkgName ?? ""} uses the ${l.Name ?? ""} license (category: ${l.Category ?? ""}).`
|
|
9153
9438
|
}],
|
|
9154
|
-
impact:
|
|
9439
|
+
impact: severityToImpactWithAliases(l.Severity, SEVERITY_ALIASES, .5),
|
|
9155
9440
|
tags,
|
|
9156
9441
|
refs: buildRefs$5([l.Link]),
|
|
9157
9442
|
code: JSON.stringify(l, null, 2),
|
|
@@ -9232,33 +9517,19 @@ function buildCvssEntries$4(m) {
|
|
|
9232
9517
|
}
|
|
9233
9518
|
return entries.length > 0 ? entries : void 0;
|
|
9234
9519
|
}
|
|
9235
|
-
|
|
9236
|
-
deb: Ecosystem.Deb,
|
|
9237
|
-
rpm: Ecosystem.RPM,
|
|
9238
|
-
maven: Ecosystem.Maven,
|
|
9239
|
-
npm: Ecosystem.Npm,
|
|
9240
|
-
pypi: Ecosystem.Pypi,
|
|
9241
|
-
gem: Ecosystem.Gem,
|
|
9242
|
-
cargo: Ecosystem.Cargo,
|
|
9243
|
-
golang: Ecosystem.Go,
|
|
9244
|
-
nuget: Ecosystem.Nuget
|
|
9245
|
-
};
|
|
9246
|
-
function buildAffectedPackage$1(v) {
|
|
9247
|
-
const ap = {};
|
|
9248
|
-
if (v.PkgName) ap.name = v.PkgName;
|
|
9249
|
-
if (v.InstalledVersion) ap.version = v.InstalledVersion;
|
|
9250
|
-
if (v.FixedVersion) ap.fixedInVersion = v.FixedVersion;
|
|
9520
|
+
function buildTrivyAffectedPackage(v) {
|
|
9251
9521
|
const purl = v.PkgIdentifier?.PURL;
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9522
|
+
return buildAffectedPackage$1({
|
|
9523
|
+
name: v.PkgName,
|
|
9524
|
+
version: v.InstalledVersion,
|
|
9525
|
+
fixedInVersion: v.FixedVersion,
|
|
9526
|
+
purl,
|
|
9527
|
+
ecosystem: purl ? ecosystemFromPurl(purl) : void 0
|
|
9528
|
+
});
|
|
9258
9529
|
}
|
|
9259
9530
|
function ecosystemFromPurl(purl) {
|
|
9260
|
-
const
|
|
9261
|
-
return
|
|
9531
|
+
const eco = ecosystemFromPurlType(purl.replace(/^pkg:/, "").split(/[/@?]/)[0] ?? "");
|
|
9532
|
+
return eco === Ecosystem.Generic ? void 0 : eco;
|
|
9262
9533
|
}
|
|
9263
9534
|
function buildRefs$5(urls) {
|
|
9264
9535
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -9285,10 +9556,6 @@ function digestPart(ref) {
|
|
|
9285
9556
|
const at = ref.lastIndexOf("@");
|
|
9286
9557
|
return at >= 0 ? ref.slice(at + 1) : ref;
|
|
9287
9558
|
}
|
|
9288
|
-
function firstNonEmpty(...vals) {
|
|
9289
|
-
for (const v of vals) if (v) return v;
|
|
9290
|
-
return "";
|
|
9291
|
-
}
|
|
9292
9559
|
function putIf(m, key, val) {
|
|
9293
9560
|
if (val) m[key] = val;
|
|
9294
9561
|
}
|
|
@@ -9448,7 +9715,7 @@ function buildRequirement$12(reqID, findings) {
|
|
|
9448
9715
|
* @returns HDF JSON string
|
|
9449
9716
|
*/
|
|
9450
9717
|
async function convertTrufflehogToHdf(input, converterVersion = "1.0.0") {
|
|
9451
|
-
validateInputSize(input, "trufflehog");
|
|
9718
|
+
validateInputSize$1(input, "trufflehog");
|
|
9452
9719
|
const findings = !input || input.trim().length === 0 ? [] : parseFindings(input);
|
|
9453
9720
|
const resultsChecksum = await inputChecksum(input);
|
|
9454
9721
|
const limitedFindings = limitArrayWithWarning(findings, "finding");
|
|
@@ -9581,7 +9848,7 @@ function investigateReason(reason) {
|
|
|
9581
9848
|
* @returns HDF JSON string
|
|
9582
9849
|
*/
|
|
9583
9850
|
async function convertHipcheckToHdf(input, converterVersion = "1.0.0") {
|
|
9584
|
-
validateInputSize(input, "hipcheck");
|
|
9851
|
+
validateInputSize$1(input, "hipcheck");
|
|
9585
9852
|
if (!input || input.trim().length === 0) throw new Error("hipcheck: empty input");
|
|
9586
9853
|
const report = parseJSON(input);
|
|
9587
9854
|
if (!report.hipcheck_version && !report.repo_name) throw new Error("hipcheck: input does not look like a Hipcheck report");
|
|
@@ -9704,7 +9971,7 @@ function buildIssueCode(issue) {
|
|
|
9704
9971
|
*/
|
|
9705
9972
|
async function convertBurpsuiteToHdf(input, converterVersion = "1.0.0") {
|
|
9706
9973
|
if (!input || input.trim().length === 0) throw new Error("burpsuite: empty input");
|
|
9707
|
-
validateInputSize(input, "burpsuite");
|
|
9974
|
+
validateInputSize$1(input, "burpsuite");
|
|
9708
9975
|
const resultsChecksum = await inputChecksum(input);
|
|
9709
9976
|
const parsed = parseXmlWithArrays(input, ["issue"]);
|
|
9710
9977
|
if (!parsed.issues) throw new Error("burpsuite: invalid XML — missing <issues> root element");
|
|
@@ -10010,7 +10277,7 @@ function buildRequirement$10(checkID, findings, hasStatus) {
|
|
|
10010
10277
|
*/
|
|
10011
10278
|
async function convertDbprotectToHdf(input, converterVersion = "1.0.0") {
|
|
10012
10279
|
if (!input || input.trim().length === 0) throw new Error("dbprotect: empty input");
|
|
10013
|
-
validateInputSize(input, "dbprotect");
|
|
10280
|
+
validateInputSize$1(input, "dbprotect");
|
|
10014
10281
|
const resultsChecksum = await inputChecksum(input);
|
|
10015
10282
|
const parsed = parseXmlWithArrays(input, [
|
|
10016
10283
|
"item",
|
|
@@ -10093,15 +10360,15 @@ function buildCvss$1(vuln) {
|
|
|
10093
10360
|
source: vuln.cve ?? vuln.id
|
|
10094
10361
|
});
|
|
10095
10362
|
}
|
|
10096
|
-
const CWE_REGEX = /cwe[-_]?(\d+)/gi;
|
|
10097
10363
|
/**
|
|
10098
|
-
* Extracts canonical CWE-N identifiers from a free-form string
|
|
10364
|
+
* Extracts canonical CWE-N identifiers from a free-form string, deduplicated
|
|
10365
|
+
* in source order.
|
|
10099
10366
|
*/
|
|
10100
10367
|
function parseCwes(raw) {
|
|
10101
10368
|
if (!raw) return [];
|
|
10102
10369
|
const out = [];
|
|
10103
10370
|
const seen = /* @__PURE__ */ new Set();
|
|
10104
|
-
for (const m of raw.matchAll(
|
|
10371
|
+
for (const m of raw.matchAll(CWE_PATTERN$1)) {
|
|
10105
10372
|
const id = `CWE-${m[1]}`;
|
|
10106
10373
|
if (seen.has(id)) continue;
|
|
10107
10374
|
seen.add(id);
|
|
@@ -10128,7 +10395,9 @@ function debDistro(distro) {
|
|
|
10128
10395
|
}
|
|
10129
10396
|
/**
|
|
10130
10397
|
* Maps a Twistlock package type plus the result's distro string to a schema
|
|
10131
|
-
* Ecosystem value.
|
|
10398
|
+
* Ecosystem value. Only the Twistlock spellings that differ from the PURL type
|
|
10399
|
+
* vocabulary are listed; the rest defer to the shared resolver, which falls
|
|
10400
|
+
* back to 'generic' for unknown types.
|
|
10132
10401
|
*/
|
|
10133
10402
|
function resolveEcosystem(packageType, distro) {
|
|
10134
10403
|
const t = (packageType ?? "").toLowerCase();
|
|
@@ -10138,19 +10407,10 @@ function resolveEcosystem(packageType, distro) {
|
|
|
10138
10407
|
if (rhelDistro(d)) return Ecosystem.RPM;
|
|
10139
10408
|
if (debDistro(d)) return Ecosystem.Deb;
|
|
10140
10409
|
return Ecosystem.Generic;
|
|
10141
|
-
case "
|
|
10142
|
-
case "
|
|
10143
|
-
case "
|
|
10144
|
-
|
|
10145
|
-
case "python":
|
|
10146
|
-
case "pypi": return Ecosystem.Pypi;
|
|
10147
|
-
case "nodejs":
|
|
10148
|
-
case "npm": return Ecosystem.Npm;
|
|
10149
|
-
case "gem": return Ecosystem.Gem;
|
|
10150
|
-
case "nuget": return Ecosystem.Nuget;
|
|
10151
|
-
case "go": return Ecosystem.Go;
|
|
10152
|
-
case "cargo": return Ecosystem.Cargo;
|
|
10153
|
-
default: return Ecosystem.Generic;
|
|
10410
|
+
case "jar": return Ecosystem.Maven;
|
|
10411
|
+
case "python": return Ecosystem.Pypi;
|
|
10412
|
+
case "nodejs": return Ecosystem.Npm;
|
|
10413
|
+
default: return ecosystemFromPurlType(t);
|
|
10154
10414
|
}
|
|
10155
10415
|
}
|
|
10156
10416
|
const FIX_VERSION_REGEX = /\d+(?:\.\d+)+[A-Za-z0-9._+\-]*/;
|
|
@@ -10165,20 +10425,18 @@ function extractFixedInVersion(vuln) {
|
|
|
10165
10425
|
return m ? m[0] : "";
|
|
10166
10426
|
}
|
|
10167
10427
|
/**
|
|
10168
|
-
* Builds an AffectedPackage entry from per-vulnerability fields.
|
|
10169
|
-
*
|
|
10428
|
+
* Builds an AffectedPackage entry from per-vulnerability fields. Twistlock
|
|
10429
|
+
* carries no purl or cpe, so the shared builder returns undefined unless the
|
|
10430
|
+
* name + version + ecosystem triple is complete.
|
|
10170
10431
|
*/
|
|
10171
10432
|
function buildAffectedPackage(vuln, packageTypes, distro) {
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
const pkg = {
|
|
10433
|
+
const pkgType = vuln.packageType ?? packageTypes.get(vuln.packageName ?? "");
|
|
10434
|
+
return buildAffectedPackage$1({
|
|
10175
10435
|
name: vuln.packageName,
|
|
10176
10436
|
version: vuln.packageVersion,
|
|
10177
|
-
ecosystem: resolveEcosystem(pkgType, distro)
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
if (fixed) pkg.fixedInVersion = fixed;
|
|
10181
|
-
return pkg;
|
|
10437
|
+
ecosystem: resolveEcosystem(pkgType, distro),
|
|
10438
|
+
fixedInVersion: extractFixedInVersion(vuln)
|
|
10439
|
+
});
|
|
10182
10440
|
}
|
|
10183
10441
|
/**
|
|
10184
10442
|
* Indexes packageName → packageType from the result-level packages array.
|
|
@@ -10308,7 +10566,7 @@ function convertSingleResult(result, resultsChecksum) {
|
|
|
10308
10566
|
*/
|
|
10309
10567
|
async function convertTwistlockToHdf(input, converterVersion = "1.0.0") {
|
|
10310
10568
|
if (!input || input.trim().length === 0) throw new Error("twistlock: empty input");
|
|
10311
|
-
validateInputSize(input, "twistlock");
|
|
10569
|
+
validateInputSize$1(input, "twistlock");
|
|
10312
10570
|
const resultsChecksum = await inputChecksum(input);
|
|
10313
10571
|
const parsed = parseJSON(input);
|
|
10314
10572
|
if (!parsed || typeof parsed !== "object") throw new Error("twistlock: invalid JSON");
|
|
@@ -10515,7 +10773,7 @@ function buildAffectedPackageFromComponent(c) {
|
|
|
10515
10773
|
let ecosystem;
|
|
10516
10774
|
if (c.purl) ecosystem = ecosystemFromPurlType(parsePurl(c.purl)?.type);
|
|
10517
10775
|
else if (c.name && c.version) ecosystem = ecosystemFromPurlType(void 0);
|
|
10518
|
-
return buildAffectedPackage$
|
|
10776
|
+
return buildAffectedPackage$1({
|
|
10519
10777
|
name: c.name,
|
|
10520
10778
|
version: c.version,
|
|
10521
10779
|
ecosystem,
|
|
@@ -10532,7 +10790,7 @@ function buildAffectedPackageFromComponent(c) {
|
|
|
10532
10790
|
*/
|
|
10533
10791
|
async function convertDeptrackToHdf(input, converterVersion = "1.0.0") {
|
|
10534
10792
|
if (!input || input.trim().length === 0) throw new Error("deptrack: empty input");
|
|
10535
|
-
validateInputSize(input, "deptrack");
|
|
10793
|
+
validateInputSize$1(input, "deptrack");
|
|
10536
10794
|
const resultsChecksum = await inputChecksum(input);
|
|
10537
10795
|
const parsed = parseJSON(input);
|
|
10538
10796
|
if (!parsed || typeof parsed !== "object") throw new Error("deptrack: invalid JSON");
|
|
@@ -10787,7 +11045,7 @@ function buildAffectedPackageFromEntry(entry) {
|
|
|
10787
11045
|
ecosystem = ecosystemFromXraySource(parsed.scheme);
|
|
10788
11046
|
}
|
|
10789
11047
|
const fixed = entry.component_versions?.fixed_versions?.[0];
|
|
10790
|
-
return buildAffectedPackage$
|
|
11048
|
+
return buildAffectedPackage$1({
|
|
10791
11049
|
name,
|
|
10792
11050
|
version,
|
|
10793
11051
|
ecosystem: ecosystem ?? (name ? Ecosystem.Generic : void 0),
|
|
@@ -10802,7 +11060,7 @@ function buildAffectedPackageFromEntry(entry) {
|
|
|
10802
11060
|
*/
|
|
10803
11061
|
async function convertJfrogXrayToHdf(input, converterVersion = "1.0.0") {
|
|
10804
11062
|
if (!input || input.trim().length === 0) throw new Error("jfrog-xray: empty input");
|
|
10805
|
-
validateInputSize(input, "jfrog-xray");
|
|
11063
|
+
validateInputSize$1(input, "jfrog-xray");
|
|
10806
11064
|
const resultsChecksum = await inputChecksum(input);
|
|
10807
11065
|
const parsed = parseJSON(input);
|
|
10808
11066
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.data)) throw new Error("jfrog-xray: invalid JSON structure");
|
|
@@ -11019,7 +11277,7 @@ function buildRequirement$6(vuln, scanTime, ml) {
|
|
|
11019
11277
|
const refs = buildRefs$3(vuln);
|
|
11020
11278
|
if (refs) req.refs = refs;
|
|
11021
11279
|
const cpe23 = (vuln.cpes ?? []).find((c) => /^cpe:2\.3:[aho]:/.test(c));
|
|
11022
|
-
const pkg = buildAffectedPackage$
|
|
11280
|
+
const pkg = buildAffectedPackage$1({
|
|
11023
11281
|
name: vuln.package_name,
|
|
11024
11282
|
version: vuln.package_version,
|
|
11025
11283
|
ecosystem: vuln.package_name && vuln.package_version ? Ecosystem.Generic : void 0,
|
|
@@ -11104,7 +11362,7 @@ function buildComponent(report) {
|
|
|
11104
11362
|
*/
|
|
11105
11363
|
async function convertNeuvectorToHdf(input, converterVersion = "1.0.0") {
|
|
11106
11364
|
if (!input || input.trim().length === 0) throw new Error("neuvector: empty input");
|
|
11107
|
-
validateInputSize(input, "neuvector");
|
|
11365
|
+
validateInputSize$1(input, "neuvector");
|
|
11108
11366
|
const scan = parseJSON(input);
|
|
11109
11367
|
if (!scan || typeof scan !== "object" || !("report" in scan)) throw new Error("neuvector: invalid JSON structure");
|
|
11110
11368
|
const resultsChecksum = await inputChecksum(input);
|
|
@@ -11346,7 +11604,7 @@ function buildRequirement$5(desc, vulns, snippetMap, startTimeStr) {
|
|
|
11346
11604
|
*/
|
|
11347
11605
|
async function convertFortifyToHdf(input, converterVersion = "1.0.0") {
|
|
11348
11606
|
if (!input || input.trim().length === 0) throw new Error("fortify: empty input");
|
|
11349
|
-
validateInputSize(input, "fortify");
|
|
11607
|
+
validateInputSize$1(input, "fortify");
|
|
11350
11608
|
const resultsChecksum = await inputChecksum(input);
|
|
11351
11609
|
const parsed = parseXml(input, { stopNodes: ["FVDL.Description.Abstract", "FVDL.Description.Explanation"] });
|
|
11352
11610
|
if (!parsed.FVDL) throw new Error("fortify: invalid FVDL — missing <FVDL> root element");
|
|
@@ -11592,7 +11850,7 @@ function buildAffectedPackageFromRecord(rec) {
|
|
|
11592
11850
|
const version = rec["Package Version"];
|
|
11593
11851
|
if (!name || !version) return void 0;
|
|
11594
11852
|
const fixMatch = rec["Fix Status"] ? FIX_VERSION_PATTERN.exec(rec["Fix Status"]) : null;
|
|
11595
|
-
return buildAffectedPackage$
|
|
11853
|
+
return buildAffectedPackage$1({
|
|
11596
11854
|
name,
|
|
11597
11855
|
version,
|
|
11598
11856
|
ecosystem: ecosystemFromDistro(rec.Distro),
|
|
@@ -11629,7 +11887,7 @@ function buildBaseline(hostname, records, resultsChecksum, scanTime) {
|
|
|
11629
11887
|
*/
|
|
11630
11888
|
async function convertPrismaToHdf(input, converterVersion = "1.0.0") {
|
|
11631
11889
|
if (!input || input.trim().length === 0) throw new Error("prisma: empty input");
|
|
11632
|
-
validateInputSize(input, "prisma");
|
|
11890
|
+
validateInputSize$1(input, "prisma");
|
|
11633
11891
|
const headers = (input.split(/\r?\n/, 1)[0] ?? "").split(",").map((h) => h.trim());
|
|
11634
11892
|
for (const col of REQUIRED_COLUMNS) if (!headers.includes(col)) throw new Error(`prisma: missing required CSV column "${col}"`);
|
|
11635
11893
|
const records = parseCsv(input);
|
|
@@ -11731,12 +11989,13 @@ function baseScoreFromBlock(block) {
|
|
|
11731
11989
|
*/
|
|
11732
11990
|
function buildNetsparkerCvss(classification) {
|
|
11733
11991
|
const out = [];
|
|
11734
|
-
|
|
11992
|
+
const blocks = [[classification?.cvss, Version.The30], [classification?.cvss31, Version.The31]];
|
|
11993
|
+
for (const [block, elementVersion] of blocks) {
|
|
11735
11994
|
const baseScore = baseScoreFromBlock(block);
|
|
11736
11995
|
const vector = block?.vector ?? "";
|
|
11737
11996
|
if (!vector && baseScore === void 0) continue;
|
|
11738
11997
|
out.push(buildCvss$3({
|
|
11739
|
-
version: cvssVersionFromVector(vector),
|
|
11998
|
+
version: cvssVersionFromVector(vector, elementVersion),
|
|
11740
11999
|
baseScore,
|
|
11741
12000
|
baseVector: vector
|
|
11742
12001
|
}));
|
|
@@ -11857,7 +12116,7 @@ function buildRequirement$3(vuln, initiated) {
|
|
|
11857
12116
|
*/
|
|
11858
12117
|
async function convertNetsparkerToHdf(input, converterVersion = "1.0.0") {
|
|
11859
12118
|
if (!input || input.trim().length === 0) throw new Error("netsparker: empty input");
|
|
11860
|
-
validateInputSize(input, "netsparker");
|
|
12119
|
+
validateInputSize$1(input, "netsparker");
|
|
11861
12120
|
const resultsChecksum = await inputChecksum(input);
|
|
11862
12121
|
const parsed = parseXmlWithArrays(input, [
|
|
11863
12122
|
"vulnerability",
|
|
@@ -12018,7 +12277,7 @@ function buildRequirement$2(ruleID, finding, startTime) {
|
|
|
12018
12277
|
*/
|
|
12019
12278
|
async function convertScoutsuiteToHdf(input, converterVersion = "1.0.0") {
|
|
12020
12279
|
if (!input || input.trim().length === 0) throw new Error("scoutsuite: empty input");
|
|
12021
|
-
validateInputSize(input, "scoutsuite");
|
|
12280
|
+
validateInputSize$1(input, "scoutsuite");
|
|
12022
12281
|
const jsonStr = stripJSPrefix(input);
|
|
12023
12282
|
const resultsChecksum = await inputChecksum(jsonStr);
|
|
12024
12283
|
const report = parseJSON(jsonStr);
|
|
@@ -12230,7 +12489,7 @@ function buildScannerBaseline(scannerName, results, shaMap, resultsChecksum) {
|
|
|
12230
12489
|
*/
|
|
12231
12490
|
async function convertConveyorToHdf(input, converterVersion = "1.0.0") {
|
|
12232
12491
|
if (!input || input.trim().length === 0) throw new Error("conveyor: empty input");
|
|
12233
|
-
validateInputSize(input, "conveyor");
|
|
12492
|
+
validateInputSize$1(input, "conveyor");
|
|
12234
12493
|
const data = parseJSON(input);
|
|
12235
12494
|
if (!data || typeof data !== "object") throw new Error("conveyor: invalid JSON");
|
|
12236
12495
|
if (!data.api_response) throw new Error("conveyor: missing api_response field");
|
|
@@ -12702,7 +12961,7 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
|
|
|
12702
12961
|
*/
|
|
12703
12962
|
async function convertVeracodeToHdf(input, converterVersion = "1.0.0") {
|
|
12704
12963
|
if (!input || input.trim().length === 0) throw new Error("veracode: empty input");
|
|
12705
|
-
validateInputSize(input, "veracode");
|
|
12964
|
+
validateInputSize$1(input, "veracode");
|
|
12706
12965
|
const parsed = parseXml(input, {
|
|
12707
12966
|
attributeNamePrefix: "@_",
|
|
12708
12967
|
trimValues: false
|
|
@@ -12858,7 +13117,7 @@ function buildRequirement$1(cs, profiles, createdDateTime) {
|
|
|
12858
13117
|
*/
|
|
12859
13118
|
async function convertMsftSecureScoreToHdf(input, converterVersion = "1.0.0") {
|
|
12860
13119
|
if (!input || input.trim().length === 0) throw new Error("msft-secure-score: empty input");
|
|
12861
|
-
validateInputSize(input, "msft-secure-score");
|
|
13120
|
+
validateInputSize$1(input, "msft-secure-score");
|
|
12862
13121
|
const combined = parseJSON(input);
|
|
12863
13122
|
if (!combined || typeof combined !== "object") throw new Error("msft-secure-score: invalid JSON");
|
|
12864
13123
|
if (!combined.secureScore?.value) throw new Error("msft-secure-score: missing secureScore.value");
|
|
@@ -12905,7 +13164,7 @@ async function convertMsftSecureScoreToHdf(input, converterVersion = "1.0.0") {
|
|
|
12905
13164
|
* the output with MSDO-specific metadata.
|
|
12906
13165
|
*/
|
|
12907
13166
|
async function convertMsftDefenderDevopsToHdf(input, converterVersion = "1.0.0") {
|
|
12908
|
-
validateInputSize(input, "msft-defender-devops");
|
|
13167
|
+
validateInputSize$1(input, "msft-defender-devops");
|
|
12909
13168
|
const raw = parseJSON(input);
|
|
12910
13169
|
if (!raw || !Array.isArray(raw.runs)) throw new Error("Invalid MSDO SARIF structure: missing or invalid runs field");
|
|
12911
13170
|
const { components, runEnrichments } = extractEnrichments(raw);
|
|
@@ -13070,7 +13329,7 @@ function buildResultFromAssessment(a, scanTime) {
|
|
|
13070
13329
|
* @returns HDF JSON string
|
|
13071
13330
|
*/
|
|
13072
13331
|
async function convertMsftDefenderCloudToHdf(input, converterVersion = "1.0.0") {
|
|
13073
|
-
validateInputSize(input, "msft-defender-cloud");
|
|
13332
|
+
validateInputSize$1(input, "msft-defender-cloud");
|
|
13074
13333
|
const scanTime = /* @__PURE__ */ new Date();
|
|
13075
13334
|
const resultsChecksum = await inputChecksum(input);
|
|
13076
13335
|
const raw = parseJSON(input);
|
|
@@ -13180,9 +13439,7 @@ function buildTriageOverride(alert, startTime) {
|
|
|
13180
13439
|
default: return null;
|
|
13181
13440
|
}
|
|
13182
13441
|
const appliedAt = (alert.resolvedDateTime ? parseTimestamp(alert.resolvedDateTime) : null) ?? (alert.lastUpdateDateTime ? parseTimestamp(alert.lastUpdateDateTime) : null) ?? startTime;
|
|
13183
|
-
const expiresAt =
|
|
13184
|
-
expiresAt.setTime(appliedAt.getTime());
|
|
13185
|
-
expiresAt.setUTCFullYear(expiresAt.getUTCFullYear() + 1);
|
|
13442
|
+
const expiresAt = defaultOverrideExpiry(appliedAt);
|
|
13186
13443
|
return {
|
|
13187
13444
|
override: {
|
|
13188
13445
|
type: disposition,
|
|
@@ -13356,7 +13613,7 @@ function alertToRequirement(alert, scanTime) {
|
|
|
13356
13613
|
* @returns HDF JSON string
|
|
13357
13614
|
*/
|
|
13358
13615
|
async function convertMsftDefenderEndpointToHdf(input, converterVersion = "1.0.0") {
|
|
13359
|
-
validateInputSize(input, "msft-defender-endpoint");
|
|
13616
|
+
validateInputSize$1(input, "msft-defender-endpoint");
|
|
13360
13617
|
const resultsChecksum = await inputChecksum(input);
|
|
13361
13618
|
const response = parseJSON(input);
|
|
13362
13619
|
if (!response || typeof response !== "object") throw new Error("Invalid Microsoft Defender for Endpoint structure: not a valid JSON object");
|
|
@@ -13408,7 +13665,7 @@ const BUILD_OPTIONS = {
|
|
|
13408
13665
|
* @returns XCCDF 1.2 XML string
|
|
13409
13666
|
*/
|
|
13410
13667
|
function convertHdfToXccdf(input) {
|
|
13411
|
-
validateInputSize(input, "hdf-to-xccdf");
|
|
13668
|
+
validateInputSize$1(input, "hdf-to-xccdf");
|
|
13412
13669
|
const hdfData = parseHdf(input);
|
|
13413
13670
|
if (!hdfData || typeof hdfData !== "object" || !("baselines" in hdfData)) throw new Error("Invalid HDF structure: missing baselines field");
|
|
13414
13671
|
if (!Array.isArray(hdfData.baselines)) throw new Error("Invalid HDF structure: baselines must be an array");
|
|
@@ -13418,12 +13675,14 @@ function convertHdfToXccdf(input) {
|
|
|
13418
13675
|
function wrap(value) {
|
|
13419
13676
|
return { "#text": value };
|
|
13420
13677
|
}
|
|
13421
|
-
/** Map HDF impact (0.0-1.0) to XCCDF severity
|
|
13678
|
+
/** Map HDF impact (0.0-1.0) to XCCDF severity via the shared band mapper. The
|
|
13679
|
+
* XCCDF vocabulary has no critical band, so critical folds into high, and its
|
|
13680
|
+
* zero band is spelled 'info'. */
|
|
13422
13681
|
function impactToSeverity$2(impact) {
|
|
13423
|
-
|
|
13424
|
-
if (
|
|
13425
|
-
if (
|
|
13426
|
-
return
|
|
13682
|
+
const sev = impactToSeverity(impact);
|
|
13683
|
+
if (sev === "critical") return "high";
|
|
13684
|
+
if (sev === "informational") return "info";
|
|
13685
|
+
return sev;
|
|
13427
13686
|
}
|
|
13428
13687
|
/** Map HDF result status to XCCDF result value. */
|
|
13429
13688
|
function hdfStatusToXccdf(status) {
|
|
@@ -13441,7 +13700,44 @@ function findDescription(descriptions, label) {
|
|
|
13441
13700
|
}
|
|
13442
13701
|
/** Replace characters not valid in XCCDF IDs with underscores. */
|
|
13443
13702
|
function sanitizeXccdfId(id) {
|
|
13444
|
-
return id.replace(/[^a-zA-Z0-9_.\-]/
|
|
13703
|
+
return id.replace(/[^a-zA-Z0-9_.\-]/gu, "_");
|
|
13704
|
+
}
|
|
13705
|
+
/**
|
|
13706
|
+
* Supply the trailing name segment that benchmarkIdType and profileIdType
|
|
13707
|
+
* require via their .+ — a baseline with an empty name is valid HDF but
|
|
13708
|
+
* produced "xccdf_hdf_benchmark_", which the XSD rejects.
|
|
13709
|
+
*/
|
|
13710
|
+
function xccdfNamePart(name) {
|
|
13711
|
+
return name === "" ? "unnamed" : name;
|
|
13712
|
+
}
|
|
13713
|
+
/**
|
|
13714
|
+
* Render an HDF tags.gid as a groupIdType: an NCName that must also match
|
|
13715
|
+
* xccdf_[^_]+_group_.+ (xccdf_1.2.xsd:821). HDF constrains gid not at all, and
|
|
13716
|
+
* most real data does not satisfy it — 783 of the 1216 distinct gid values in
|
|
13717
|
+
* this package's converter fixtures are bare STIG ids like "V-257777" — so copying one
|
|
13718
|
+
* through produced XSD-invalid output from valid HDF.
|
|
13719
|
+
*
|
|
13720
|
+
* A gid that already conforms is a genuine XCCDF group id (STIG content carries
|
|
13721
|
+
* "xccdf_mil.disa.stig_group_V-204393") and is passed through; anything else is
|
|
13722
|
+
* sanitized and namespaced under this converter, matching what the benchmark,
|
|
13723
|
+
* profile, and rule ids already do.
|
|
13724
|
+
*
|
|
13725
|
+
* Mirrored by xccdfGroupID in the Go converter. Exported so the shared table
|
|
13726
|
+
* can be asserted against the encoder directly, including the empty gid the
|
|
13727
|
+
* converter itself skips.
|
|
13728
|
+
*/
|
|
13729
|
+
function xccdfGroupId(gid) {
|
|
13730
|
+
if (isXccdfGroupId(gid)) return gid;
|
|
13731
|
+
return sanitizeXccdfId("xccdf_hdf_group_" + (gid === "" ? "unnamed" : gid));
|
|
13732
|
+
}
|
|
13733
|
+
/**
|
|
13734
|
+
* Whether a value already satisfies groupIdType. The character test is
|
|
13735
|
+
* ASCII-only, deliberately narrower than NCName: a conforming non-ASCII id is
|
|
13736
|
+
* re-encoded rather than trusted, which errs toward valid output and keeps the
|
|
13737
|
+
* check identical to the Go peer's.
|
|
13738
|
+
*/
|
|
13739
|
+
function isXccdfGroupId(id) {
|
|
13740
|
+
return /^xccdf_[^_]+_group_.+$/.test(id) && /^[A-Za-z0-9._-]+$/.test(id);
|
|
13445
13741
|
}
|
|
13446
13742
|
/** Read a string-valued tag, or undefined if absent/non-string. */
|
|
13447
13743
|
function tagString(tags, key) {
|
|
@@ -13466,12 +13762,22 @@ function toolTestSystem(tool) {
|
|
|
13466
13762
|
const name = tool.name ? cpeField(tool.name) : "tool";
|
|
13467
13763
|
return `cpe:/a:${name}:${name}:${cpeField(tool.version)}`;
|
|
13468
13764
|
}
|
|
13765
|
+
/**
|
|
13766
|
+
* The requirement id as text for id construction. HDF makes id required, but
|
|
13767
|
+
* nested-invalid input reaches the converter and concatenating an absent id
|
|
13768
|
+
* wrote the literal string "undefined" into the emitted identifier — where Go,
|
|
13769
|
+
* concatenating a zero-value string, wrote nothing. Both satisfied the XCCDF
|
|
13770
|
+
* pattern, so no schema gate caught the divergence.
|
|
13771
|
+
*/
|
|
13772
|
+
function requirementIdText(id) {
|
|
13773
|
+
return typeof id === "string" ? id : "";
|
|
13774
|
+
}
|
|
13469
13775
|
/** Build the Benchmark XML object from HDF data. */
|
|
13470
13776
|
function buildBenchmarkObj(hdfData) {
|
|
13471
13777
|
const empty = !hdfData.baselines || hdfData.baselines.length === 0;
|
|
13472
13778
|
const benchmark = {
|
|
13473
13779
|
[`${ATTR}xmlns`]: "http://checklists.nist.gov/xccdf/1.2",
|
|
13474
|
-
[`${ATTR}id`]: empty ? "xccdf_hdf_benchmark_exported" : sanitizeXccdfId("xccdf_hdf_benchmark_" + hdfData.baselines[0].name),
|
|
13780
|
+
[`${ATTR}id`]: empty ? "xccdf_hdf_benchmark_exported" : sanitizeXccdfId("xccdf_hdf_benchmark_" + xccdfNamePart(hdfData.baselines[0].name)),
|
|
13475
13781
|
[`${ATTR}resolved`]: "1",
|
|
13476
13782
|
status: wrap("incomplete")
|
|
13477
13783
|
};
|
|
@@ -13485,7 +13791,7 @@ function buildBenchmarkObj(hdfData) {
|
|
|
13485
13791
|
if (baseline.summary) benchmark.description = wrap(baseline.summary);
|
|
13486
13792
|
benchmark.version = wrap(baseline.version ?? "1.0");
|
|
13487
13793
|
benchmark.Profile = {
|
|
13488
|
-
[`${ATTR}id`]: sanitizeXccdfId("xccdf_hdf_profile_" + baseline.name),
|
|
13794
|
+
[`${ATTR}id`]: sanitizeXccdfId("xccdf_hdf_profile_" + xccdfNamePart(baseline.name)),
|
|
13489
13795
|
title: wrap(baseline.title ?? baseline.name)
|
|
13490
13796
|
};
|
|
13491
13797
|
const groups = [];
|
|
@@ -13502,7 +13808,7 @@ function buildBenchmarkObj(hdfData) {
|
|
|
13502
13808
|
if (idx === void 0) {
|
|
13503
13809
|
idx = groups.length;
|
|
13504
13810
|
groupIndex.set(gid, idx);
|
|
13505
|
-
const group = { [`${ATTR}id`]: gid };
|
|
13811
|
+
const group = { [`${ATTR}id`]: xccdfGroupId(gid) };
|
|
13506
13812
|
const gtitle = tagString(req.tags, "gtitle");
|
|
13507
13813
|
if (gtitle) group.title = wrap(gtitle);
|
|
13508
13814
|
group.Rule = [];
|
|
@@ -13512,12 +13818,13 @@ function buildBenchmarkObj(hdfData) {
|
|
|
13512
13818
|
}
|
|
13513
13819
|
if (groups.length > 0) benchmark.Group = groups;
|
|
13514
13820
|
if (flatRules.length > 0) benchmark.Rule = flatRules;
|
|
13515
|
-
|
|
13821
|
+
const testResult = buildTestResultObj(hdfData, baseline);
|
|
13822
|
+
if (testResult) benchmark.TestResult = testResult;
|
|
13516
13823
|
return benchmark;
|
|
13517
13824
|
}
|
|
13518
13825
|
/** Build an XCCDF Rule object from an HDF EvaluatedRequirement. */
|
|
13519
13826
|
function buildRuleObj(req) {
|
|
13520
|
-
const ruleId = sanitizeXccdfId("xccdf_hdf_rule_" + req.id + "_rule");
|
|
13827
|
+
const ruleId = sanitizeXccdfId("xccdf_hdf_rule_" + requirementIdText(req.id) + "_rule");
|
|
13521
13828
|
const rule = {
|
|
13522
13829
|
[`${ATTR}id`]: ruleId,
|
|
13523
13830
|
[`${ATTR}severity`]: impactToSeverity$2(req.impact),
|
|
@@ -13566,27 +13873,74 @@ function buildRuleObj(req) {
|
|
|
13566
13873
|
[`${ATTR}system`]: "http://oval.mitre.org/XMLSchema/oval-definitions-5",
|
|
13567
13874
|
"check-content": wrap(checkContent)
|
|
13568
13875
|
});
|
|
13569
|
-
|
|
13876
|
+
const code = firstNonEmpty(req.code ?? "");
|
|
13877
|
+
if (code !== "") checks.push({
|
|
13570
13878
|
[`${ATTR}system`]: "http://inspec.io/",
|
|
13571
|
-
"check-content": wrap(
|
|
13879
|
+
"check-content": wrap(code)
|
|
13572
13880
|
});
|
|
13573
13881
|
if (checks.length > 0) rule.check = checks;
|
|
13574
13882
|
return rule;
|
|
13575
13883
|
}
|
|
13576
|
-
/**
|
|
13884
|
+
/**
|
|
13885
|
+
* The earliest and latest result time in a baseline.
|
|
13886
|
+
*
|
|
13887
|
+
* XCCDF makes TestResult/@end-time required while HDF's top-level timestamp is
|
|
13888
|
+
* optional, so a document without one still has to carry a scan window. It is
|
|
13889
|
+
* derived from the results rather than the wall clock, which would break both
|
|
13890
|
+
* determinism and the golden comparison. A TestResult is only built when a
|
|
13891
|
+
* baseline exists, and the HDF schema puts minItems 1 on both requirements and
|
|
13892
|
+
* results with startTime required on each, so a window is always available
|
|
13893
|
+
* here; undefined is returned anyway rather than assuming it.
|
|
13894
|
+
*
|
|
13895
|
+
* Times are emitted through formatTimestamp, HDF's canonical form, which is
|
|
13896
|
+
* documented as byte-identical to the RFC3339Nano string the Go converter
|
|
13897
|
+
* emits for the same instant.
|
|
13898
|
+
*
|
|
13899
|
+
* Mirrored by resultTimeWindow in the Go converter.
|
|
13900
|
+
*/
|
|
13901
|
+
function resultTimeWindow(baseline) {
|
|
13902
|
+
let first;
|
|
13903
|
+
let last;
|
|
13904
|
+
for (const req of baseline.requirements) for (const res of req.results) {
|
|
13905
|
+
const parsed = parseTimestamp(typeof res.startTime === "string" ? res.startTime : res.startTime.toISOString());
|
|
13906
|
+
if (!parsed) continue;
|
|
13907
|
+
const at = parsed.getTime();
|
|
13908
|
+
const canonical = formatTimestamp(parsed);
|
|
13909
|
+
if (!first || at < first.at) first = {
|
|
13910
|
+
at,
|
|
13911
|
+
text: canonical
|
|
13912
|
+
};
|
|
13913
|
+
if (!last || at > last.at) last = {
|
|
13914
|
+
at,
|
|
13915
|
+
text: canonical
|
|
13916
|
+
};
|
|
13917
|
+
}
|
|
13918
|
+
return first && last ? {
|
|
13919
|
+
first: first.text,
|
|
13920
|
+
last: last.text
|
|
13921
|
+
} : void 0;
|
|
13922
|
+
}
|
|
13923
|
+
/** Build the XCCDF TestResult object, or undefined when it cannot carry a time. */
|
|
13577
13924
|
function buildTestResultObj(hdfData, baseline) {
|
|
13578
13925
|
const testResult = {
|
|
13579
13926
|
[`${ATTR}id`]: "xccdf_hdf_testresult_1",
|
|
13580
13927
|
title: wrap("HDF Assessment Results")
|
|
13581
13928
|
};
|
|
13582
13929
|
if (hdfData.timestamp) {
|
|
13583
|
-
const
|
|
13930
|
+
const raw = typeof hdfData.timestamp === "string" ? hdfData.timestamp : hdfData.timestamp.toISOString();
|
|
13931
|
+
const parsedTs = parseTimestamp(raw);
|
|
13932
|
+
const ts = parsedTs ? formatTimestamp(parsedTs) : raw;
|
|
13584
13933
|
testResult[`${ATTR}start-time`] = ts;
|
|
13585
13934
|
let endTime = ts;
|
|
13586
13935
|
const duration = hdfData.statistics?.duration;
|
|
13587
13936
|
const start = parseTimestamp(ts);
|
|
13588
13937
|
if (start && typeof duration === "number" && duration > 0) endTime = formatTimestamp(new Date(start.getTime() + duration * 1e3));
|
|
13589
13938
|
testResult[`${ATTR}end-time`] = endTime;
|
|
13939
|
+
} else {
|
|
13940
|
+
const window = resultTimeWindow(baseline);
|
|
13941
|
+
if (!window) return;
|
|
13942
|
+
testResult[`${ATTR}start-time`] = window.first;
|
|
13943
|
+
testResult[`${ATTR}end-time`] = window.last;
|
|
13590
13944
|
}
|
|
13591
13945
|
const testSystem = toolTestSystem(hdfData.tool);
|
|
13592
13946
|
if (testSystem) testResult[`${ATTR}test-system`] = testSystem;
|
|
@@ -13599,7 +13953,7 @@ function buildTestResultObj(hdfData, baseline) {
|
|
|
13599
13953
|
let passed = 0;
|
|
13600
13954
|
let scorable = 0;
|
|
13601
13955
|
for (const req of baseline.requirements) {
|
|
13602
|
-
const ruleIdRef = sanitizeXccdfId("xccdf_hdf_rule_" + req.id + "_rule");
|
|
13956
|
+
const ruleIdRef = sanitizeXccdfId("xccdf_hdf_rule_" + requirementIdText(req.id) + "_rule");
|
|
13603
13957
|
const stigId = tagString(req.tags, "stig_id");
|
|
13604
13958
|
const statusInput = requirementStatusInput(req);
|
|
13605
13959
|
const resolved = computeEffectiveStatus(statusInput);
|
|
@@ -13611,7 +13965,9 @@ function buildTestResultObj(hdfData, baseline) {
|
|
|
13611
13965
|
scorable++;
|
|
13612
13966
|
} else if (status === "fail") scorable++;
|
|
13613
13967
|
const rr = { [`${ATTR}idref`]: ruleIdRef };
|
|
13614
|
-
|
|
13968
|
+
const rawStart = typeof result.startTime === "string" ? result.startTime : result.startTime.toISOString();
|
|
13969
|
+
const parsedStart = parseTimestamp(rawStart);
|
|
13970
|
+
rr[`${ATTR}time`] = parsedStart ? formatTimestamp(parsedStart) : rawStart;
|
|
13615
13971
|
if (stigId) rr[`${ATTR}version`] = stigId;
|
|
13616
13972
|
rr.result = wrap(status);
|
|
13617
13973
|
if (result.message) rr.message = wrap(result.message);
|
|
@@ -13633,6 +13989,11 @@ function buildTestResultObj(hdfData, baseline) {
|
|
|
13633
13989
|
}
|
|
13634
13990
|
//#endregion
|
|
13635
13991
|
//#region converters/oscal-to-hdf/typescript/shared.ts
|
|
13992
|
+
/**
|
|
13993
|
+
* Shared helper functions for OSCAL-to-HDF converters.
|
|
13994
|
+
*
|
|
13995
|
+
* Mirrors the Go helpers in converters/oscal-to-hdf/go/shared.go.
|
|
13996
|
+
*/
|
|
13636
13997
|
const controlEnhancementRe = /^([a-z]{2}-\d+)\.(\d+)$/;
|
|
13637
13998
|
const objectiveIDRe = /^([a-z]{2}-\d+(?:\.\d+)?)/;
|
|
13638
13999
|
/**
|
|
@@ -13717,6 +14078,12 @@ function flattenPartsByName(parts, name) {
|
|
|
13717
14078
|
}
|
|
13718
14079
|
return pieces.join("\n").trim();
|
|
13719
14080
|
}
|
|
14081
|
+
/** The one OSCAL spelling the standard severity map does not know; every other
|
|
14082
|
+
* facet value is standard vocabulary. */
|
|
14083
|
+
const RISK_FACET_SEVERITY_ALIASES = { moderate: .5 };
|
|
14084
|
+
/** Sentinel for "this facet's value is not a severity" — impact is 0.0-1.0, so
|
|
14085
|
+
* a negative can never be a real reading. */
|
|
14086
|
+
const UNRECOGNIZED_FACET_IMPACT = -1;
|
|
13720
14087
|
/**
|
|
13721
14088
|
* Extracts impact/severity from risk characterization facets.
|
|
13722
14089
|
* Returns a normalized 0.0-1.0 impact value. Falls back to defaultImpact.
|
|
@@ -13725,15 +14092,10 @@ function extractRiskSeverity(characterizations, defaultImpact) {
|
|
|
13725
14092
|
if (!characterizations) return defaultImpact;
|
|
13726
14093
|
for (const c of characterizations) {
|
|
13727
14094
|
if (!c.facets) continue;
|
|
13728
|
-
for (const f of c.facets)
|
|
13729
|
-
|
|
13730
|
-
|
|
13731
|
-
|
|
13732
|
-
case "medium": return .5;
|
|
13733
|
-
case "low": return .3;
|
|
13734
|
-
case "info":
|
|
13735
|
-
case "informational":
|
|
13736
|
-
case "none": return 0;
|
|
14095
|
+
for (const f of c.facets) {
|
|
14096
|
+
if (f.name !== "impact" && f.name !== "risk" && f.name !== "likelihood") continue;
|
|
14097
|
+
const impact = severityToImpactWithAliases(f.value, RISK_FACET_SEVERITY_ALIASES, UNRECOGNIZED_FACET_IMPACT);
|
|
14098
|
+
if (impact >= 0) return impact;
|
|
13737
14099
|
}
|
|
13738
14100
|
}
|
|
13739
14101
|
return defaultImpact;
|
|
@@ -13764,11 +14126,7 @@ function nistTagToControlId(tag) {
|
|
|
13764
14126
|
* This is the reverse of extractRiskSeverity.
|
|
13765
14127
|
*/
|
|
13766
14128
|
function impactToSeverity$1(impact) {
|
|
13767
|
-
|
|
13768
|
-
if (impact >= .7) return "high";
|
|
13769
|
-
if (impact >= .4) return "moderate";
|
|
13770
|
-
if (impact >= .1) return "low";
|
|
13771
|
-
return "info";
|
|
14129
|
+
return oscalSeverityFromHdf(impactToSeverity(impact));
|
|
13772
14130
|
}
|
|
13773
14131
|
/**
|
|
13774
14132
|
* Maps an HDF status string to an OSCAL risk status string.
|
|
@@ -13866,15 +14224,9 @@ function oscalString(s) {
|
|
|
13866
14224
|
* @returns OSCAL SAR JSON string
|
|
13867
14225
|
*/
|
|
13868
14226
|
async function convertHdfToOscalSar(input) {
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
try {
|
|
13873
|
-
hdfResults = parseHdf(input);
|
|
13874
|
-
} catch {
|
|
13875
|
-
throw new Error("hdf-to-oscal-sar: failed to parse HDF JSON");
|
|
13876
|
-
}
|
|
13877
|
-
if (!hdfResults || typeof hdfResults !== "object" || !("baselines" in hdfResults)) throw new Error("hdf-to-oscal-sar: invalid HDF structure: missing baselines field");
|
|
14227
|
+
const { doc: hdfResultsDoc } = requireHdfResults(input, "hdf-to-oscal-sar");
|
|
14228
|
+
const hdfResults = hdfResultsDoc;
|
|
14229
|
+
if (hdfResults.baselines.length === 0) throw new Error("hdf-to-oscal-sar: cannot represent an assessment with no evaluated baselines as OSCAL Assessment Results, which requires at least one result");
|
|
13878
14230
|
const doc = buildOSCALDocument(hdfResults);
|
|
13879
14231
|
return JSON.stringify(doc, null, 2);
|
|
13880
14232
|
}
|
|
@@ -13976,7 +14328,7 @@ function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
|
13976
14328
|
const includeControls = [];
|
|
13977
14329
|
const seenControl = /* @__PURE__ */ new Set();
|
|
13978
14330
|
for (const req of baseline.requirements) {
|
|
13979
|
-
const nistId = nistTagToControlId(req.id);
|
|
14331
|
+
const nistId = nistTagToControlId(req.id ?? "");
|
|
13980
14332
|
if (nistId === "") continue;
|
|
13981
14333
|
const { finding, observation, risk, resource } = requirementToFindingSet(req, timestamp, toolActorUuid, subjects);
|
|
13982
14334
|
findings.push(finding);
|
|
@@ -13998,9 +14350,9 @@ function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
|
13998
14350
|
start: assessmentStart(baseline, timestamp),
|
|
13999
14351
|
...resultProps.length > 0 ? { props: resultProps } : {},
|
|
14000
14352
|
"reviewed-controls": { "control-selections": [controlSelection] },
|
|
14001
|
-
findings,
|
|
14002
|
-
observations,
|
|
14003
|
-
risks
|
|
14353
|
+
...findings.length > 0 ? { findings } : {},
|
|
14354
|
+
...observations.length > 0 ? { observations } : {},
|
|
14355
|
+
...risks.length > 0 ? { risks } : {}
|
|
14004
14356
|
},
|
|
14005
14357
|
resources
|
|
14006
14358
|
};
|
|
@@ -14010,10 +14362,17 @@ function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
|
14010
14362
|
* component's UUID (componentId when present, otherwise a fresh one) identifies
|
|
14011
14363
|
* the subject; the HDF component type is a valid OSCAL subject type token and
|
|
14012
14364
|
* its name becomes the subject title.
|
|
14365
|
+
*
|
|
14366
|
+
* A component with no type is skipped rather than given one. OSCAL requires both
|
|
14367
|
+
* subject-uuid and type on a subject-reference, so the type cannot simply be
|
|
14368
|
+
* omitted, and hdf-results defines no default component type to fall back on.
|
|
14369
|
+
* String(c.type) turned an absent type into the literal "undefined" — a valid
|
|
14370
|
+
* OSCAL token, so a schema check could not see it, asserting a component type
|
|
14371
|
+
* the source never stated. Mirrors the Go peer.
|
|
14013
14372
|
*/
|
|
14014
14373
|
function buildSubjects(components) {
|
|
14015
14374
|
if (!Array.isArray(components) || components.length === 0) return [];
|
|
14016
|
-
return components.map((c) => ({
|
|
14375
|
+
return components.filter((c) => oscalString(c.type ?? "") !== "").map((c) => ({
|
|
14017
14376
|
"subject-uuid": c.componentId && c.componentId !== "" ? c.componentId : crypto.randomUUID(),
|
|
14018
14377
|
type: String(c.type),
|
|
14019
14378
|
title: c.name
|
|
@@ -14023,7 +14382,7 @@ function buildSubjects(components) {
|
|
|
14023
14382
|
* Converts an EvaluatedRequirement into a Finding, optional Observation, and optional Risk.
|
|
14024
14383
|
*/
|
|
14025
14384
|
function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
14026
|
-
const controlID = oscalToken(nistTagToControlId(req.id));
|
|
14385
|
+
const controlID = oscalToken(nistTagToControlId(req.id ?? ""));
|
|
14027
14386
|
const results = req.results ?? [];
|
|
14028
14387
|
const descriptions = req.descriptions ?? [];
|
|
14029
14388
|
const { state, reason } = effectiveState(req);
|
|
@@ -14105,7 +14464,7 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
14105
14464
|
uuid: resourceUuid,
|
|
14106
14465
|
title: `Check source code for ${req.id}`,
|
|
14107
14466
|
base64: {
|
|
14108
|
-
value:
|
|
14467
|
+
value: encodeBase64Utf8(req.code),
|
|
14109
14468
|
"media-type": "text/plain"
|
|
14110
14469
|
}
|
|
14111
14470
|
};
|
|
@@ -14287,14 +14646,7 @@ function sourceLocationText(loc) {
|
|
|
14287
14646
|
}
|
|
14288
14647
|
/** Maps an explicit HDF severity to the OSCAL risk facet value vocabulary. */
|
|
14289
14648
|
function severityToFacetValue(s) {
|
|
14290
|
-
|
|
14291
|
-
case "critical": return "critical";
|
|
14292
|
-
case "high": return "high";
|
|
14293
|
-
case "medium": return "moderate";
|
|
14294
|
-
case "low": return "low";
|
|
14295
|
-
case "informational": return "info";
|
|
14296
|
-
default: return "";
|
|
14297
|
-
}
|
|
14649
|
+
return oscalSeverityFromHdf(s);
|
|
14298
14650
|
}
|
|
14299
14651
|
/**
|
|
14300
14652
|
* Turns the requirement's fix description and any governing risk-acceptance
|
|
@@ -14380,17 +14732,27 @@ function riskStatusFromState(state) {
|
|
|
14380
14732
|
* @returns OSCAL POA&M JSON string
|
|
14381
14733
|
*/
|
|
14382
14734
|
async function convertHdfToOscalPoam(input) {
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
let amendments;
|
|
14386
|
-
try {
|
|
14387
|
-
amendments = parseHdf(input);
|
|
14388
|
-
} catch {
|
|
14389
|
-
throw new Error("hdf-to-oscal-poam: failed to parse JSON");
|
|
14390
|
-
}
|
|
14391
|
-
const doc = { "plan-of-action-and-milestones": amendmentsToPOAM(amendments) };
|
|
14735
|
+
const { doc: amendmentsDoc } = requireHdfAmendments(input, "hdf-to-oscal-poam");
|
|
14736
|
+
const doc = { "plan-of-action-and-milestones": amendmentsToPOAM(amendmentsDoc) };
|
|
14392
14737
|
return JSON.stringify(doc, null, 2);
|
|
14393
14738
|
}
|
|
14739
|
+
/**
|
|
14740
|
+
* Picks the document title, which OSCAL requires on metadata. The HDF name is
|
|
14741
|
+
* schema-required, so the fallbacks only matter for a document that slipped
|
|
14742
|
+
* through some other producer's validation.
|
|
14743
|
+
*/
|
|
14744
|
+
function poamTitle(a) {
|
|
14745
|
+
return firstNonEmpty(a.name, a.amendmentId ?? "", "HDF Amendments");
|
|
14746
|
+
}
|
|
14747
|
+
/**
|
|
14748
|
+
* Supplies the text OSCAL requires for a risk's description and statement. HDF
|
|
14749
|
+
* puts no minLength on reason, so an override can legitimately carry none; the
|
|
14750
|
+
* fallback states that absence rather than inventing an impact assessment the
|
|
14751
|
+
* source never made.
|
|
14752
|
+
*/
|
|
14753
|
+
function riskRationale(override) {
|
|
14754
|
+
return firstNonEmpty(override.reason, `No rationale was recorded for the ${String(override.type)} override applied to ${override.requirementId}.`);
|
|
14755
|
+
}
|
|
14394
14756
|
/** HDF dates arrive as strings from JSON.parse but are typed as Date. */
|
|
14395
14757
|
function toDate(value) {
|
|
14396
14758
|
if (value === void 0) return void 0;
|
|
@@ -14408,14 +14770,15 @@ var PartyRegistry = class {
|
|
|
14408
14770
|
this.byId = /* @__PURE__ */ new Map();
|
|
14409
14771
|
}
|
|
14410
14772
|
getOrAdd(id) {
|
|
14411
|
-
const
|
|
14773
|
+
const name = oscalString(id.identifier);
|
|
14774
|
+
const existing = this.byId.get(name);
|
|
14412
14775
|
if (existing) return existing.uuid;
|
|
14413
14776
|
const party = {
|
|
14414
14777
|
uuid: crypto.randomUUID(),
|
|
14415
14778
|
type: "person",
|
|
14416
|
-
name:
|
|
14779
|
+
...name === "" ? {} : { name }
|
|
14417
14780
|
};
|
|
14418
|
-
this.byId.set(
|
|
14781
|
+
this.byId.set(name, party);
|
|
14419
14782
|
return party.uuid;
|
|
14420
14783
|
}
|
|
14421
14784
|
list() {
|
|
@@ -14465,7 +14828,7 @@ function amendmentsToPOAM(amendments) {
|
|
|
14465
14828
|
observations.push(...itemObs);
|
|
14466
14829
|
}
|
|
14467
14830
|
const metadata = {
|
|
14468
|
-
title: amendments
|
|
14831
|
+
title: poamTitle(amendments),
|
|
14469
14832
|
"last-modified": latestAppliedAt(amendments.overrides),
|
|
14470
14833
|
version: amendmentsVersion(amendments),
|
|
14471
14834
|
"oscal-version": OSCAL_VERSION
|
|
@@ -14483,9 +14846,9 @@ function amendmentsToPOAM(amendments) {
|
|
|
14483
14846
|
uuid: crypto.randomUUID(),
|
|
14484
14847
|
metadata,
|
|
14485
14848
|
"import-ssp": importSSP,
|
|
14486
|
-
risks,
|
|
14487
14849
|
"poam-items": poamItems
|
|
14488
14850
|
};
|
|
14851
|
+
if (risks.length > 0) poam.risks = risks;
|
|
14489
14852
|
if (observations.length > 0) poam.observations = observations;
|
|
14490
14853
|
const resources = externalRefResources(amendments.overrides);
|
|
14491
14854
|
if (resources.length > 0) poam["back-matter"] = { resources };
|
|
@@ -14498,10 +14861,9 @@ function amendmentsToPOAM(amendments) {
|
|
|
14498
14861
|
function overrideToPOAMItem(override, parties) {
|
|
14499
14862
|
const riskUUID = crypto.randomUUID();
|
|
14500
14863
|
const riskStatus = override.status ? hdfStatusToOscalRiskStatus(String(override.status)) : "open";
|
|
14501
|
-
const
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
}];
|
|
14864
|
+
const controlID = nistTagToControlId(override.requirementId);
|
|
14865
|
+
const riskProps = [];
|
|
14866
|
+
pushStringProp(riskProps, "impacted-control-id", controlID);
|
|
14505
14867
|
if (override.type) riskProps.push({
|
|
14506
14868
|
name: "override-type",
|
|
14507
14869
|
value: String(override.type)
|
|
@@ -14510,18 +14872,9 @@ function overrideToPOAMItem(override, parties) {
|
|
|
14510
14872
|
name: "impact-override",
|
|
14511
14873
|
value: String(override.impact.value)
|
|
14512
14874
|
});
|
|
14513
|
-
if (override.justification) riskProps.
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
});
|
|
14517
|
-
if (override.baselineRef) riskProps.push({
|
|
14518
|
-
name: "baseline-ref",
|
|
14519
|
-
value: override.baselineRef
|
|
14520
|
-
});
|
|
14521
|
-
if (override.componentRef) riskProps.push({
|
|
14522
|
-
name: "component-ref",
|
|
14523
|
-
value: override.componentRef
|
|
14524
|
-
});
|
|
14875
|
+
if (override.justification) pushStringProp(riskProps, "justification", String(override.justification));
|
|
14876
|
+
if (override.baselineRef) pushStringProp(riskProps, "baseline-ref", override.baselineRef);
|
|
14877
|
+
if (override.componentRef) pushStringProp(riskProps, "component-ref", override.componentRef);
|
|
14525
14878
|
const remediations = [];
|
|
14526
14879
|
if (override.milestones) for (const ms of override.milestones) {
|
|
14527
14880
|
const msProps = [];
|
|
@@ -14583,11 +14936,12 @@ function overrideToPOAMItem(override, parties) {
|
|
|
14583
14936
|
itemObs.push(evidenceObservation(ev, obsUUID, collected));
|
|
14584
14937
|
relatedObs.push({ "observation-uuid": obsUUID });
|
|
14585
14938
|
}
|
|
14939
|
+
const rationale = riskRationale(override);
|
|
14586
14940
|
const risk = {
|
|
14587
14941
|
uuid: riskUUID,
|
|
14588
14942
|
title: override.requirementId,
|
|
14589
|
-
description:
|
|
14590
|
-
statement:
|
|
14943
|
+
description: rationale,
|
|
14944
|
+
statement: rationale,
|
|
14591
14945
|
status: riskStatus,
|
|
14592
14946
|
deadline,
|
|
14593
14947
|
props: riskProps,
|
|
@@ -14622,9 +14976,22 @@ function latestAppliedAt(overrides) {
|
|
|
14622
14976
|
}
|
|
14623
14977
|
return formatTimestampSeconds(latest ?? /* @__PURE__ */ new Date());
|
|
14624
14978
|
}
|
|
14979
|
+
/**
|
|
14980
|
+
* Add a property whose value OSCAL types as StringDatatype, trimming it and
|
|
14981
|
+
* omitting the property entirely when nothing survives. A prop with an empty
|
|
14982
|
+
* value carries no more than an absent one and is schema-invalid. Mirrors
|
|
14983
|
+
* appendStringProp in the Go peer.
|
|
14984
|
+
*/
|
|
14985
|
+
function pushStringProp(props, name, value) {
|
|
14986
|
+
const trimmed = oscalString(value ?? "");
|
|
14987
|
+
if (trimmed !== "") props.push({
|
|
14988
|
+
name,
|
|
14989
|
+
value: trimmed
|
|
14990
|
+
});
|
|
14991
|
+
}
|
|
14625
14992
|
/** Sources metadata.version from the amendments document, defaulting when omitted. */
|
|
14626
14993
|
function amendmentsVersion(a) {
|
|
14627
|
-
return a.version
|
|
14994
|
+
return oscalString(a.version ?? "") || "1.0.0";
|
|
14628
14995
|
}
|
|
14629
14996
|
/**
|
|
14630
14997
|
* Carries document identifiers and labels that have no first-class OSCAL home.
|
|
@@ -14632,17 +14999,33 @@ function amendmentsVersion(a) {
|
|
|
14632
14999
|
*/
|
|
14633
15000
|
function metadataProps(a) {
|
|
14634
15001
|
const props = [];
|
|
14635
|
-
if (a.amendmentId) props.
|
|
14636
|
-
|
|
14637
|
-
value
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
name: k,
|
|
14641
|
-
value: a.labels[k],
|
|
14642
|
-
class: "amendment-label"
|
|
14643
|
-
});
|
|
15002
|
+
if (a.amendmentId) pushStringProp(props, "amendment-id", a.amendmentId);
|
|
15003
|
+
if (a.labels) for (const [key, value] of Object.entries(a.labels).sort(([x], [y]) => x < y ? -1 : x > y ? 1 : 0)) {
|
|
15004
|
+
const prop = labelProp(key, value);
|
|
15005
|
+
if (prop.value !== "") props.push(prop);
|
|
15006
|
+
}
|
|
14644
15007
|
return props;
|
|
14645
15008
|
}
|
|
15009
|
+
/**
|
|
15010
|
+
* Render one amendments label as a property. OSCAL types prop/@name as
|
|
15011
|
+
* TokenDatatype while HDF puts no constraint on label keys, so the key is
|
|
15012
|
+
* encoded and the source key kept in remarks when it had to change. Every label
|
|
15013
|
+
* key in this package's converter fixtures is token-shaped today, so this guards a shape real
|
|
15014
|
+
* data has not yet produced — but Kubernetes and OCI label keys are namespaced
|
|
15015
|
+
* with '/', which HDF permits and OSCAL rejects.
|
|
15016
|
+
*
|
|
15017
|
+
* Mirrored by labelProp in the Go converter.
|
|
15018
|
+
*/
|
|
15019
|
+
function labelProp(key, value) {
|
|
15020
|
+
const name = oscalToken(key) === "" ? "_" : oscalToken(key);
|
|
15021
|
+
const prop = {
|
|
15022
|
+
name,
|
|
15023
|
+
value: oscalString(value),
|
|
15024
|
+
class: "amendment-label"
|
|
15025
|
+
};
|
|
15026
|
+
if (key !== "" && name !== key) prop.remarks = key;
|
|
15027
|
+
return prop;
|
|
15028
|
+
}
|
|
14646
15029
|
/** Picks the collection timestamp for evidence observations. */
|
|
14647
15030
|
function observationCollected(override) {
|
|
14648
15031
|
return formatTimestampSeconds(toDate(override.appliedAt) ?? /* @__PURE__ */ new Date());
|
|
@@ -14656,14 +15039,8 @@ function evidenceObservation(ev, uuid, defaultCollected) {
|
|
|
14656
15039
|
if (ev.type === "url") re.href = ev.data;
|
|
14657
15040
|
else if (ev.data) re.remarks = ev.data;
|
|
14658
15041
|
const props = [];
|
|
14659
|
-
if (ev.mimeType) props.
|
|
14660
|
-
|
|
14661
|
-
value: ev.mimeType
|
|
14662
|
-
});
|
|
14663
|
-
if (ev.capturedBy && ev.capturedBy.identifier) props.push({
|
|
14664
|
-
name: "captured-by",
|
|
14665
|
-
value: ev.capturedBy.identifier
|
|
14666
|
-
});
|
|
15042
|
+
if (ev.mimeType) pushStringProp(props, "mime-type", ev.mimeType);
|
|
15043
|
+
if (ev.capturedBy && ev.capturedBy.identifier) pushStringProp(props, "captured-by", ev.capturedBy.identifier);
|
|
14667
15044
|
return {
|
|
14668
15045
|
uuid,
|
|
14669
15046
|
description: desc,
|
|
@@ -14886,7 +15263,7 @@ function verdictLabels(a) {
|
|
|
14886
15263
|
}
|
|
14887
15264
|
async function convertIonchannelToHdf(input, converterVersion = "1.0.0") {
|
|
14888
15265
|
if (!input?.trim()) throw new Error("Empty input");
|
|
14889
|
-
validateInputSize(input, "ionchannel");
|
|
15266
|
+
validateInputSize$1(input, "ionchannel");
|
|
14890
15267
|
const analysis = parseJSON(input);
|
|
14891
15268
|
if (!Array.isArray(analysis.scan_summaries)) throw new Error(`Ion Channel scan_summaries invalid summary data (expecting array, got ${typeof analysis.scan_summaries})`);
|
|
14892
15269
|
let allDeps = [];
|
|
@@ -15248,12 +15625,8 @@ function supplierEvidence(sourceURI, description) {
|
|
|
15248
15625
|
*
|
|
15249
15626
|
* Spec: https://github.com/openvex/spec
|
|
15250
15627
|
*/
|
|
15251
|
-
/** One year in milliseconds. VEX statements are re-evaluated as new info
|
|
15252
|
-
* arrives; one year is a defensive default consistent with the
|
|
15253
|
-
* no-permanent-amendment rule on Standalone_Override. */
|
|
15254
|
-
const DEFAULT_EXPIRY_HORIZON_MS$3 = 365 * 24 * 60 * 60 * 1e3;
|
|
15255
15628
|
async function convertOpenVexToHdf(input, converterVersion = "1.0.0") {
|
|
15256
|
-
validateInputSize(input, "openvex-to-hdf");
|
|
15629
|
+
validateInputSize$1(input, "openvex-to-hdf");
|
|
15257
15630
|
const doc = parseJSON(input);
|
|
15258
15631
|
const docTime = (doc.timestamp ? parseTimestamp(doc.timestamp) : null) ?? /* @__PURE__ */ new Date();
|
|
15259
15632
|
const overrides = [];
|
|
@@ -15262,8 +15635,10 @@ async function convertOpenVexToHdf(input, converterVersion = "1.0.0") {
|
|
|
15262
15635
|
if (override) overrides.push(override);
|
|
15263
15636
|
}
|
|
15264
15637
|
if (overrides.length === 0) throw new Error("openvex-to-hdf: VEX document contains no actionable statements (all 'affected' or 'under_investigation'); no amendment to write");
|
|
15638
|
+
const name = doc.author ? `OpenVEX statements from ${doc.author}` : "OpenVEX statements";
|
|
15639
|
+
await chainOverrides(overrides);
|
|
15265
15640
|
return {
|
|
15266
|
-
name
|
|
15641
|
+
name,
|
|
15267
15642
|
description: `Imported VEX statements from ${truncateId(doc["@id"] ?? "")}`,
|
|
15268
15643
|
overrides,
|
|
15269
15644
|
appliedBy: identityFor$2(doc.author, doc.role),
|
|
@@ -15283,7 +15658,7 @@ function statementToOverride(stmt, doc, docTime) {
|
|
|
15283
15658
|
if (!requirementId) return void 0;
|
|
15284
15659
|
const stmtTime = (stmt.timestamp ? parseTimestamp(stmt.timestamp) : null) ?? docTime;
|
|
15285
15660
|
const author = stmt.author ?? doc.author ?? "";
|
|
15286
|
-
const expiresAt =
|
|
15661
|
+
const expiresAt = defaultOverrideExpiry(stmtTime);
|
|
15287
15662
|
const override = {
|
|
15288
15663
|
type: target.overrideType,
|
|
15289
15664
|
requirementId,
|
|
@@ -15343,9 +15718,8 @@ function truncateId(id) {
|
|
|
15343
15718
|
*
|
|
15344
15719
|
* Spec: https://docs.oasis-open.org/csaf/csaf/v2.0/csaf-v2.0.html
|
|
15345
15720
|
*/
|
|
15346
|
-
const DEFAULT_EXPIRY_HORIZON_MS$2 = 365 * 24 * 60 * 60 * 1e3;
|
|
15347
15721
|
async function convertCsafVexToHdf(input, converterVersion = "1.0.0") {
|
|
15348
|
-
validateInputSize(input, "csaf-vex-to-hdf");
|
|
15722
|
+
validateInputSize$1(input, "csaf-vex-to-hdf");
|
|
15349
15723
|
const doc = parseJSON(input);
|
|
15350
15724
|
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`);
|
|
15351
15725
|
const dm = doc.document;
|
|
@@ -15357,6 +15731,7 @@ async function convertCsafVexToHdf(input, converterVersion = "1.0.0") {
|
|
|
15357
15731
|
for (const v of doc.vulnerabilities ?? []) overrides.push(...vulnerabilityToOverrides(v, publisher, tracking, docTime, productLookup));
|
|
15358
15732
|
if (overrides.length === 0) throw new Error("csaf-vex-to-hdf: CSAF VEX document contains no actionable statements (only affected/under_investigation/recommended); no amendment to write");
|
|
15359
15733
|
const publisherName = publisher.name ?? "";
|
|
15734
|
+
await chainOverrides(overrides);
|
|
15360
15735
|
return {
|
|
15361
15736
|
name: publisherName ? `CSAF VEX statements from ${publisherName}` : "CSAF VEX statements",
|
|
15362
15737
|
description: `Imported VEX advisory ${tracking.id ?? ""}`,
|
|
@@ -15389,7 +15764,7 @@ function vulnerabilityToOverrides(vuln, publisher, tracking, docTime, productLoo
|
|
|
15389
15764
|
function buildOverride(vuln, publisher, tracking, docTime, canonical, products, productLookup) {
|
|
15390
15765
|
const target = importTargetFor(canonical);
|
|
15391
15766
|
if (!target) return void 0;
|
|
15392
|
-
const expiresAt =
|
|
15767
|
+
const expiresAt = defaultOverrideExpiry(docTime);
|
|
15393
15768
|
const override = {
|
|
15394
15769
|
type: target.overrideType,
|
|
15395
15770
|
requirementId: vuln.cve,
|
|
@@ -15560,9 +15935,8 @@ function overlap(ids, scope) {
|
|
|
15560
15935
|
*
|
|
15561
15936
|
* Spec: https://cyclonedx.org/use-cases/#vulnerability-exploitability
|
|
15562
15937
|
*/
|
|
15563
|
-
const DEFAULT_EXPIRY_HORIZON_MS$1 = 365 * 24 * 60 * 60 * 1e3;
|
|
15564
15938
|
async function convertCyclonedxVexToHdf(input, converterVersion = "1.0.0") {
|
|
15565
|
-
validateInputSize(input, "cyclonedx-vex-to-hdf");
|
|
15939
|
+
validateInputSize$1(input, "cyclonedx-vex-to-hdf");
|
|
15566
15940
|
const bom = parseJSON(input);
|
|
15567
15941
|
if (bom.bomFormat !== "CycloneDX") throw new Error(`cyclonedx-vex-to-hdf: bomFormat is ${JSON.stringify(bom.bomFormat)}; only 'CycloneDX' is supported`);
|
|
15568
15942
|
const docTime = (bom.metadata?.timestamp ? parseTimestamp(bom.metadata.timestamp) : null) ?? /* @__PURE__ */ new Date();
|
|
@@ -15575,9 +15949,12 @@ async function convertCyclonedxVexToHdf(input, converterVersion = "1.0.0") {
|
|
|
15575
15949
|
}
|
|
15576
15950
|
if (overrides.length === 0) throw new Error("cyclonedx-vex-to-hdf: BOM contains no actionable VEX statements (only exploitable/in_triage or no analysis); no amendment to write");
|
|
15577
15951
|
const publisherName = publisher.identifier;
|
|
15952
|
+
const name = publisherName && publisherName !== "cyclonedx-vex-import" ? `CycloneDX VEX statements from ${publisherName}` : "CycloneDX VEX statements";
|
|
15953
|
+
const description = bom.serialNumber ? `Imported CycloneDX VEX ${bom.serialNumber}` : "Imported CycloneDX VEX";
|
|
15954
|
+
await chainOverrides(overrides);
|
|
15578
15955
|
return {
|
|
15579
|
-
name
|
|
15580
|
-
description
|
|
15956
|
+
name,
|
|
15957
|
+
description,
|
|
15581
15958
|
overrides,
|
|
15582
15959
|
appliedBy: publisher,
|
|
15583
15960
|
generator: {
|
|
@@ -15594,7 +15971,7 @@ function vulnerabilityToOverride(v, productLookup, docTime, publisher) {
|
|
|
15594
15971
|
const target = importTargetFor(canonical);
|
|
15595
15972
|
if (!target) return void 0;
|
|
15596
15973
|
const affectedPackages = affectedPackagesForVuln(v, productLookup);
|
|
15597
|
-
const expiresAt =
|
|
15974
|
+
const expiresAt = defaultOverrideExpiry(docTime);
|
|
15598
15975
|
const override = {
|
|
15599
15976
|
type: target.overrideType,
|
|
15600
15977
|
requirementId: v.id,
|
|
@@ -15728,8 +16105,6 @@ function publisherIdentityOrDefault(bom) {
|
|
|
15728
16105
|
*
|
|
15729
16106
|
* Spec: https://spdx.github.io/spdx-spec/v3.0.1/ (security profile)
|
|
15730
16107
|
*/
|
|
15731
|
-
/** One year in milliseconds — the override expiresAt horizon. */
|
|
15732
|
-
const DEFAULT_EXPIRY_HORIZON_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
15733
16108
|
/** Fallback system identity when no supplier agent resolves. */
|
|
15734
16109
|
const DEFAULT_APPLIED_BY = "spdx-vex-import";
|
|
15735
16110
|
/** The four SPDX-3 VEX assessment relationship subtypes -> canonical status. */
|
|
@@ -15740,7 +16115,7 @@ const VEX_STATUS_BY_TYPE = {
|
|
|
15740
16115
|
security_VexUnderInvestigationVulnAssessmentRelationship: VexStatus.UnderInvestigation
|
|
15741
16116
|
};
|
|
15742
16117
|
async function convertSpdxVexToHdf(input, converterVersion = "1.0.0") {
|
|
15743
|
-
validateInputSize(input, "spdx-vex-to-hdf");
|
|
16118
|
+
validateInputSize$1(input, "spdx-vex-to-hdf");
|
|
15744
16119
|
const graph = parseJSON(input)["@graph"] ?? [];
|
|
15745
16120
|
if (graph.length === 0) throw new Error("spdx-vex-to-hdf: document has no @graph elements");
|
|
15746
16121
|
const idx = buildIndex(graph);
|
|
@@ -15752,8 +16127,10 @@ async function convertSpdxVexToHdf(input, converterVersion = "1.0.0") {
|
|
|
15752
16127
|
}
|
|
15753
16128
|
if (overrides.length === 0) throw new Error("spdx-vex-to-hdf: SPDX document contains no actionable VEX statements (only affected/under_investigation); no amendment to write");
|
|
15754
16129
|
const appliedBy = documentIdentity(graph, idx);
|
|
16130
|
+
const name = appliedBy.identifier && appliedBy.identifier !== DEFAULT_APPLIED_BY ? `SPDX VEX statements from ${appliedBy.identifier}` : "SPDX VEX statements";
|
|
16131
|
+
await chainOverrides(overrides);
|
|
15755
16132
|
return {
|
|
15756
|
-
name
|
|
16133
|
+
name,
|
|
15757
16134
|
description: "Imported SPDX 3.0.1 security-profile VEX statements",
|
|
15758
16135
|
overrides,
|
|
15759
16136
|
appliedBy,
|
|
@@ -15800,7 +16177,7 @@ function relationshipToOverride(rel, idx) {
|
|
|
15800
16177
|
const requirementId = cveIdentifier(vuln);
|
|
15801
16178
|
if (!requirementId) return void 0;
|
|
15802
16179
|
const appliedAt = createdAt(rel.creationInfo, idx) ?? createdAt(vuln?.creationInfo, idx) ?? /* @__PURE__ */ new Date();
|
|
15803
|
-
const expiresAt =
|
|
16180
|
+
const expiresAt = defaultOverrideExpiry(appliedAt);
|
|
15804
16181
|
const override = {
|
|
15805
16182
|
type: target.overrideType,
|
|
15806
16183
|
requirementId,
|
|
@@ -15897,16 +16274,11 @@ function buildCvss(rel, cve) {
|
|
|
15897
16274
|
return cvss;
|
|
15898
16275
|
}
|
|
15899
16276
|
function cvssVersion(relType, vector) {
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
case "3.0": return Version.The30;
|
|
15906
|
-
case "3.1": return Version.The31;
|
|
15907
|
-
case "4.0": return Version.The40;
|
|
15908
|
-
}
|
|
15909
|
-
}
|
|
16277
|
+
return cvssVersionFromVector(vector, cvssVersionFromRelType(relType));
|
|
16278
|
+
}
|
|
16279
|
+
/** Fallback when the vector carries no recognized prefix: the relationship
|
|
16280
|
+
* subtype names the CVSS generation. */
|
|
16281
|
+
function cvssVersionFromRelType(relType) {
|
|
15910
16282
|
if ((relType ?? "").includes("CvssV2")) return Version.The20;
|
|
15911
16283
|
if ((relType ?? "").includes("CvssV4")) return Version.The40;
|
|
15912
16284
|
return Version.The31;
|
|
@@ -15965,7 +16337,7 @@ function isCvssType(type) {
|
|
|
15965
16337
|
const GO_ZERO_TIME$1 = /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
15966
16338
|
const CVE_ID_PATTERN$2 = /^CVE-\d{4}-\d{4,}$/;
|
|
15967
16339
|
const PRODUCTS_LINE$2 = /^Products:\s*(.+)$/m;
|
|
15968
|
-
const DEFAULT_PRODUCT_ID$
|
|
16340
|
+
const DEFAULT_PRODUCT_ID$1 = "HDFPID-0001";
|
|
15969
16341
|
/** Map an HDF Cvss block to a CSAF score entry (cvss_v2/v3/v4 by version). */
|
|
15970
16342
|
function buildCsafScore(cvss, products) {
|
|
15971
16343
|
const inner = {
|
|
@@ -15981,7 +16353,7 @@ function buildCsafScore(cvss, products) {
|
|
|
15981
16353
|
};
|
|
15982
16354
|
}
|
|
15983
16355
|
function convertHdfToCsafVex(input, converterVersion) {
|
|
15984
|
-
validateInputSize(input, "hdf-to-csaf-vex");
|
|
16356
|
+
validateInputSize$1(input, "hdf-to-csaf-vex");
|
|
15985
16357
|
const amendments = parseHdf(input);
|
|
15986
16358
|
const groups = groupByCVE(amendments.overrides ?? []);
|
|
15987
16359
|
const registry = /* @__PURE__ */ new Map();
|
|
@@ -16000,8 +16372,8 @@ function convertHdfToCsafVex(input, converterVersion) {
|
|
|
16000
16372
|
const doc = buildDocument(amendments, converterVersion);
|
|
16001
16373
|
doc.vulnerabilities = vulnerabilities;
|
|
16002
16374
|
doc.product_tree.full_product_names = names.length > 0 ? names : [{
|
|
16003
|
-
name: DEFAULT_PRODUCT_ID$
|
|
16004
|
-
product_id: DEFAULT_PRODUCT_ID$
|
|
16375
|
+
name: DEFAULT_PRODUCT_ID$1,
|
|
16376
|
+
product_id: DEFAULT_PRODUCT_ID$1
|
|
16005
16377
|
}];
|
|
16006
16378
|
return JSON.stringify(doc, null, 2);
|
|
16007
16379
|
}
|
|
@@ -16054,8 +16426,8 @@ function productEntriesFor(o) {
|
|
|
16054
16426
|
}));
|
|
16055
16427
|
}
|
|
16056
16428
|
return [{
|
|
16057
|
-
name: DEFAULT_PRODUCT_ID$
|
|
16058
|
-
product_id: DEFAULT_PRODUCT_ID$
|
|
16429
|
+
name: DEFAULT_PRODUCT_ID$1,
|
|
16430
|
+
product_id: DEFAULT_PRODUCT_ID$1
|
|
16059
16431
|
}];
|
|
16060
16432
|
}
|
|
16061
16433
|
/** product_tree entries for the synthesized fixed-version products. */
|
|
@@ -16094,18 +16466,36 @@ function productIDsFor$1(o) {
|
|
|
16094
16466
|
const parts = m[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
16095
16467
|
if (parts.length > 0) return parts;
|
|
16096
16468
|
}
|
|
16097
|
-
return [DEFAULT_PRODUCT_ID$
|
|
16469
|
+
return [DEFAULT_PRODUCT_ID$1];
|
|
16098
16470
|
}
|
|
16099
16471
|
function stripProductsLine$1(reason) {
|
|
16100
16472
|
return reason.replace(PRODUCTS_LINE$2, "").replace(/\n+$/, "");
|
|
16101
16473
|
}
|
|
16102
16474
|
/**
|
|
16475
|
+
* Fill CSAF references[].summary, which is required with minLength 1 while the
|
|
16476
|
+
* HDF description backing it is optional. The URL is the documented fallback:
|
|
16477
|
+
* the only text a reference always carries, where a placeholder would assert a
|
|
16478
|
+
* description the source never gave.
|
|
16479
|
+
*/
|
|
16480
|
+
function referenceSummary(description, url) {
|
|
16481
|
+
return firstNonEmpty(description, url);
|
|
16482
|
+
}
|
|
16483
|
+
/**
|
|
16484
|
+
* The override reason with the machine-written 'Products:' line removed, or ''
|
|
16485
|
+
* when no prose remains. Both sinks it feeds — threats[].details and
|
|
16486
|
+
* notes[].text — are minLength 1 in CSAF, so an empty result means the element
|
|
16487
|
+
* is omitted, never emitted blank.
|
|
16488
|
+
*/
|
|
16489
|
+
function reasonProse(reason) {
|
|
16490
|
+
return firstNonEmpty(stripProductsLine$1(reason ?? ""));
|
|
16491
|
+
}
|
|
16492
|
+
/**
|
|
16103
16493
|
* Surface the override reason prose as a CSAF description note. Used on the
|
|
16104
16494
|
* not_affected/fixed paths, which otherwise drop reason (the affected path
|
|
16105
16495
|
* keeps it as threats[impact]).
|
|
16106
16496
|
*/
|
|
16107
16497
|
function appendReasonNote(v, reason) {
|
|
16108
|
-
const text =
|
|
16498
|
+
const text = reasonProse(reason);
|
|
16109
16499
|
if (!text) return;
|
|
16110
16500
|
v.notes = v.notes ?? [];
|
|
16111
16501
|
v.notes.push({
|
|
@@ -16182,11 +16572,12 @@ function buildVulnerability(group) {
|
|
|
16182
16572
|
emitted = true;
|
|
16183
16573
|
} else if (canonical === VexStatus.Affected) {
|
|
16184
16574
|
status.known_affected = (status.known_affected ?? []).concat(pids);
|
|
16185
|
-
|
|
16575
|
+
const details = reasonProse(o.reason);
|
|
16576
|
+
if (details) {
|
|
16186
16577
|
v.threats = v.threats ?? [];
|
|
16187
16578
|
v.threats.push({
|
|
16188
16579
|
category: "impact",
|
|
16189
|
-
details
|
|
16580
|
+
details,
|
|
16190
16581
|
product_ids: pids
|
|
16191
16582
|
});
|
|
16192
16583
|
}
|
|
@@ -16208,7 +16599,7 @@ function buildVulnerability(group) {
|
|
|
16208
16599
|
v.references = v.references ?? [];
|
|
16209
16600
|
v.references.push({
|
|
16210
16601
|
category: "external",
|
|
16211
|
-
summary: e.description
|
|
16602
|
+
summary: referenceSummary(e.description, e.data),
|
|
16212
16603
|
url: e.data
|
|
16213
16604
|
});
|
|
16214
16605
|
}
|
|
@@ -16217,7 +16608,7 @@ function buildVulnerability(group) {
|
|
|
16217
16608
|
v.references = v.references ?? [];
|
|
16218
16609
|
v.references.push({
|
|
16219
16610
|
category: "external",
|
|
16220
|
-
summary: r.description
|
|
16611
|
+
summary: referenceSummary(r.description, r.href),
|
|
16221
16612
|
url: r.href
|
|
16222
16613
|
});
|
|
16223
16614
|
}
|
|
@@ -16239,7 +16630,7 @@ function buildVulnerability(group) {
|
|
|
16239
16630
|
return {
|
|
16240
16631
|
cve: v.cve,
|
|
16241
16632
|
...v.notes && { notes: v.notes },
|
|
16242
|
-
product_status: status,
|
|
16633
|
+
...Object.keys(status).length > 0 && { product_status: status },
|
|
16243
16634
|
...v.flags && { flags: v.flags },
|
|
16244
16635
|
...v.threats && { threats: v.threats },
|
|
16245
16636
|
...v.remediations && { remediations: v.remediations },
|
|
@@ -16319,9 +16710,8 @@ const CVE_ID_PATTERN$1 = /^CVE-\d{4}-\d{4,}$/;
|
|
|
16319
16710
|
const PRODUCTS_LINE$1 = /^Products:\s*(.+)$/m;
|
|
16320
16711
|
const OPENVEX_CONTEXT = "https://openvex.dev/ns/v0.2.0";
|
|
16321
16712
|
const OPENVEX_NAMESPACE = "https://openvex.dev/docs/public/";
|
|
16322
|
-
const DEFAULT_PRODUCT_ID$1 = "HDFPID-0001";
|
|
16323
16713
|
async function convertHdfToOpenVex(input, converterVersion) {
|
|
16324
|
-
validateInputSize(input, "hdf-to-openvex");
|
|
16714
|
+
validateInputSize$1(input, "hdf-to-openvex");
|
|
16325
16715
|
const amendments = parseHdf(input);
|
|
16326
16716
|
const author = amendments.appliedBy?.identifier || "HDF Amendments Export";
|
|
16327
16717
|
const role = amendments.appliedBy?.description;
|
|
@@ -16361,19 +16751,12 @@ function overrideToStatement(o, docAuthor) {
|
|
|
16361
16751
|
if (o.type === OverrideType.Poam && canonical === VexStatus.Affected && allMilestonesCompleted$1(o)) canonical = VexStatus.Fixed;
|
|
16362
16752
|
const notAffected = canonical === VexStatus.NotAffected;
|
|
16363
16753
|
const justification = notAffected && o.justification ? String(o.justification) : "";
|
|
16364
|
-
const reason = stripProductsLine(o.reason ?? "");
|
|
16365
|
-
const impact = notAffected ? reason : "";
|
|
16754
|
+
const reason = firstNonEmpty(stripProductsLine(o.reason ?? ""));
|
|
16755
|
+
const impact = notAffected ? firstNonEmpty(reason, justification ? "" : noImpactRationaleRecorded(o)) : "";
|
|
16366
16756
|
let action = "";
|
|
16367
|
-
|
|
16368
|
-
if (canonical === VexStatus.
|
|
16369
|
-
|
|
16370
|
-
action = firstMilestoneAction(o);
|
|
16371
|
-
if (!action) {
|
|
16372
|
-
action = reason;
|
|
16373
|
-
reasonEmitted = reason !== "";
|
|
16374
|
-
}
|
|
16375
|
-
}
|
|
16376
|
-
const statusNotes = buildStatusNotes(o, reason, reasonEmitted, docAuthor);
|
|
16757
|
+
if (canonical === VexStatus.Fixed) action = firstNonEmpty(firstMilestoneAction(o), "Fix applied; consumer re-scan confirmed clean.");
|
|
16758
|
+
else if (canonical === VexStatus.Affected) action = firstNonEmpty(firstMilestoneAction(o), reason, noRemediationRecorded(o));
|
|
16759
|
+
const statusNotes = buildStatusNotes(o, reason, reason !== "" && (impact === reason || action === reason), docAuthor);
|
|
16377
16760
|
return {
|
|
16378
16761
|
vulnerability: {
|
|
16379
16762
|
"@id": `https://nvd.nist.gov/vuln/detail/${o.requirementId}`,
|
|
@@ -16420,12 +16803,26 @@ function productsFor(o) {
|
|
|
16420
16803
|
const m = PRODUCTS_LINE$1.exec(o.reason ?? "");
|
|
16421
16804
|
if (m && m[1]) ids = m[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
16422
16805
|
}
|
|
16423
|
-
if (ids.length === 0)
|
|
16806
|
+
if (ids.length === 0) return void 0;
|
|
16424
16807
|
return ids.map((id) => ({ "@id": id }));
|
|
16425
16808
|
}
|
|
16809
|
+
/**
|
|
16810
|
+
* States the absence OpenVEX has no other way to express: action_statement is
|
|
16811
|
+
* required on an affected statement, and a source override may carry neither a
|
|
16812
|
+
* milestone nor a reason to fill it.
|
|
16813
|
+
*/
|
|
16814
|
+
function noRemediationRecorded(o) {
|
|
16815
|
+
return `No remediation action was recorded for the ${String(o.type)} override applied to ${o.requirementId}.`;
|
|
16816
|
+
}
|
|
16817
|
+
/**
|
|
16818
|
+
* The not_affected peer of noRemediationRecorded, for an override carrying
|
|
16819
|
+
* neither a justification enum nor a reason.
|
|
16820
|
+
*/
|
|
16821
|
+
function noImpactRationaleRecorded(o) {
|
|
16822
|
+
return `No impact rationale was recorded for the ${String(o.type)} override applied to ${o.requirementId}.`;
|
|
16823
|
+
}
|
|
16426
16824
|
function firstMilestoneAction(o) {
|
|
16427
|
-
|
|
16428
|
-
return "";
|
|
16825
|
+
return firstNonEmpty(...(o.milestones ?? []).map((m) => m.description));
|
|
16429
16826
|
}
|
|
16430
16827
|
function allMilestonesCompleted$1(o) {
|
|
16431
16828
|
if (!o.milestones || o.milestones.length === 0) return false;
|
|
@@ -16512,7 +16909,7 @@ function buildCdxRating(cvss) {
|
|
|
16512
16909
|
return rating;
|
|
16513
16910
|
}
|
|
16514
16911
|
async function convertHdfToCyclonedxVex(input, converterVersion) {
|
|
16515
|
-
validateInputSize(input, "hdf-to-cyclonedx-vex");
|
|
16912
|
+
validateInputSize$1(input, "hdf-to-cyclonedx-vex");
|
|
16516
16913
|
const amendments = parseHdf(input);
|
|
16517
16914
|
const componentRegistry = /* @__PURE__ */ new Map();
|
|
16518
16915
|
const vulnerabilities = [];
|
|
@@ -16712,7 +17109,7 @@ function detectOscalDocumentType(input) {
|
|
|
16712
17109
|
* @returns HDF Baseline JSON string
|
|
16713
17110
|
*/
|
|
16714
17111
|
async function convertOscalCatalogToHdf(input) {
|
|
16715
|
-
validateInputSize(input, "oscal-catalog");
|
|
17112
|
+
validateInputSize$1(input, "oscal-catalog");
|
|
16716
17113
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
16717
17114
|
const doc = parseJSON(input);
|
|
16718
17115
|
if (!doc.catalog) throw new Error("oscal-catalog: input is not a catalog document (root key is not 'catalog')");
|
|
@@ -16833,7 +17230,7 @@ function catalogBaselineName(catalog) {
|
|
|
16833
17230
|
* @returns HDF Baseline JSON string
|
|
16834
17231
|
*/
|
|
16835
17232
|
async function convertOscalComponentToHdf(input) {
|
|
16836
|
-
validateInputSize(input, "oscal-component-definition");
|
|
17233
|
+
validateInputSize$1(input, "oscal-component-definition");
|
|
16837
17234
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
16838
17235
|
const doc = parseJSON(input);
|
|
16839
17236
|
if (!doc["component-definition"]) throw new Error("oscal-component-definition: input is not a component-definition document (root key is not 'component-definition')");
|
|
@@ -16902,7 +17299,7 @@ function componentBaselineName(comp, compDef) {
|
|
|
16902
17299
|
* @returns HDF Amendments JSON string
|
|
16903
17300
|
*/
|
|
16904
17301
|
async function convertOscalPoamToHdf(input) {
|
|
16905
|
-
validateInputSize(input, "oscal-poam");
|
|
17302
|
+
validateInputSize$1(input, "oscal-poam");
|
|
16906
17303
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
16907
17304
|
const doc = parseJSON(input);
|
|
16908
17305
|
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')");
|
|
@@ -16914,6 +17311,7 @@ async function convertOscalPoamToHdf(input) {
|
|
|
16914
17311
|
for (const item of poam["poam-items"]) overrides.push(poamItemToOverride(item, riskMap, poam));
|
|
16915
17312
|
const systemRef = poam["import-ssp"]?.href || void 0;
|
|
16916
17313
|
const appliedBy = extractAppliedBy(poam.metadata);
|
|
17314
|
+
await chainOverrides(overrides);
|
|
16917
17315
|
return serializeHdf({
|
|
16918
17316
|
name: toKebabCase(poam.metadata.title, "oscal-poam"),
|
|
16919
17317
|
overrides,
|
|
@@ -16973,27 +17371,27 @@ function poamItemStatus(item, riskMap) {
|
|
|
16973
17371
|
const risk = riskMap.get(riskUuid);
|
|
16974
17372
|
if (risk) {
|
|
16975
17373
|
const status = oscalStatusToHdf(risk.status);
|
|
16976
|
-
if (status === "passed") return
|
|
16977
|
-
if (status === "failed") return
|
|
17374
|
+
if (status === "passed") return ResultStatus.Passed;
|
|
17375
|
+
if (status === "failed") return ResultStatus.Failed;
|
|
16978
17376
|
}
|
|
16979
17377
|
}
|
|
16980
17378
|
}
|
|
16981
|
-
return
|
|
17379
|
+
return ResultStatus.Failed;
|
|
16982
17380
|
}
|
|
16983
17381
|
function poamItemAppliedBy(poam) {
|
|
16984
17382
|
const rps = poam.metadata["responsible-parties"];
|
|
16985
17383
|
if (rps) {
|
|
16986
17384
|
for (const rp of rps) if (rp["role-id"] === "prepared-by" && rp["party-uuids"].length > 0) return {
|
|
16987
|
-
type:
|
|
17385
|
+
type: IdentityType.Simple,
|
|
16988
17386
|
identifier: rp["party-uuids"][0]
|
|
16989
17387
|
};
|
|
16990
17388
|
if (rps.length > 0 && rps[0]["party-uuids"].length > 0) return {
|
|
16991
|
-
type:
|
|
17389
|
+
type: IdentityType.Simple,
|
|
16992
17390
|
identifier: rps[0]["party-uuids"][0]
|
|
16993
17391
|
};
|
|
16994
17392
|
}
|
|
16995
17393
|
return {
|
|
16996
|
-
type:
|
|
17394
|
+
type: IdentityType.System,
|
|
16997
17395
|
identifier: "oscal-poam-converter"
|
|
16998
17396
|
};
|
|
16999
17397
|
}
|
|
@@ -17029,7 +17427,7 @@ function extractMilestones(item, riskMap) {
|
|
|
17029
17427
|
milestones.push({
|
|
17030
17428
|
description: task.description ? `${task.title}: ${task.description}` : task.title,
|
|
17031
17429
|
estimatedCompletion,
|
|
17032
|
-
status:
|
|
17430
|
+
status: MilestoneStatus.Pending
|
|
17033
17431
|
});
|
|
17034
17432
|
}
|
|
17035
17433
|
}
|
|
@@ -17040,7 +17438,7 @@ function extractAppliedBy(meta) {
|
|
|
17040
17438
|
const rps = meta["responsible-parties"];
|
|
17041
17439
|
if (rps) {
|
|
17042
17440
|
for (const rp of rps) if (rp["role-id"] === "prepared-by" && rp["party-uuids"].length > 0) return {
|
|
17043
|
-
type:
|
|
17441
|
+
type: IdentityType.Simple,
|
|
17044
17442
|
identifier: rp["party-uuids"][0]
|
|
17045
17443
|
};
|
|
17046
17444
|
}
|
|
@@ -17059,7 +17457,7 @@ function extractAppliedBy(meta) {
|
|
|
17059
17457
|
* @returns HDF Plan JSON string
|
|
17060
17458
|
*/
|
|
17061
17459
|
async function convertOscalSapToHdf(input) {
|
|
17062
|
-
validateInputSize(input, "oscal-assessment-plan");
|
|
17460
|
+
validateInputSize$1(input, "oscal-assessment-plan");
|
|
17063
17461
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
17064
17462
|
const doc = parseJSON(input);
|
|
17065
17463
|
if (!doc["assessment-plan"]) throw new Error("oscal-assessment-plan: input is not an assessment-plan document (root key is not 'assessment-plan')");
|
|
@@ -17189,7 +17587,7 @@ function buildPlanDescription(ap) {
|
|
|
17189
17587
|
* @returns HDF Results JSON string
|
|
17190
17588
|
*/
|
|
17191
17589
|
async function convertOscalSarToHdf(input) {
|
|
17192
|
-
validateInputSize(input, "oscal-assessment-results");
|
|
17590
|
+
validateInputSize$1(input, "oscal-assessment-results");
|
|
17193
17591
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
17194
17592
|
const doc = parseJSON(input);
|
|
17195
17593
|
if (!doc["assessment-results"]) throw new Error("oscal-assessment-results: input is not an assessment-results document (root key is not 'assessment-results')");
|
|
@@ -17498,7 +17896,7 @@ function sarBaselineName(result, sar) {
|
|
|
17498
17896
|
* @returns HDF System JSON string
|
|
17499
17897
|
*/
|
|
17500
17898
|
async function convertOscalSspToHdf(input) {
|
|
17501
|
-
validateInputSize(input, "oscal-ssp");
|
|
17899
|
+
validateInputSize$1(input, "oscal-ssp");
|
|
17502
17900
|
if (!input || input.trim().length === 0) throw new Error("empty input");
|
|
17503
17901
|
const doc = parseJSON(input);
|
|
17504
17902
|
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')");
|
|
@@ -17675,7 +18073,7 @@ function mapOSCALComponentType(oscalType) {
|
|
|
17675
18073
|
* @returns HDF Baseline JSON string
|
|
17676
18074
|
*/
|
|
17677
18075
|
async function convertOscalProfileToHdf(profileInput, catalogInput) {
|
|
17678
|
-
validateInputSize(profileInput, "oscal-profile");
|
|
18076
|
+
validateInputSize$1(profileInput, "oscal-profile");
|
|
17679
18077
|
if (!profileInput || profileInput.trim().length === 0) throw new Error("empty profile input");
|
|
17680
18078
|
if (!catalogInput || catalogInput.trim().length === 0) throw new Error("empty catalog input");
|
|
17681
18079
|
const profileDoc = parseJSON(profileInput);
|
|
@@ -17872,8 +18270,8 @@ function toRfc3339(d) {
|
|
|
17872
18270
|
* TypeScript peer of shared/go/enrich_stix.go (kept at parity).
|
|
17873
18271
|
*/
|
|
17874
18272
|
function enrichStix(resultsInput, bundleInput, opts) {
|
|
17875
|
-
validateInputSize(resultsInput, "enrich-stix", opts?.maxSize);
|
|
17876
|
-
validateInputSize(bundleInput, "enrich-stix", opts?.maxSize);
|
|
18273
|
+
validateInputSize$1(resultsInput, "enrich-stix", opts?.maxSize);
|
|
18274
|
+
validateInputSize$1(bundleInput, "enrich-stix", opts?.maxSize);
|
|
17877
18275
|
if (!resultsInput) throw new Error("enrich-stix: empty results input");
|
|
17878
18276
|
const bundle = parseStixBundle(bundleInput);
|
|
17879
18277
|
let parsed;
|