@mitre/hdf-converters 3.5.1 → 3.6.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +1 -1
- package/dist/index.d.ts +58 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1336 -345
- package/dist/index.js.map +1 -1
- package/dist/{register-all-As5mLT-V.js → register-all-CDfYvwQF.js} +77 -1
- package/dist/register-all-CDfYvwQF.js.map +1 -0
- package/package.json +9 -9
- package/dist/register-all-As5mLT-V.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as detectFormat, r as detectConverter, t as registerAllFingerprints } from "./register-all-
|
|
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, impactToSeverity, normalizeHdfTimestamps, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, roundImpact, severityToImpact, sha256, stripHtml as stripHTML, trimUtcFraction, worstStatus } from "@mitre/hdf-utilities";
|
|
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
4
|
import { Applicability, AuthorizationStatus, CVSSSeverity, CategorizationLevel, ControlType, Ecosystem, EvidenceType, HashAlgorithm, IdentityType, Justification, MilestoneStatus, OverrideType, PlanType, ResultStatus, Severity, TargetType, VerificationMethodEnum, Version, createDescription, createMinimalBaseline, createRequirement, createResult, severityToImpact as severityToImpact$1 } from "@mitre/hdf-schema";
|
|
5
5
|
import { DEFAULT_COMPONENT_MANAGEMENT_NIST_TAGS, DEFAULT_CONFIG_MANAGEMENT_NIST_TAGS, DEFAULT_REMEDIATION_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS, DEFAULT_STATIC_ANALYSIS_NIST_TAGS as DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1, awsConfigMappedRevisions, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getAwsConfigNistControlsBySubstring, getCCINistMappings, getCurrentNistRevision, getCweNistControl, getHipcheckNistControls, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistToCci } from "@mitre/hdf-mappings";
|
|
6
6
|
//#region shared/typescript/converterutil.ts
|
|
@@ -26,6 +26,32 @@ async function inputChecksum(input) {
|
|
|
26
26
|
value: await sha256(input)
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
const DIGEST_HASH_ALGORITHMS = {
|
|
30
|
+
sha256: HashAlgorithm.Sha256,
|
|
31
|
+
sha384: HashAlgorithm.Sha384,
|
|
32
|
+
sha512: HashAlgorithm.Sha512,
|
|
33
|
+
blake3: HashAlgorithm.Blake3
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Convert a "<algo>:<hex>" digest into a Component.integrity checksum, folding
|
|
37
|
+
* the algorithm prefix into Checksum.algorithm. Returns undefined for an empty
|
|
38
|
+
* digest, one with no algorithm prefix, or one whose algorithm the closed
|
|
39
|
+
* Hash_Algorithm enum cannot represent (e.g. sha1/md5) — it never mislabels a
|
|
40
|
+
* digest under a wrong algorithm. Callers whose digest is embedded in a larger
|
|
41
|
+
* reference ("name@sha256:hex") must extract the "<algo>:<hex>" portion first.
|
|
42
|
+
* Go peer: DigestToChecksums.
|
|
43
|
+
*/
|
|
44
|
+
function digestToChecksums(digest) {
|
|
45
|
+
if (!digest) return void 0;
|
|
46
|
+
const i = digest.indexOf(":");
|
|
47
|
+
if (i < 0) return void 0;
|
|
48
|
+
const algorithm = DIGEST_HASH_ALGORITHMS[digest.slice(0, i)];
|
|
49
|
+
if (!algorithm) return void 0;
|
|
50
|
+
return [{
|
|
51
|
+
algorithm,
|
|
52
|
+
value: digest.slice(i + 1)
|
|
53
|
+
}];
|
|
54
|
+
}
|
|
29
55
|
/**
|
|
30
56
|
* Compute an Integrity object (for root-level document integrity) from raw input.
|
|
31
57
|
*
|
|
@@ -135,6 +161,20 @@ function extractCWEIDs(text) {
|
|
|
135
161
|
ids.sort();
|
|
136
162
|
return ids;
|
|
137
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Return the first candidate whose content is not empty or whitespace-only,
|
|
166
|
+
* as-is (the emptiness test trims, the returned value does not). Returns '' when
|
|
167
|
+
* every candidate is empty or whitespace. The caller supplies its own documented
|
|
168
|
+
* fallback as the last candidate so the substituted value is meaningful per
|
|
169
|
+
* field — this helper never invents a placeholder like "N/A". Use it where a
|
|
170
|
+
* target schema requires a non-empty (often minLength:1) string and a source
|
|
171
|
+
* value may be absent; it is a selection helper, not a normalizer, and never
|
|
172
|
+
* decides to OMIT a field (that stays the caller's call). Go parity: FirstNonEmpty.
|
|
173
|
+
*/
|
|
174
|
+
function firstNonEmpty(...candidates) {
|
|
175
|
+
for (const c of candidates) if (c.trim() !== "") return c;
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
138
178
|
/** Default maximum input size for converters (50MB) */
|
|
139
179
|
const DEFAULT_MAX_INPUT_SIZE = 50 * 1024 * 1024;
|
|
140
180
|
/**
|
|
@@ -165,6 +205,37 @@ function parseHdf(input) {
|
|
|
165
205
|
return parseJSON(normalizeHdfTimestamps(input));
|
|
166
206
|
}
|
|
167
207
|
/**
|
|
208
|
+
* The shared structural-input guard for HDF Results exporters: the prologue
|
|
209
|
+
* every exporter needs — empty guard, input-size validation, parse, and a
|
|
210
|
+
* top-level `baselines` array check — returning the parsed document and its
|
|
211
|
+
* baselines. Extracted from exportmap.runExport so non-exportmap exporters stop
|
|
212
|
+
* reinventing (and skipping) it. The "missing baselines field" wording matches
|
|
213
|
+
* exportmap and hdf-to-oscal-sar, so there is no consumer-visible message churn.
|
|
214
|
+
*/
|
|
215
|
+
function requireHdfResults(input, converterName) {
|
|
216
|
+
return requireHdfStructure(input, converterName, "baselines");
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Reject malformed HDF input (empty, oversized, non-JSON, top-level array/null,
|
|
220
|
+
* or a missing/wrong-typed structural key) before a converter zero-fills it into
|
|
221
|
+
* a typed document. A parsed JSON `null` (or array, or primitive) is NOT an HDF
|
|
222
|
+
* document object, so it produces the canonical missing-field error rather than
|
|
223
|
+
* a TypeError — matching Go's nil-map no-op on `null`.
|
|
224
|
+
*/
|
|
225
|
+
function requireHdfStructure(input, converterName, field) {
|
|
226
|
+
if (input.length === 0) throw new Error(`${converterName}: empty input`);
|
|
227
|
+
validateInputSize(input, converterName);
|
|
228
|
+
const parsed = parseHdf(input);
|
|
229
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${converterName}: invalid HDF structure: missing ${field} field`);
|
|
230
|
+
const doc = parsed;
|
|
231
|
+
const items = doc[field];
|
|
232
|
+
if (!Array.isArray(items)) throw new Error(`${converterName}: invalid HDF structure: missing ${field} field`);
|
|
233
|
+
return {
|
|
234
|
+
doc,
|
|
235
|
+
items
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
168
239
|
* Read a timestamp field off a parsed HDF document.
|
|
169
240
|
*
|
|
170
241
|
* The generated schema types these as `Date` (startTime, appliedAt, ...), but
|
|
@@ -241,7 +312,7 @@ function ecosystemFromPurlType(type) {
|
|
|
241
312
|
* - purl alone
|
|
242
313
|
* - cpe alone
|
|
243
314
|
*/
|
|
244
|
-
function buildAffectedPackage$
|
|
315
|
+
function buildAffectedPackage$2(opts) {
|
|
245
316
|
const pkg = {};
|
|
246
317
|
if (opts.purl) pkg.purl = opts.purl;
|
|
247
318
|
if (opts.cpe) pkg.cpe = opts.cpe;
|
|
@@ -444,6 +515,16 @@ function buildNoFindingsRequirement(id, codeDesc, startTime) {
|
|
|
444
515
|
tags: {}
|
|
445
516
|
};
|
|
446
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Shared marker converters emit when a source severity is unrated
|
|
520
|
+
* (isUnratedSeverity), so a defaulted impact stays distinguishable from a
|
|
521
|
+
* genuine rated medium. TS peer of MarkUnratedSeverity in converterutil.go.
|
|
522
|
+
*/
|
|
523
|
+
const UNRATED_SEVERITY_TAG = "severity_rating";
|
|
524
|
+
const UNRATED_SEVERITY_VALUE = "unrated";
|
|
525
|
+
function markUnratedSeverity(tags, severity) {
|
|
526
|
+
if (isUnratedSeverity(severity)) tags[UNRATED_SEVERITY_TAG] = UNRATED_SEVERITY_VALUE;
|
|
527
|
+
}
|
|
447
528
|
//#endregion
|
|
448
529
|
//#region converters/legacyhdf-to-hdf/typescript/converter.ts
|
|
449
530
|
/**
|
|
@@ -457,7 +538,7 @@ function buildNoFindingsRequirement(id, codeDesc, startTime) {
|
|
|
457
538
|
* - Overlay flattening: merge overlay/wrapper baselines so every requirement has results
|
|
458
539
|
*/
|
|
459
540
|
/** Valid severity values per HDF v2.0 schema (matches the Go converter). */
|
|
460
|
-
const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
541
|
+
const VALID_SEVERITIES$1 = /* @__PURE__ */ new Set([
|
|
461
542
|
"critical",
|
|
462
543
|
"high",
|
|
463
544
|
"medium",
|
|
@@ -471,7 +552,7 @@ const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
|
471
552
|
function tagSeverityToSeverity(tagSeverity) {
|
|
472
553
|
if (typeof tagSeverity !== "string") return null;
|
|
473
554
|
const normalized = tagSeverity.toLowerCase().trim();
|
|
474
|
-
return VALID_SEVERITIES.has(normalized) ? normalized : null;
|
|
555
|
+
return VALID_SEVERITIES$1.has(normalized) ? normalized : null;
|
|
475
556
|
}
|
|
476
557
|
/**
|
|
477
558
|
* Normalize status values from v1.0 to v2.0 format.
|
|
@@ -489,8 +570,9 @@ function normalizeStatus$1(status) {
|
|
|
489
570
|
}
|
|
490
571
|
/**
|
|
491
572
|
* Compute effectiveStatus from impact and v2 results via the canonical
|
|
492
|
-
* worst-wins ordering in @mitre/hdf-utilities (impact=0 → notApplicable
|
|
493
|
-
* error > failed > passed > notApplicable >
|
|
573
|
+
* worst-wins ordering in @mitre/hdf-utilities (impact=0 → notApplicable
|
|
574
|
+
* unless the roll-up is error; error > failed > passed > notApplicable >
|
|
575
|
+
* notReviewed).
|
|
494
576
|
*/
|
|
495
577
|
function computeEffectiveStatus$1(impact, results) {
|
|
496
578
|
return computeEffectiveStatus({
|
|
@@ -499,6 +581,18 @@ function computeEffectiveStatus$1(impact, results) {
|
|
|
499
581
|
});
|
|
500
582
|
}
|
|
501
583
|
/**
|
|
584
|
+
* Encodes a control-level execution error the source tool reported but did not
|
|
585
|
+
* record as a result, reusing the earliest recorded result time (matching the
|
|
586
|
+
* Go converter).
|
|
587
|
+
*/
|
|
588
|
+
function synthesizedErrorResult(results) {
|
|
589
|
+
return {
|
|
590
|
+
status: "error",
|
|
591
|
+
codeDesc: "Synthesized from the source control-level error status: the tool reported an execution error not reflected in any recorded result",
|
|
592
|
+
startTime: results.map((r) => r.startTime).filter((t) => typeof t === "string" && t !== "" && t !== "0001-01-01T00:00:00Z").sort()[0] ?? "0001-01-01T00:00:00Z"
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
502
596
|
* Convert v1.0 result to v2.0 result.
|
|
503
597
|
* Transforms snake_case field names to camelCase.
|
|
504
598
|
*/
|
|
@@ -592,9 +686,10 @@ function convertControl(v1Control) {
|
|
|
592
686
|
if (refs) v2Req.refs = refs;
|
|
593
687
|
}
|
|
594
688
|
if (v1Control.source_location !== void 0) v2Req.sourceLocation = v1Control.source_location;
|
|
595
|
-
|
|
689
|
+
const controlLevelError = v1Control.status !== void 0 && normalizeStatus$1(v1Control.status) === "error";
|
|
596
690
|
if (v1Control.results && Array.isArray(v1Control.results)) v2Req.results = v1Control.results.map(convertResult);
|
|
597
|
-
if (!v2Req.
|
|
691
|
+
if (controlLevelError && !(v2Req.results ?? []).some((r) => r.status === "error")) v2Req.results = [...v2Req.results ?? [], synthesizedErrorResult(v2Req.results ?? [])];
|
|
692
|
+
v2Req.effectiveStatus = computeEffectiveStatus$1(v1Control.impact, v2Req.results ?? []);
|
|
598
693
|
v2Req.severity = tagSeverityToSeverity(v1Control.tags?.severity) ?? impactToSeverity(v1Control.impact);
|
|
599
694
|
const rawNist = v1Control.tags?.nist;
|
|
600
695
|
if (Array.isArray(rawNist)) {
|
|
@@ -774,7 +869,7 @@ function documentTimestamp(v1Data) {
|
|
|
774
869
|
}
|
|
775
870
|
return latest ? formatTimestamp(latest) : void 0;
|
|
776
871
|
}
|
|
777
|
-
function
|
|
872
|
+
function convertLegacyHdf(v1Data, converterVersion = "1.0.0") {
|
|
778
873
|
validateInputSize(JSON.stringify(v1Data), "legacyhdf-to-hdf");
|
|
779
874
|
const v2 = {
|
|
780
875
|
baselines: (v1Data.profiles || []).map(convertProfile),
|
|
@@ -820,7 +915,7 @@ function convertV1ToV2(v1Data, converterVersion = "1.0.0") {
|
|
|
820
915
|
* @param data - Unknown data to validate
|
|
821
916
|
* @returns true if data looks like HDF v1.0
|
|
822
917
|
*/
|
|
823
|
-
function
|
|
918
|
+
function isLegacyHdf(data) {
|
|
824
919
|
if (typeof data !== "object" || data === null) return false;
|
|
825
920
|
const obj = data;
|
|
826
921
|
return typeof obj.version === "string" && Array.isArray(obj.profiles) && typeof obj.platform === "object" && obj.platform !== null;
|
|
@@ -1194,7 +1289,7 @@ function trivyLocation(o) {
|
|
|
1194
1289
|
if (el && el !== "0" && el !== sl) loc += `-${el}`;
|
|
1195
1290
|
return loc;
|
|
1196
1291
|
}
|
|
1197
|
-
function buildRequirement$
|
|
1292
|
+
function buildRequirement$19(id, group) {
|
|
1198
1293
|
const primary = group[0];
|
|
1199
1294
|
const impact = Math.max(...group.map(findingImpact));
|
|
1200
1295
|
const descriptions = [{
|
|
@@ -1241,7 +1336,7 @@ function buildBaseline$1(name, findings, resultsChecksum) {
|
|
|
1241
1336
|
groups.set(id, [f]);
|
|
1242
1337
|
}
|
|
1243
1338
|
}
|
|
1244
|
-
return createMinimalBaseline(name, order.map((id) => buildRequirement$
|
|
1339
|
+
return createMinimalBaseline(name, order.map((id) => buildRequirement$19(id, groups.get(id))), { resultsChecksum });
|
|
1245
1340
|
}
|
|
1246
1341
|
/** Converts an ASFF document to HDF Results JSON. */
|
|
1247
1342
|
async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
@@ -1289,7 +1384,7 @@ async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
|
1289
1384
|
}
|
|
1290
1385
|
//#endregion
|
|
1291
1386
|
//#region converters/sarif-to-hdf/typescript/converter.ts
|
|
1292
|
-
const
|
|
1387
|
+
const SARIF_ALIASES = {
|
|
1293
1388
|
error: .7,
|
|
1294
1389
|
warning: .5,
|
|
1295
1390
|
note: .3
|
|
@@ -1361,17 +1456,17 @@ function convertResultGroup(ruleId, rule, sarifResults, timestamp) {
|
|
|
1361
1456
|
const firstResult = sarifResults[0];
|
|
1362
1457
|
const { title, description } = deriveMetadata(firstResult, rule);
|
|
1363
1458
|
let cweIds = extractCweFromRule(rule);
|
|
1364
|
-
if (cweIds.length === 0) cweIds = extractCweIds(resolveMessageText(firstResult.message, rule));
|
|
1459
|
+
if (cweIds.length === 0) cweIds = extractCweIds$1(resolveMessageText(firstResult.message, rule));
|
|
1365
1460
|
const nistControls = mapCWEToNIST(cweIds, DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
1366
1461
|
const cciControls = nistToCci(nistControls);
|
|
1367
1462
|
const ruleLevel = resolveRuleLevel(rule, sarifResults);
|
|
1368
|
-
const impact =
|
|
1463
|
+
const impact = severityToImpactWithAliases(ruleLevel, SARIF_ALIASES, 0) || .1;
|
|
1369
1464
|
const sourceLocation = firstResult.locations && firstResult.locations.length > 0 ? extractSourceLocation$1(firstResult.locations[0]) : void 0;
|
|
1370
1465
|
const results = [];
|
|
1371
1466
|
for (const sr of sarifResults) results.push(...convertSarifResultToHDFResults(sr));
|
|
1372
1467
|
const descriptions = buildDescriptions$2(description, rule, firstResult);
|
|
1373
1468
|
const allSuppressions = sarifResults.flatMap((sr) => sr.suppressions ?? []);
|
|
1374
|
-
const options = { tags: buildTags$
|
|
1469
|
+
const options = { tags: buildTags$4(firstResult, rule, ruleLevel, cweIds, nistControls, cciControls, allSuppressions) };
|
|
1375
1470
|
if (sourceLocation) options.sourceLocation = sourceLocation;
|
|
1376
1471
|
const req = createRequirement(ruleId, title, descriptions, impact, results, options);
|
|
1377
1472
|
const controlType = deriveControlTypeFromTags(nistControls);
|
|
@@ -1431,7 +1526,7 @@ function packageFromSarifProperties(props) {
|
|
|
1431
1526
|
if (props.purl) ecosystem = ecosystemFromPurlType(parsePurl(props.purl)?.type);
|
|
1432
1527
|
else if (props.ecosystem) ecosystem = ecosystemFromPurlType(props.ecosystem);
|
|
1433
1528
|
else if (name && version) ecosystem = Ecosystem.Generic;
|
|
1434
|
-
return buildAffectedPackage$
|
|
1529
|
+
return buildAffectedPackage$2({
|
|
1435
1530
|
name,
|
|
1436
1531
|
version,
|
|
1437
1532
|
ecosystem,
|
|
@@ -1520,7 +1615,7 @@ function extractCweFromRule(rule) {
|
|
|
1520
1615
|
}
|
|
1521
1616
|
return [];
|
|
1522
1617
|
}
|
|
1523
|
-
function extractCweIds(text) {
|
|
1618
|
+
function extractCweIds$1(text) {
|
|
1524
1619
|
return extractCWEIDs(text).map((id) => `CWE-${id}`);
|
|
1525
1620
|
}
|
|
1526
1621
|
function mapKindToStatus(kind) {
|
|
@@ -1602,7 +1697,7 @@ function buildDescriptions$2(defaultDesc, rule, result) {
|
|
|
1602
1697
|
if (result.fixes && result.fixes.length > 0 && result.fixes[0]?.description?.text) descriptions.push(createDescription("fix", result.fixes[0].description.text));
|
|
1603
1698
|
return descriptions;
|
|
1604
1699
|
}
|
|
1605
|
-
function buildTags$
|
|
1700
|
+
function buildTags$4(result, rule, resolvedLevel, cweIds, nistControls, cciControls, allSuppressions) {
|
|
1606
1701
|
const tags = {
|
|
1607
1702
|
severity: resolvedLevel,
|
|
1608
1703
|
cwe: cweIds,
|
|
@@ -2181,7 +2276,7 @@ function ruleResultToRequirement(rr, ruleIndex, groupIndex, scanTime) {
|
|
|
2181
2276
|
const req = createRequirement(id, title, descriptions, impact, [result], { tags });
|
|
2182
2277
|
const code = buildCheckCode(check);
|
|
2183
2278
|
if (code) req.code = code;
|
|
2184
|
-
const refs = buildRefs$
|
|
2279
|
+
const refs = buildRefs$10(ruleDef?.reference);
|
|
2185
2280
|
if (refs.length > 0) req.refs = refs;
|
|
2186
2281
|
const hdfSeverity = xccdfSeverityToHdf(severity);
|
|
2187
2282
|
if (hdfSeverity) req.severity = hdfSeverity;
|
|
@@ -2338,7 +2433,7 @@ function checkDescriptionData(check) {
|
|
|
2338
2433
|
* href, when present, populates url. References that carry nothing are skipped.
|
|
2339
2434
|
* Inner keys are inserted alphabetically to match Go's map JSON ordering.
|
|
2340
2435
|
*/
|
|
2341
|
-
function buildRefs$
|
|
2436
|
+
function buildRefs$10(refs) {
|
|
2342
2437
|
if (!refs) return [];
|
|
2343
2438
|
const out = [];
|
|
2344
2439
|
for (const r of refs) {
|
|
@@ -2863,6 +2958,13 @@ function cklbTitle(cl) {
|
|
|
2863
2958
|
//#endregion
|
|
2864
2959
|
//#region shared/typescript/checklist/to-hdf.ts
|
|
2865
2960
|
const CONVERTER_VERSION = "1.0.0";
|
|
2961
|
+
const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
2962
|
+
"critical",
|
|
2963
|
+
"high",
|
|
2964
|
+
"medium",
|
|
2965
|
+
"low",
|
|
2966
|
+
"informational"
|
|
2967
|
+
]);
|
|
2866
2968
|
/**
|
|
2867
2969
|
* Map the format-neutral Checklist model to an HDF Results object.
|
|
2868
2970
|
* controlType is derived per-Vuln from CCI->NIST; verificationMethod and
|
|
@@ -2934,7 +3036,7 @@ function vulnToRequirement(v, scanTime) {
|
|
|
2934
3036
|
if (v.legacyIDs && v.legacyIDs.length) tags["legacy_ids"] = v.legacyIDs;
|
|
2935
3037
|
if (v.extra && Object.keys(v.extra).length > 0) tags["cklMetadata"] = { ...v.extra };
|
|
2936
3038
|
const req = createRequirement(v.vulnNum, v.ruleTitle ?? v.vulnNum, descriptions, impact, [result], { tags });
|
|
2937
|
-
if (severity) req.severity = severity;
|
|
3039
|
+
if (VALID_SEVERITIES.has(severity)) req.severity = severity;
|
|
2938
3040
|
const controlType = deriveControlTypeFromTags(nistTags);
|
|
2939
3041
|
if (controlType !== void 0) req.controlType = controlType;
|
|
2940
3042
|
return req;
|
|
@@ -2985,6 +3087,36 @@ function setIf$1(m, key, val) {
|
|
|
2985
3087
|
if (val) m[key] = val;
|
|
2986
3088
|
}
|
|
2987
3089
|
//#endregion
|
|
3090
|
+
//#region shared/typescript/status.ts
|
|
3091
|
+
/**
|
|
3092
|
+
* Bridge from schema-typed requirements onto the canonical effective-status
|
|
3093
|
+
* helper in @mitre/hdf-utilities, so every consumer computes status through
|
|
3094
|
+
* the single shared implementation (twin of shared/go/status.go). The stored
|
|
3095
|
+
* effectiveStatus field is never read — it is an output cache (see
|
|
3096
|
+
* status-determination.md).
|
|
3097
|
+
*/
|
|
3098
|
+
/** RFC3339 string from a schema timestamp (quicktype Date or raw string). */
|
|
3099
|
+
function stamp(v) {
|
|
3100
|
+
if (v instanceof Date) return v.toISOString();
|
|
3101
|
+
if (typeof v === "string" && v !== "") return v;
|
|
3102
|
+
}
|
|
3103
|
+
/** Maps a requirement onto the canonical effective-status input shape. */
|
|
3104
|
+
function requirementStatusInput(req) {
|
|
3105
|
+
return {
|
|
3106
|
+
impact: req.impact,
|
|
3107
|
+
resultStatuses: (req.results ?? []).map((r) => String(r.status)),
|
|
3108
|
+
overrides: (req.statusOverrides ?? []).map((o) => ({
|
|
3109
|
+
status: o.status ? String(o.status) : void 0,
|
|
3110
|
+
appliedAt: stamp(o.appliedAt),
|
|
3111
|
+
expiresAt: stamp(o.expiresAt)
|
|
3112
|
+
}))
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
/** The requirement's canonical effective status via the shared ladder. */
|
|
3116
|
+
function requirementEffectiveStatus(req) {
|
|
3117
|
+
return computeEffectiveStatus(requirementStatusInput(req));
|
|
3118
|
+
}
|
|
3119
|
+
//#endregion
|
|
2988
3120
|
//#region shared/typescript/checklist/from-hdf.ts
|
|
2989
3121
|
/**
|
|
2990
3122
|
* Map HDF Results back to the format-neutral Checklist model. When the HDF
|
|
@@ -3077,7 +3209,7 @@ function requirementToVuln(req) {
|
|
|
3077
3209
|
};
|
|
3078
3210
|
}
|
|
3079
3211
|
function effectiveOrRawStatus(req) {
|
|
3080
|
-
return req
|
|
3212
|
+
return requirementEffectiveStatus(req);
|
|
3081
3213
|
}
|
|
3082
3214
|
function composeFindingDetails(req) {
|
|
3083
3215
|
const results = req.results ?? [];
|
|
@@ -3252,6 +3384,527 @@ function convertHdfToCklb(input) {
|
|
|
3252
3384
|
return serializeCklb(hdfToChecklist(input));
|
|
3253
3385
|
}
|
|
3254
3386
|
//#endregion
|
|
3387
|
+
//#region converters/kics-to-hdf/typescript/mapping-data.ts
|
|
3388
|
+
const kicsMappingData = {};
|
|
3389
|
+
//#endregion
|
|
3390
|
+
//#region converters/kics-to-hdf/typescript/converter.ts
|
|
3391
|
+
/**
|
|
3392
|
+
* KICS publishes five severities. SARIF collapses CRITICAL and HIGH into
|
|
3393
|
+
* `error`; converting the native format keeps them apart, which is a large part
|
|
3394
|
+
* of why this converter exists. The shared standard map covers four of them,
|
|
3395
|
+
* with INFO at the canonical 0.0 tier like every other converter: the
|
|
3396
|
+
* effective-status layer maps impact-0 requirements to notApplicable, so
|
|
3397
|
+
* info-tier findings stay visible in the output without entering the
|
|
3398
|
+
* compliance ratio. TRACE is KICS-specific and joins the info tier as an
|
|
3399
|
+
* alias — the Go peer of this table is kicsSeverityAliases.
|
|
3400
|
+
*/
|
|
3401
|
+
const KICS_SEVERITY_ALIASES = { trace: 0 };
|
|
3402
|
+
const DEFAULT_IMPACT$1 = .5;
|
|
3403
|
+
/**
|
|
3404
|
+
* Records which tier resolved a requirement's NIST tags, so a reviewed mapping
|
|
3405
|
+
* stays distinguishable from a CWE-derived guess and from a static default.
|
|
3406
|
+
* Follows the precedent set by UNRATED_SEVERITY_TAG in converterutil, which
|
|
3407
|
+
* exists so a defaulted impact stays distinguishable from a genuine rated one.
|
|
3408
|
+
*
|
|
3409
|
+
* The per-query table is authoritative, matching how the Checkov and AWS Config
|
|
3410
|
+
* mappers in the v1 line work: the rule-to-control decision is reviewed before
|
|
3411
|
+
* shipping rather than computed at conversion time, which is also what lets
|
|
3412
|
+
* those tables carry control enhancements. CWE is a fallback because it is a
|
|
3413
|
+
* lossy proxy — only 30 of the 102 CWEs KICS uses resolve against the CWE→NIST
|
|
3414
|
+
* table, 52% of queries by volume.
|
|
3415
|
+
*/
|
|
3416
|
+
const NIST_MAPPING_TAG = "nistMapping";
|
|
3417
|
+
const NIST_MAPPING_TABLE = "mapped";
|
|
3418
|
+
const NIST_MAPPING_CWE = "cwe-derived";
|
|
3419
|
+
const NIST_MAPPING_FALLBACK = "static-fallback";
|
|
3420
|
+
function impactFor$1(severity) {
|
|
3421
|
+
return severityToImpactWithAliases(severity, KICS_SEVERITY_ALIASES, DEFAULT_IMPACT$1);
|
|
3422
|
+
}
|
|
3423
|
+
/** KICS emits the bare number, e.g. "778". Normalize before the lookup. */
|
|
3424
|
+
function cweIdentifiers(query) {
|
|
3425
|
+
const raw = query.cwe;
|
|
3426
|
+
if (raw === void 0 || raw === null || String(raw).trim() === "") return [];
|
|
3427
|
+
const id = String(raw).trim().replace(/^CWE-/i, "");
|
|
3428
|
+
return /^\d+$/.test(id) ? [`CWE-${id}`] : [];
|
|
3429
|
+
}
|
|
3430
|
+
function locationFor(file) {
|
|
3431
|
+
const parts = [`File: ${file.file_name || "unknown"}`];
|
|
3432
|
+
if (typeof file.line === "number" && file.line > 0) parts.push(`Line: ${file.line}`);
|
|
3433
|
+
if (file.resource_type) parts.push(`Resource type: ${file.resource_type}`);
|
|
3434
|
+
if (file.resource_name && file.resource_name !== "unknown") parts.push(`Resource: ${file.resource_name}`);
|
|
3435
|
+
if (file.search_key) parts.push(`Key: ${file.search_key}`);
|
|
3436
|
+
if (file.similarity_id) parts.push(`Similarity ID: ${file.similarity_id}`);
|
|
3437
|
+
return parts.join("\n");
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* The remediation pair. SARIF keeps only `actual_value` inside its message, so
|
|
3441
|
+
* a SARIF-derived control can say what the configuration is but never what it
|
|
3442
|
+
* should be.
|
|
3443
|
+
*/
|
|
3444
|
+
function evidenceFor(file) {
|
|
3445
|
+
const parts = [];
|
|
3446
|
+
if (file.expected_value) parts.push(`Expected: ${file.expected_value}`);
|
|
3447
|
+
if (file.actual_value) parts.push(`Actual: ${file.actual_value}`);
|
|
3448
|
+
if (file.issue_type) parts.push(`Issue type: ${file.issue_type}`);
|
|
3449
|
+
if (file.search_value) parts.push(`Search value: ${file.search_value}`);
|
|
3450
|
+
return parts.join("\n");
|
|
3451
|
+
}
|
|
3452
|
+
/**
|
|
3453
|
+
* Resolution order: the reviewed per-query table, then the query's CWE, then the
|
|
3454
|
+
* static-analysis defaults. `table` is a parameter so all three tiers are
|
|
3455
|
+
* testable without shipping unreviewed rows in the table itself.
|
|
3456
|
+
*/
|
|
3457
|
+
function resolveControls(query, table = kicsMappingData) {
|
|
3458
|
+
const entry = typeof query.query_id === "string" && Object.prototype.hasOwnProperty.call(table, query.query_id) ? table[query.query_id] : void 0;
|
|
3459
|
+
if (entry !== void 0 && entry.nist.length > 0) return {
|
|
3460
|
+
nist: entry.nist,
|
|
3461
|
+
cci: entry.cci,
|
|
3462
|
+
source: NIST_MAPPING_TABLE
|
|
3463
|
+
};
|
|
3464
|
+
const fromCwe = mapCWEToNIST(cweIdentifiers(query), []);
|
|
3465
|
+
if (fromCwe.length > 0) return {
|
|
3466
|
+
nist: fromCwe,
|
|
3467
|
+
cci: nistToCci(fromCwe),
|
|
3468
|
+
source: NIST_MAPPING_CWE
|
|
3469
|
+
};
|
|
3470
|
+
const fallback = [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS];
|
|
3471
|
+
return {
|
|
3472
|
+
nist: fallback,
|
|
3473
|
+
cci: nistToCci(fallback),
|
|
3474
|
+
source: NIST_MAPPING_FALLBACK
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3477
|
+
function tagsFor$1(query) {
|
|
3478
|
+
const cwe = cweIdentifiers(query);
|
|
3479
|
+
const { nist, cci, source } = resolveControls(query);
|
|
3480
|
+
const extras = { [NIST_MAPPING_TAG]: source };
|
|
3481
|
+
if (cwe.length > 0) extras.cwe = cwe;
|
|
3482
|
+
if (query.severity) extras.severity = query.severity;
|
|
3483
|
+
if (query.platform) extras.platform = query.platform;
|
|
3484
|
+
if (query.cloud_provider) extras.cloudProvider = query.cloud_provider;
|
|
3485
|
+
if (query.category) extras.category = query.category;
|
|
3486
|
+
if (query.query_url) extras.queryUrl = query.query_url;
|
|
3487
|
+
if (query.risk_score != null) extras.riskScore = String(query.risk_score);
|
|
3488
|
+
if (query.description_id) extras.descriptionId = query.description_id;
|
|
3489
|
+
if (query.experimental) extras.experimental = true;
|
|
3490
|
+
const issueTypes = [...new Set((query.files ?? []).map((f) => f.issue_type).filter(Boolean))];
|
|
3491
|
+
if (issueTypes.length > 0) extras.issueType = issueTypes;
|
|
3492
|
+
const resourceTypes = [...new Set((query.files ?? []).map((f) => f.resource_type).filter(Boolean))];
|
|
3493
|
+
if (resourceTypes.length > 0) extras.resourceType = resourceTypes;
|
|
3494
|
+
markUnratedSeverity(extras, query.severity);
|
|
3495
|
+
return buildNistCciTags(nist, cci, extras);
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* KICS reports violations only. Its output carries no record of the queries
|
|
3499
|
+
* that ran without finding anything — `queries[]` contains only those that
|
|
3500
|
+
* fired — so no passing requirement can be derived from it, and a converted
|
|
3501
|
+
* profile is failures-only by construction.
|
|
3502
|
+
*
|
|
3503
|
+
* That makes the compliance percentage misleading on its own: a scan where 72
|
|
3504
|
+
* of 2,034 queries fired renders as 100% failed. This requirement carries the
|
|
3505
|
+
* denominator so the ratio is legible.
|
|
3506
|
+
*
|
|
3507
|
+
* Impact 0 and status notApplicable both keep it out of the score: the
|
|
3508
|
+
* effective-status layer (computeEffectiveStatus) maps impact-0 requirements
|
|
3509
|
+
* to notApplicable before statuses are counted, and notApplicable is the one
|
|
3510
|
+
* status the compliance rollup excludes from both numerator and denominator.
|
|
3511
|
+
* Emitting notApplicable as the raw status too keeps raw-status consumers
|
|
3512
|
+
* agreeing with the effective view — Passed would export to CKL as
|
|
3513
|
+
* NotAFinding, a compliant-looking control that was never checked, and count
|
|
3514
|
+
* as a free pass in raw status rollups.
|
|
3515
|
+
*/
|
|
3516
|
+
function buildCoverageRequirement$1(report, startTime) {
|
|
3517
|
+
const executed = report.queries_total ?? 0;
|
|
3518
|
+
const fired = (report.queries ?? []).filter((q) => (q.files ?? []).length > 0).length;
|
|
3519
|
+
const summary = `KICS executed ${executed} queries against ${report.files_scanned ?? 0} file(s) (${report.files_parsed ?? 0} parsed); ${fired} produced findings. KICS reports violations only and does not enumerate the queries that ran without finding anything, so no passing requirements can be derived from its output and the compliance ratio should not be read as a pass rate.`;
|
|
3520
|
+
return {
|
|
3521
|
+
id: "kics-scan-coverage",
|
|
3522
|
+
title: "KICS scan coverage",
|
|
3523
|
+
impact: 0,
|
|
3524
|
+
descriptions: [{
|
|
3525
|
+
label: "default",
|
|
3526
|
+
data: summary
|
|
3527
|
+
}],
|
|
3528
|
+
results: [{
|
|
3529
|
+
status: ResultStatus.NotApplicable,
|
|
3530
|
+
codeDesc: summary,
|
|
3531
|
+
startTime
|
|
3532
|
+
}],
|
|
3533
|
+
tags: {
|
|
3534
|
+
queriesExecuted: executed,
|
|
3535
|
+
queriesWithFindings: fired,
|
|
3536
|
+
filesScanned: report.files_scanned ?? 0,
|
|
3537
|
+
filesParsed: report.files_parsed ?? 0,
|
|
3538
|
+
filesFailedToScan: report.files_failed_to_scan ?? 0,
|
|
3539
|
+
queriesFailedToExecute: report.queries_failed_to_execute ?? 0
|
|
3540
|
+
}
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
function buildRequirement$18(query, startTime) {
|
|
3544
|
+
const tags = tagsFor$1(query);
|
|
3545
|
+
const descriptions = [{
|
|
3546
|
+
label: "default",
|
|
3547
|
+
data: query.description ?? ""
|
|
3548
|
+
}];
|
|
3549
|
+
const results = (query.files ?? []).map((file) => ({
|
|
3550
|
+
status: ResultStatus.Failed,
|
|
3551
|
+
codeDesc: locationFor(file),
|
|
3552
|
+
message: evidenceFor(file),
|
|
3553
|
+
startTime
|
|
3554
|
+
}));
|
|
3555
|
+
const requirement = createRequirement(query.query_id || query.query_name || "unknown", query.query_name || query.query_id || "Unnamed KICS query", descriptions, impactFor$1(query.severity), results, { tags });
|
|
3556
|
+
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3557
|
+
const controlType = deriveControlTypeFromTags(tags.nist);
|
|
3558
|
+
if (controlType !== void 0) requirement.controlType = controlType;
|
|
3559
|
+
return requirement;
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* Converts native `kics scan --report-formats json` output to HDF.
|
|
3563
|
+
*
|
|
3564
|
+
* KICS also emits SARIF, which carries its CWE taxonomy properly. What SARIF
|
|
3565
|
+
* drops is the remediation pair (`expected_value`), the add-a-block versus
|
|
3566
|
+
* fix-a-value distinction (`issue_type`), the stable finding fingerprint
|
|
3567
|
+
* (`similarity_id`), and one level of severity granularity.
|
|
3568
|
+
*
|
|
3569
|
+
* @param input - KICS JSON report string
|
|
3570
|
+
* @returns HDF JSON string
|
|
3571
|
+
*/
|
|
3572
|
+
async function convertKicsToHdf(input, converterVersion = "1.0.0") {
|
|
3573
|
+
validateInputSize(input, "kics");
|
|
3574
|
+
if (!input || input.trim().length === 0) throw new Error("kics: empty input");
|
|
3575
|
+
const report = parseJSON(input);
|
|
3576
|
+
if (!Array.isArray(report.queries) || typeof report.kics_version !== "string") throw new Error("kics: input does not look like a KICS report");
|
|
3577
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
3578
|
+
const requirements = report.queries.filter((q) => Array.isArray(q.files) && q.files.length > 0).map((q) => buildRequirement$18(q, startTime));
|
|
3579
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("kics-no-findings", `KICS scanned ${report.files_scanned ?? 0} file(s) and reported no findings.`, startTime));
|
|
3580
|
+
requirements.push(buildCoverageRequirement$1(report, startTime));
|
|
3581
|
+
const baseline = createMinimalBaseline("KICS Scan", requirements, {
|
|
3582
|
+
title: "KICS infrastructure-as-code scan",
|
|
3583
|
+
resultsChecksum: await inputChecksum(input)
|
|
3584
|
+
});
|
|
3585
|
+
return buildHdfResults({
|
|
3586
|
+
generatorName: "kics-to-hdf",
|
|
3587
|
+
converterVersion,
|
|
3588
|
+
toolName: "KICS",
|
|
3589
|
+
toolVersion: report.kics_version,
|
|
3590
|
+
toolFormat: "json",
|
|
3591
|
+
baselines: [baseline],
|
|
3592
|
+
timestamp: startTime
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
//#endregion
|
|
3596
|
+
//#region converters/semgrep-to-hdf/typescript/converter.ts
|
|
3597
|
+
/**
|
|
3598
|
+
* Semgrep's native three-level scale mapped onto the canonical impact tiers,
|
|
3599
|
+
* mirroring the sarif converter's error/warning/note aliases. INFO is
|
|
3600
|
+
* deliberately 0.3 (an actionable low, semgrep's analogue of SARIF 'note'),
|
|
3601
|
+
* not the canonical 0.0 info tier. Supply-chain severities
|
|
3602
|
+
* (critical/high/medium/low) resolve through the shared canonical map.
|
|
3603
|
+
*/
|
|
3604
|
+
const SEMGREP_SEVERITY_ALIASES = {
|
|
3605
|
+
error: .7,
|
|
3606
|
+
warning: .5,
|
|
3607
|
+
info: .3
|
|
3608
|
+
};
|
|
3609
|
+
/**
|
|
3610
|
+
* An unrecognized or absent severity is treated as moderate; the absent case
|
|
3611
|
+
* additionally carries the shared unrated marker so consumers can tell a
|
|
3612
|
+
* defaulted 0.5 from a genuine medium.
|
|
3613
|
+
*/
|
|
3614
|
+
const DEFAULT_IMPACT = .5;
|
|
3615
|
+
/** Fields Semgrep redacts in unauthenticated (OSS) scans. */
|
|
3616
|
+
const REDACTED_PLACEHOLDER = "requires login";
|
|
3617
|
+
const SCAN_ERRORS_ID = "semgrep-scan-errors";
|
|
3618
|
+
const COVERAGE_ID = "semgrep-scan-coverage";
|
|
3619
|
+
function isPresent(value) {
|
|
3620
|
+
return typeof value === "string" && value.length > 0 && value !== REDACTED_PLACEHOLDER;
|
|
3621
|
+
}
|
|
3622
|
+
/**
|
|
3623
|
+
* Array entries are filtered per-entry (non-string members dropped); a bare
|
|
3624
|
+
* empty string is treated as absent, matching the Go StringOrSlice decoder.
|
|
3625
|
+
*/
|
|
3626
|
+
function normalizeToArray(value) {
|
|
3627
|
+
if (Array.isArray(value)) return value.filter((entry) => typeof entry === "string");
|
|
3628
|
+
return typeof value === "string" && value !== "" ? [value] : [];
|
|
3629
|
+
}
|
|
3630
|
+
function lineOf(position) {
|
|
3631
|
+
const line = position?.line;
|
|
3632
|
+
return typeof line === "number" && line > 0 ? line : void 0;
|
|
3633
|
+
}
|
|
3634
|
+
/**
|
|
3635
|
+
* Semgrep emits CWEs in prose form -- 'CWE-89: Improper Neutralization of ...'
|
|
3636
|
+
* -- while the mapping keys on the bare number.
|
|
3637
|
+
*/
|
|
3638
|
+
function extractCweIds(metadata) {
|
|
3639
|
+
return normalizeToArray(metadata.cwe).map((entry) => /CWE-(\d+)/i.exec(entry)?.[1]).filter((id) => id !== void 0).map((id) => Number.parseInt(id, 10)).filter((id) => Number.isFinite(id));
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* The parsed CWE ids in the CWE-N catalog form the schema's cwe[] field asks
|
|
3643
|
+
* for, deduplicated in source order.
|
|
3644
|
+
*/
|
|
3645
|
+
function cweCatalogFor(metadata) {
|
|
3646
|
+
return [...new Set(extractCweIds(metadata).map((id) => `CWE-${id}`))];
|
|
3647
|
+
}
|
|
3648
|
+
function nistControlsFor(metadata) {
|
|
3649
|
+
const controls = extractCweIds(metadata).map((cweId) => getCweNistControl(cweId)).filter(isPresent);
|
|
3650
|
+
const deduped = [...new Set(controls)];
|
|
3651
|
+
return deduped.length > 0 ? deduped : [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1];
|
|
3652
|
+
}
|
|
3653
|
+
function impactFor(result) {
|
|
3654
|
+
const severity = result.extra?.severity;
|
|
3655
|
+
if (!isPresent(severity)) return DEFAULT_IMPACT;
|
|
3656
|
+
return severityToImpactWithAliases(severity, SEMGREP_SEVERITY_ALIASES, DEFAULT_IMPACT);
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Semgrep rule ids are dotted paths whose final segment is the rule name. The
|
|
3660
|
+
* JSON output carries no human-readable rule title anywhere -- unlike the SARIF
|
|
3661
|
+
* output, whose rule objects have `name` and `shortDescription` -- so one is
|
|
3662
|
+
* derived. Separator runs collapse and only the first character of each word
|
|
3663
|
+
* is uppercased, matching the Go twin exactly.
|
|
3664
|
+
*/
|
|
3665
|
+
function titleFor(checkId) {
|
|
3666
|
+
const segments = checkId.split(".").filter((segment) => segment.length > 0);
|
|
3667
|
+
return (segments[segments.length - 1] ?? checkId).split(/[-_]+/).filter((word) => word.length > 0).map((word) => word[0].toUpperCase() + word.slice(1)).join(" ");
|
|
3668
|
+
}
|
|
3669
|
+
function codeDescFor(result) {
|
|
3670
|
+
const path = isPresent(result.path) ? result.path : "unknown";
|
|
3671
|
+
const startLine = lineOf(result.start);
|
|
3672
|
+
const endLine = lineOf(result.end);
|
|
3673
|
+
if (startLine === void 0) return `Path: ${path}`;
|
|
3674
|
+
return `Path: ${path}, ${endLine === void 0 || endLine === startLine ? `line ${startLine}` : `lines ${startLine}-${endLine}`}`;
|
|
3675
|
+
}
|
|
3676
|
+
/**
|
|
3677
|
+
* Points at the first occurrence of the rule; per-occurrence locations remain
|
|
3678
|
+
* on each result's codeDesc.
|
|
3679
|
+
*/
|
|
3680
|
+
function sourceLocationFor(result) {
|
|
3681
|
+
if (!isPresent(result.path)) return;
|
|
3682
|
+
const location = { ref: result.path };
|
|
3683
|
+
const line = lineOf(result.start);
|
|
3684
|
+
if (line !== void 0) location.line = line;
|
|
3685
|
+
return location;
|
|
3686
|
+
}
|
|
3687
|
+
function messageFor(result) {
|
|
3688
|
+
const parts = [];
|
|
3689
|
+
if (isPresent(result.extra?.lines)) parts.push(`Matched code:\n${result.extra.lines}`);
|
|
3690
|
+
if (isPresent(result.extra?.fix)) parts.push(`Suggested fix -- replace the matched code with:\n${result.extra.fix}`);
|
|
3691
|
+
return parts.join("\n\n");
|
|
3692
|
+
}
|
|
3693
|
+
/** Documentation and cross-framework links, deduplicated. */
|
|
3694
|
+
function referencesFor(metadata) {
|
|
3695
|
+
const urls = [
|
|
3696
|
+
...normalizeToArray(metadata.references),
|
|
3697
|
+
metadata.source,
|
|
3698
|
+
metadata.shortlink,
|
|
3699
|
+
metadata["source-rule-url"],
|
|
3700
|
+
typeof metadata.asvs?.control_url === "string" ? metadata.asvs.control_url : void 0
|
|
3701
|
+
].filter(isPresent);
|
|
3702
|
+
return [...new Set(urls)];
|
|
3703
|
+
}
|
|
3704
|
+
/** The deduplicated reference URLs in refs[], their structured HDF home. */
|
|
3705
|
+
function refsFor(metadata) {
|
|
3706
|
+
return referencesFor(metadata).map((url) => ({ url }));
|
|
3707
|
+
}
|
|
3708
|
+
function asvsOf(metadata) {
|
|
3709
|
+
const asvs = metadata.asvs;
|
|
3710
|
+
if (asvs && typeof asvs === "object" && !Array.isArray(asvs) && Object.keys(asvs).length > 0) return asvs;
|
|
3711
|
+
}
|
|
3712
|
+
function tagsFor(metadata, checkId, severity) {
|
|
3713
|
+
const nist = [...new Set(nistControlsFor(metadata))];
|
|
3714
|
+
const cci = nistToCci(nist);
|
|
3715
|
+
const extras = { checkId };
|
|
3716
|
+
const cwe = normalizeToArray(metadata.cwe);
|
|
3717
|
+
if (cwe.length > 0) extras.cwe = cwe;
|
|
3718
|
+
const owasp = normalizeToArray(metadata.owasp);
|
|
3719
|
+
if (owasp.length > 0) extras.owasp = owasp;
|
|
3720
|
+
const subcategory = normalizeToArray(metadata.subcategory);
|
|
3721
|
+
if (subcategory.length > 0) extras.subcategory = subcategory;
|
|
3722
|
+
const technology = normalizeToArray(metadata.technology);
|
|
3723
|
+
if (technology.length > 0) extras.technology = technology;
|
|
3724
|
+
const vulnerabilityClass = normalizeToArray(metadata.vulnerability_class);
|
|
3725
|
+
if (vulnerabilityClass.length > 0) extras.vulnerabilityClass = vulnerabilityClass;
|
|
3726
|
+
if (isPresent(severity)) extras.severity = severity;
|
|
3727
|
+
if (isPresent(metadata.confidence)) extras.confidence = metadata.confidence;
|
|
3728
|
+
if (isPresent(metadata.likelihood)) extras.likelihood = metadata.likelihood;
|
|
3729
|
+
if (isPresent(metadata.impact)) extras.semgrepImpact = metadata.impact;
|
|
3730
|
+
if (isPresent(metadata.category)) extras.category = metadata.category;
|
|
3731
|
+
if (isPresent(metadata["bandit-code"])) extras.banditCode = metadata["bandit-code"];
|
|
3732
|
+
const asvs = asvsOf(metadata);
|
|
3733
|
+
if (asvs) extras.asvs = asvs;
|
|
3734
|
+
markUnratedSeverity(extras, isPresent(severity) ? severity : "");
|
|
3735
|
+
return buildNistCciTags(nist, cci, extras);
|
|
3736
|
+
}
|
|
3737
|
+
function codePositionFor(position) {
|
|
3738
|
+
const line = lineOf(position);
|
|
3739
|
+
if (line === void 0) return;
|
|
3740
|
+
const out = { line };
|
|
3741
|
+
const col = position?.col;
|
|
3742
|
+
if (typeof col === "number" && col > 0) out.col = col;
|
|
3743
|
+
return out;
|
|
3744
|
+
}
|
|
3745
|
+
/**
|
|
3746
|
+
* Serializes the curated match envelope into requirement.code for the CODE
|
|
3747
|
+
* tab: the rule source itself is not present in semgrep's JSON output, and the
|
|
3748
|
+
* raw finding bytes are not byte-stable across the Go/TS pair (escape forms
|
|
3749
|
+
* differ), so both languages serialize this envelope field-for-field in the
|
|
3750
|
+
* same order. Rule metadata is deliberately excluded — it is already carried
|
|
3751
|
+
* structurally in tags, cwe[], and refs[] — and redacted fields are filtered
|
|
3752
|
+
* per the converter's redaction policy.
|
|
3753
|
+
*/
|
|
3754
|
+
function codeFor(result) {
|
|
3755
|
+
const envelope = { check_id: result.check_id };
|
|
3756
|
+
if (isPresent(result.path)) envelope.path = result.path;
|
|
3757
|
+
const start = codePositionFor(result.start);
|
|
3758
|
+
if (start) envelope.start = start;
|
|
3759
|
+
const end = codePositionFor(result.end);
|
|
3760
|
+
if (end) envelope.end = end;
|
|
3761
|
+
const extra = {};
|
|
3762
|
+
if (isPresent(result.extra?.message)) extra.message = result.extra.message;
|
|
3763
|
+
if (isPresent(result.extra?.severity)) extra.severity = result.extra.severity;
|
|
3764
|
+
if (isPresent(result.extra?.lines)) extra.lines = result.extra.lines;
|
|
3765
|
+
if (isPresent(result.extra?.fix)) extra.fix = result.extra.fix;
|
|
3766
|
+
if (Object.keys(extra).length > 0) envelope.extra = extra;
|
|
3767
|
+
return JSON.stringify(envelope, null, 2);
|
|
3768
|
+
}
|
|
3769
|
+
/** Every occurrence of one rule becomes a result under a single requirement. */
|
|
3770
|
+
function buildRequirement$17(checkId, results, startTime) {
|
|
3771
|
+
const representative = results[0];
|
|
3772
|
+
const metadata = representative.extra?.metadata ?? {};
|
|
3773
|
+
const message = representative.extra?.message;
|
|
3774
|
+
const descriptions = [{
|
|
3775
|
+
label: "default",
|
|
3776
|
+
data: typeof message === "string" ? message : ""
|
|
3777
|
+
}];
|
|
3778
|
+
const requirementResults = results.map((result) => ({
|
|
3779
|
+
status: ResultStatus.Failed,
|
|
3780
|
+
codeDesc: codeDescFor(result),
|
|
3781
|
+
message: messageFor(result),
|
|
3782
|
+
startTime
|
|
3783
|
+
}));
|
|
3784
|
+
const tags = tagsFor(metadata, checkId, representative.extra?.severity);
|
|
3785
|
+
const cwe = cweCatalogFor(metadata);
|
|
3786
|
+
const refs = refsFor(metadata);
|
|
3787
|
+
const sourceLocation = sourceLocationFor(representative);
|
|
3788
|
+
const requirement = createRequirement(checkId, titleFor(checkId), descriptions, impactFor(representative), requirementResults, {
|
|
3789
|
+
tags,
|
|
3790
|
+
code: codeFor(representative),
|
|
3791
|
+
...cwe.length > 0 ? { cwe } : {},
|
|
3792
|
+
...refs.length > 0 ? { refs } : {},
|
|
3793
|
+
...sourceLocation ? { sourceLocation } : {}
|
|
3794
|
+
});
|
|
3795
|
+
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3796
|
+
const controlType = deriveControlTypeFromTags(tags.nist);
|
|
3797
|
+
if (controlType !== void 0) requirement.controlType = controlType;
|
|
3798
|
+
return requirement;
|
|
3799
|
+
}
|
|
3800
|
+
/**
|
|
3801
|
+
* Scan failures become their own requirement so a file that failed to parse is
|
|
3802
|
+
* visible rather than buried: absence of findings in it is not evidence of
|
|
3803
|
+
* compliance. errors[].level drives the status: level 'error' entries are scan
|
|
3804
|
+
* failures (status error), while level 'warn' entries are advisory (e.g.
|
|
3805
|
+
* PartialParsing — the file was partially analyzed), a genuine non-evaluation
|
|
3806
|
+
* of those paths that must not dominate worst-wins rollups, so they map to
|
|
3807
|
+
* notReviewed.
|
|
3808
|
+
*/
|
|
3809
|
+
function buildErrorsRequirement(errors, startTime) {
|
|
3810
|
+
const results = errors.map((error) => {
|
|
3811
|
+
const kind = Array.isArray(error.type) ? String(error.type[0]) : String(error.type ?? "Unknown");
|
|
3812
|
+
const level = isPresent(error.level) ? error.level : "";
|
|
3813
|
+
const message = typeof error.message === "string" ? error.message : "";
|
|
3814
|
+
return {
|
|
3815
|
+
status: level.toLowerCase() === "warn" ? ResultStatus.NotReviewed : ResultStatus.Error,
|
|
3816
|
+
codeDesc: `Path: ${isPresent(error.path) ? error.path : "unknown"}`,
|
|
3817
|
+
message: level === "" ? `${kind}: ${message}` : `${kind} [${level}]: ${message}`,
|
|
3818
|
+
startTime
|
|
3819
|
+
};
|
|
3820
|
+
});
|
|
3821
|
+
const tags = buildNistCciTags([...DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1], []);
|
|
3822
|
+
markUnratedSeverity(tags, "");
|
|
3823
|
+
const requirement = createRequirement(SCAN_ERRORS_ID, "Semgrep scan errors", [{
|
|
3824
|
+
label: "default",
|
|
3825
|
+
data: "Errors reported by Semgrep while scanning. A file that failed to parse was not fully analyzed."
|
|
3826
|
+
}], DEFAULT_IMPACT, results, { tags });
|
|
3827
|
+
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3828
|
+
return requirement;
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Records the scan's denominator. Semgrep reports violations only, so a
|
|
3832
|
+
* converted profile is failures-only by construction and its compliance ratio
|
|
3833
|
+
* is not a pass rate; this record carries the scanned count and the caveat.
|
|
3834
|
+
* Impact 0 makes the effective-status layer derive notApplicable — the only
|
|
3835
|
+
* status the compliance rollup excludes — and the raw status matches so
|
|
3836
|
+
* raw-status consumers (and CKL export, where Passed would render NotAFinding)
|
|
3837
|
+
* agree with the effective view. Mirrors kics-scan-coverage.
|
|
3838
|
+
*/
|
|
3839
|
+
function scannedCount(report) {
|
|
3840
|
+
return Array.isArray(report.paths?.scanned) ? report.paths.scanned.length : 0;
|
|
3841
|
+
}
|
|
3842
|
+
function buildCoverageRequirement(report, ruleCount, startTime) {
|
|
3843
|
+
const filesScanned = scannedCount(report);
|
|
3844
|
+
const errorCount = report.errors?.length ?? 0;
|
|
3845
|
+
const summary = `Semgrep scanned ${filesScanned} file(s); ${ruleCount} rule(s) produced findings and ${errorCount} scan error(s) were reported. Semgrep reports violations only and does not enumerate the rules that ran without finding anything, so no passing requirements can be derived from its output and the compliance ratio should not be read as a pass rate.`;
|
|
3846
|
+
return createRequirement(COVERAGE_ID, "Semgrep scan coverage", [{
|
|
3847
|
+
label: "default",
|
|
3848
|
+
data: summary
|
|
3849
|
+
}], 0, [{
|
|
3850
|
+
status: ResultStatus.NotApplicable,
|
|
3851
|
+
codeDesc: summary,
|
|
3852
|
+
startTime
|
|
3853
|
+
}], { tags: {
|
|
3854
|
+
filesScanned,
|
|
3855
|
+
rulesWithFindings: ruleCount,
|
|
3856
|
+
scanErrors: errorCount
|
|
3857
|
+
} });
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Converts native `semgrep scan --json` output to HDF Results. SARIF input is
|
|
3861
|
+
* detected and delegated to the SARIF converter.
|
|
3862
|
+
*
|
|
3863
|
+
* Native JSON is converted here because SARIF keeps the rule metadata only as
|
|
3864
|
+
* untyped prose tags on the rule object and drops impact, likelihood, the ASVS
|
|
3865
|
+
* control mapping, reference URLs and vulnerability_class outright.
|
|
3866
|
+
*
|
|
3867
|
+
* @param input - Semgrep JSON report string
|
|
3868
|
+
* @returns HDF JSON string
|
|
3869
|
+
*/
|
|
3870
|
+
async function convertSemgrepToHdf(input, converterVersion = "1.0.0") {
|
|
3871
|
+
validateInputSize(input, "semgrep");
|
|
3872
|
+
if (!input || input.trim().length === 0) throw new Error("semgrep: empty input");
|
|
3873
|
+
registerAllFingerprints();
|
|
3874
|
+
const detected = detectConverter(input);
|
|
3875
|
+
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
3876
|
+
const report = parseJSON(input);
|
|
3877
|
+
if (report === null || typeof report !== "object" || !Array.isArray(report.results) || !Array.isArray(report.errors)) throw new Error("semgrep: input does not look like a Semgrep report");
|
|
3878
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
3879
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3880
|
+
for (const result of report.results) {
|
|
3881
|
+
if (typeof result?.check_id !== "string" || result.check_id === "") continue;
|
|
3882
|
+
const existing = groups.get(result.check_id);
|
|
3883
|
+
if (existing) existing.push(result);
|
|
3884
|
+
else groups.set(result.check_id, [result]);
|
|
3885
|
+
}
|
|
3886
|
+
const requirements = [];
|
|
3887
|
+
for (const [checkId, results] of groups) requirements.push(buildRequirement$17(checkId, results, startTime));
|
|
3888
|
+
if (groups.size === 0) {
|
|
3889
|
+
const scanned = scannedCount(report);
|
|
3890
|
+
requirements.push(buildNoFindingsRequirement("semgrep-no-findings", `Semgrep scanned ${scanned} file(s) and reported no findings.`, startTime));
|
|
3891
|
+
}
|
|
3892
|
+
if (report.errors.length > 0) requirements.push(buildErrorsRequirement(report.errors, startTime));
|
|
3893
|
+
requirements.push(buildCoverageRequirement(report, groups.size, startTime));
|
|
3894
|
+
const baseline = createMinimalBaseline("Semgrep Scan", requirements, {
|
|
3895
|
+
title: "Semgrep static analysis scan",
|
|
3896
|
+
resultsChecksum: await inputChecksum(input)
|
|
3897
|
+
});
|
|
3898
|
+
return buildHdfResults({
|
|
3899
|
+
generatorName: "semgrep-to-hdf",
|
|
3900
|
+
converterVersion,
|
|
3901
|
+
toolName: "Semgrep",
|
|
3902
|
+
toolVersion: typeof report.version === "string" ? report.version : void 0,
|
|
3903
|
+
baselines: [baseline],
|
|
3904
|
+
timestamp: startTime
|
|
3905
|
+
});
|
|
3906
|
+
}
|
|
3907
|
+
//#endregion
|
|
3255
3908
|
//#region converters/snyk-to-hdf/typescript/converter.ts
|
|
3256
3909
|
/**
|
|
3257
3910
|
* Re-serialize the parsed vulnerability into indented JSON for requirement.code
|
|
@@ -3418,6 +4071,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
3418
4071
|
};
|
|
3419
4072
|
if (rep.identifiers.CVE && rep.identifiers.CVE.length > 0) tags["cve"] = rep.identifiers.CVE;
|
|
3420
4073
|
if (rep.identifiers.GHSA && rep.identifiers.GHSA.length > 0) tags["ghsaid"] = rep.identifiers.GHSA;
|
|
4074
|
+
markUnratedSeverity(tags, rep.severity);
|
|
3421
4075
|
const descriptions = [{
|
|
3422
4076
|
label: "default",
|
|
3423
4077
|
data: rep.description
|
|
@@ -3435,7 +4089,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
3435
4089
|
delete result.message;
|
|
3436
4090
|
return result;
|
|
3437
4091
|
});
|
|
3438
|
-
const req = createRequirement(vulnID, rep.title, descriptions, severityToImpact$1(rep.severity), results, { tags });
|
|
4092
|
+
const req = createRequirement(vulnID, rep.title, descriptions, severityToImpact$1(rep.severity ?? ""), results, { tags });
|
|
3439
4093
|
const controlType = deriveControlTypeFromTags(nist);
|
|
3440
4094
|
if (controlType !== void 0) req.controlType = controlType;
|
|
3441
4095
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
@@ -3450,7 +4104,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
3450
4104
|
const version = rep.version;
|
|
3451
4105
|
if (name && version) {
|
|
3452
4106
|
const ecosystem = ecosystemFromSnykPackageManager(packageManager);
|
|
3453
|
-
const pkg = buildAffectedPackage$
|
|
4107
|
+
const pkg = buildAffectedPackage$2({
|
|
3454
4108
|
name,
|
|
3455
4109
|
version,
|
|
3456
4110
|
ecosystem,
|
|
@@ -3531,17 +4185,12 @@ async function convertSnykToHdf(input, converterVersion = "1.0.0") {
|
|
|
3531
4185
|
}
|
|
3532
4186
|
//#endregion
|
|
3533
4187
|
//#region converters/grype-to-hdf/typescript/converter.ts
|
|
3534
|
-
const
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
["low", .3],
|
|
3539
|
-
["negligible", 0],
|
|
3540
|
-
["unknown", .5]
|
|
3541
|
-
]);
|
|
4188
|
+
const GRYPE_ALIASES = {
|
|
4189
|
+
critical: .9,
|
|
4190
|
+
negligible: 0
|
|
4191
|
+
};
|
|
3542
4192
|
function getImpact$9(severity) {
|
|
3543
|
-
|
|
3544
|
-
return IMPACT_MAPPING$8.get(severity.toLowerCase()) ?? .5;
|
|
4193
|
+
return severityToImpactWithAliases(severity, GRYPE_ALIASES, .5);
|
|
3545
4194
|
}
|
|
3546
4195
|
function isNegligibleOrUnknown(severity) {
|
|
3547
4196
|
if (!severity) return true;
|
|
@@ -3593,7 +4242,7 @@ function buildCodeDesc$9(match) {
|
|
|
3593
4242
|
}
|
|
3594
4243
|
return parts.join(" | ");
|
|
3595
4244
|
}
|
|
3596
|
-
function buildCvssEntries$
|
|
4245
|
+
function buildCvssEntries$7(vuln) {
|
|
3597
4246
|
if (!vuln.cvss || vuln.cvss.length === 0) return;
|
|
3598
4247
|
const entries = [];
|
|
3599
4248
|
for (const c of vuln.cvss) {
|
|
@@ -3636,10 +4285,10 @@ function buildAffectedPackages(match) {
|
|
|
3636
4285
|
if (fix && fix.state === "fixed" && fix.versions && fix.versions.length > 0 && fix.versions[0]) pkg.fixedInVersion = fix.versions[0];
|
|
3637
4286
|
return [pkg];
|
|
3638
4287
|
}
|
|
3639
|
-
const CWE_ID_PATTERN$
|
|
4288
|
+
const CWE_ID_PATTERN$2 = /^CWE-[1-9]\d*$/;
|
|
3640
4289
|
function extractCwe(raw) {
|
|
3641
4290
|
if (!raw || raw.length === 0) return void 0;
|
|
3642
|
-
const out = raw.filter((c) => CWE_ID_PATTERN$
|
|
4291
|
+
const out = raw.filter((c) => CWE_ID_PATTERN$2.test(c));
|
|
3643
4292
|
return out.length === 0 ? void 0 : out;
|
|
3644
4293
|
}
|
|
3645
4294
|
function buildEpss$3(entries) {
|
|
@@ -3671,7 +4320,6 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3671
4320
|
const refs = getReferences(vuln, match.relatedVulnerabilities);
|
|
3672
4321
|
let status;
|
|
3673
4322
|
if (isIgnored) status = ResultStatus.NotReviewed;
|
|
3674
|
-
else if (isNegligibleOrUnknown(severity)) status = ResultStatus.NotApplicable;
|
|
3675
4323
|
else status = ResultStatus.Failed;
|
|
3676
4324
|
const messageParts = [];
|
|
3677
4325
|
if (isIgnored) messageParts.push("This vulnerability was ignored by configured rules.");
|
|
@@ -3686,6 +4334,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3686
4334
|
startTime
|
|
3687
4335
|
};
|
|
3688
4336
|
const tags = buildNistCciTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS, nistToCci(DEFAULT_STATIC_ANALYSIS_NIST_TAGS));
|
|
4337
|
+
markUnratedSeverity(tags, severity);
|
|
3689
4338
|
const requirement = {
|
|
3690
4339
|
id: isIgnored ? `Grype-Ignored-Match/${cveId}` : `Grype/${cveId}`,
|
|
3691
4340
|
title: `Grype found a vulnerability to ${cveId} in ${targetName}`,
|
|
@@ -3712,7 +4361,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3712
4361
|
};
|
|
3713
4362
|
const controlType = deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
3714
4363
|
if (controlType !== void 0) requirement.controlType = controlType;
|
|
3715
|
-
const cvss = buildCvssEntries$
|
|
4364
|
+
const cvss = buildCvssEntries$7(vuln);
|
|
3716
4365
|
if (cvss) requirement.cvss = cvss;
|
|
3717
4366
|
requirement.affectedPackages = buildAffectedPackages(match);
|
|
3718
4367
|
const cwe = extractCwe(vuln.cwe);
|
|
@@ -3723,7 +4372,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3723
4372
|
if (kev) requirement.kev = kev;
|
|
3724
4373
|
return requirement;
|
|
3725
4374
|
}
|
|
3726
|
-
function buildComponent$
|
|
4375
|
+
function buildComponent$2(report, targetName) {
|
|
3727
4376
|
const t = report.source?.target;
|
|
3728
4377
|
const firstRepoDigest = t?.repoDigests?.find((d) => d);
|
|
3729
4378
|
const firstTag = t?.tags?.find((tag) => tag);
|
|
@@ -3741,10 +4390,8 @@ function buildComponent$1(report, targetName) {
|
|
|
3741
4390
|
if (image) component.image = image;
|
|
3742
4391
|
if (report.distro?.name) component.osName = report.distro.name;
|
|
3743
4392
|
if (report.distro?.version) component.osVersion = report.distro.version;
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
value: t.manifestDigest.replace(/^sha256:/, "")
|
|
3747
|
-
}];
|
|
4393
|
+
const integrity = digestToChecksums(t.manifestDigest);
|
|
4394
|
+
if (integrity) component.integrity = integrity;
|
|
3748
4395
|
if (t.architecture) component.labels = { architecture: t.architecture };
|
|
3749
4396
|
return component;
|
|
3750
4397
|
}
|
|
@@ -3776,7 +4423,7 @@ async function convertGrypeToHdf(input, converterVersion = "1.0.0") {
|
|
|
3776
4423
|
toolName: "Grype",
|
|
3777
4424
|
toolVersion: grypeData.descriptor?.version,
|
|
3778
4425
|
baselines: [baseline],
|
|
3779
|
-
components: [buildComponent$
|
|
4426
|
+
components: [buildComponent$2(grypeData, targetName)],
|
|
3780
4427
|
timestamp: scanTime ?? void 0
|
|
3781
4428
|
});
|
|
3782
4429
|
}
|
|
@@ -4024,7 +4671,7 @@ async function convertDefectDojoToHdf(input, converterVersion = "0.1.0") {
|
|
|
4024
4671
|
//#region converters/nessus-to-hdf/typescript/converter.ts
|
|
4025
4672
|
const CVE_SOURCE_RE = /^CVE-\d{4}-\d{4,}$/;
|
|
4026
4673
|
const CWE_PATTERN = /CWE[- ]?(\d+)/gi;
|
|
4027
|
-
const
|
|
4674
|
+
const NESSUS_ALIASES = {
|
|
4028
4675
|
"4": .9,
|
|
4029
4676
|
"3": .7,
|
|
4030
4677
|
"i": .7,
|
|
@@ -4038,7 +4685,7 @@ const IMPACT_MAPPING$7 = {
|
|
|
4038
4685
|
* Strip HTML tags from a string
|
|
4039
4686
|
*/
|
|
4040
4687
|
function parseHtml(html) {
|
|
4041
|
-
return html
|
|
4688
|
+
return replaceHtmlTags(html, "").trim();
|
|
4042
4689
|
}
|
|
4043
4690
|
const NAMED_ENTITIES$3 = {
|
|
4044
4691
|
lt: "<",
|
|
@@ -4168,8 +4815,8 @@ function convertReportItemToRequirement(item, host) {
|
|
|
4168
4815
|
const title = isCompliance ? item["compliance-check-name"] || item["pluginName"] : item["pluginName"];
|
|
4169
4816
|
const descriptions = buildDescriptions(item, isCompliance);
|
|
4170
4817
|
const impact = calculateImpact(item, isCompliance);
|
|
4171
|
-
const tags = buildTags$
|
|
4172
|
-
const refs = buildRefs$
|
|
4818
|
+
const tags = buildTags$3(item, isCompliance);
|
|
4819
|
+
const refs = buildRefs$9(item);
|
|
4173
4820
|
const results = [buildResult$1(item, host, isCompliance)];
|
|
4174
4821
|
const code = JSON.stringify(item, null, 2);
|
|
4175
4822
|
const nistTags = tags["nist"];
|
|
@@ -4188,7 +4835,7 @@ function convertReportItemToRequirement(item, host) {
|
|
|
4188
4835
|
if (controlType !== void 0) req.controlType = controlType;
|
|
4189
4836
|
if (verificationMethod !== void 0) req.verificationMethod = verificationMethod;
|
|
4190
4837
|
if (!isCompliance) {
|
|
4191
|
-
const cvssEntries = buildCvssEntries$
|
|
4838
|
+
const cvssEntries = buildCvssEntries$6(item);
|
|
4192
4839
|
if (cvssEntries.length > 0) req.cvss = cvssEntries;
|
|
4193
4840
|
const cweIDs = buildCweIDs(item);
|
|
4194
4841
|
if (cweIDs.length > 0) req.cwe = cweIDs;
|
|
@@ -4202,7 +4849,7 @@ function convertReportItemToRequirement(item, host) {
|
|
|
4202
4849
|
* the schema models cvss as a multi-entry array (Nessus emits one entry per
|
|
4203
4850
|
* item; multi-vendor convergence may yield more).
|
|
4204
4851
|
*/
|
|
4205
|
-
function buildCvssEntries$
|
|
4852
|
+
function buildCvssEntries$6(item) {
|
|
4206
4853
|
const source = (item.cvss_score_source || "").trim();
|
|
4207
4854
|
if (!source || !CVE_SOURCE_RE.test(source)) return [];
|
|
4208
4855
|
const hasV3 = !!(item.cvss3_vector || item.cvss3_base_score);
|
|
@@ -4342,12 +4989,12 @@ function buildDescriptions(item, isCompliance) {
|
|
|
4342
4989
|
}
|
|
4343
4990
|
function calculateImpact(item, isCompliance) {
|
|
4344
4991
|
if (isCompliance && item["compliance-reference"]) {
|
|
4345
|
-
const
|
|
4346
|
-
return
|
|
4992
|
+
const cat = parseComplianceRef(item["compliance-reference"], "CAT")[0];
|
|
4993
|
+
return cat ? severityToImpactWithAliases(cat, NESSUS_ALIASES, .5) : .5;
|
|
4347
4994
|
}
|
|
4348
|
-
return
|
|
4995
|
+
return severityToImpactWithAliases(item["severity"], NESSUS_ALIASES, 0);
|
|
4349
4996
|
}
|
|
4350
|
-
function buildTags$
|
|
4997
|
+
function buildTags$3(item, isCompliance) {
|
|
4351
4998
|
const tags = { rid: isCompliance ? parseComplianceRef(item["compliance-reference"], "Rule-ID").join(",") : item["pluginID"] };
|
|
4352
4999
|
if (isCompliance && item["compliance-reference"]) {
|
|
4353
5000
|
const cciTags = parseComplianceRef(item["compliance-reference"], "CCI");
|
|
@@ -4370,7 +5017,7 @@ function buildTags$2(item, isCompliance) {
|
|
|
4370
5017
|
if (item.cvss_base_score) tags.cvss_base_score = item.cvss_base_score;
|
|
4371
5018
|
return tags;
|
|
4372
5019
|
}
|
|
4373
|
-
function buildRefs$
|
|
5020
|
+
function buildRefs$9(item) {
|
|
4374
5021
|
const refs = [];
|
|
4375
5022
|
if (item.see_also) for (const url of item.see_also.split(/\s+/).filter(Boolean)) refs.push({ url });
|
|
4376
5023
|
return refs.length > 0 ? refs : void 0;
|
|
@@ -4436,31 +5083,29 @@ function isIPAddress(s) {
|
|
|
4436
5083
|
//#endregion
|
|
4437
5084
|
//#region converters/sonarqube-to-hdf/typescript/converter.ts
|
|
4438
5085
|
/**
|
|
4439
|
-
* Deprecated legacy severity to impact
|
|
5086
|
+
* Deprecated legacy severity aliases to impact scores.
|
|
4440
5087
|
* Canonical reference: heimdall2 sonarqube-mapper.ts IMPACT_MAPPING.
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
5088
|
+
* Note: SonarQube "CRITICAL" maps to 0.7, not the standard 1.0; INFO resolves
|
|
5089
|
+
* to 0.0 via the shared standard map.
|
|
5090
|
+
*/
|
|
5091
|
+
const SONARQUBE_ALIASES = {
|
|
5092
|
+
blocker: 1,
|
|
5093
|
+
critical: .7,
|
|
5094
|
+
major: .5,
|
|
5095
|
+
minor: .3
|
|
4448
5096
|
};
|
|
4449
|
-
/**
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
INFO: 0
|
|
4456
|
-
};
|
|
4457
|
-
/** MQR severities ordered weakest to strongest. */
|
|
5097
|
+
/**
|
|
5098
|
+
* The one MQR severity the standard map lacks; HIGH/MEDIUM/LOW/INFO already
|
|
5099
|
+
* resolve to 0.7/0.5/0.3/0.0 via severityToImpactWithAliases.
|
|
5100
|
+
*/
|
|
5101
|
+
const MQR_ALIASES = { blocker: 1 };
|
|
5102
|
+
/** MQR severities ordered weakest to strongest (lowercase keys; Go parity). */
|
|
4458
5103
|
const MQR_SEVERITY_RANK = {
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
5104
|
+
info: 0,
|
|
5105
|
+
low: 1,
|
|
5106
|
+
medium: 2,
|
|
5107
|
+
high: 3,
|
|
5108
|
+
blocker: 4
|
|
4464
5109
|
};
|
|
4465
5110
|
/**
|
|
4466
5111
|
* Which severity axis drove a requirement's severity/impact. Emitted as the
|
|
@@ -4485,17 +5130,18 @@ const byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
|
4485
5130
|
function selectSeverity(issue) {
|
|
4486
5131
|
const impacts = issue.impacts ?? [];
|
|
4487
5132
|
if (impacts.length > 0) {
|
|
4488
|
-
const
|
|
5133
|
+
const rank = (s) => MQR_SEVERITY_RANK[s.toLowerCase()] ?? 0;
|
|
5134
|
+
const worst = impacts.reduce((a, b) => rank(b.severity) > rank(a.severity) ? b : a);
|
|
4489
5135
|
return {
|
|
4490
5136
|
severity: worst.severity,
|
|
4491
5137
|
source: SEVERITY_SOURCE_MQR,
|
|
4492
|
-
impact:
|
|
5138
|
+
impact: severityToImpactWithAliases(worst.severity, MQR_ALIASES, .5)
|
|
4493
5139
|
};
|
|
4494
5140
|
}
|
|
4495
5141
|
return {
|
|
4496
|
-
severity: issue.severity,
|
|
5142
|
+
severity: issue.severity ?? "",
|
|
4497
5143
|
source: SEVERITY_SOURCE_LEGACY,
|
|
4498
|
-
impact:
|
|
5144
|
+
impact: severityToImpactWithAliases(issue.severity, SONARQUBE_ALIASES, .5)
|
|
4499
5145
|
};
|
|
4500
5146
|
}
|
|
4501
5147
|
/**
|
|
@@ -4504,6 +5150,28 @@ function selectSeverity(issue) {
|
|
|
4504
5150
|
* SonarQube is fundamentally a static analysis tool. Matches heimdall2.
|
|
4505
5151
|
*/
|
|
4506
5152
|
const DEFAULT_NIST_TAGS = ["SA-11"];
|
|
5153
|
+
const OWASP_TAG_PATTERN = /owasp[-:]?a0*(\d{1,2})/gi;
|
|
5154
|
+
const OWASP_2017_PATTERN = /top 10 2017[\s\S]{0,30}?category\s*a0*(\d{1,2})/gi;
|
|
5155
|
+
/** Records a normalized OWASP-2017 category (A1..A10) when n is in range. */
|
|
5156
|
+
function addOwaspId(set, n) {
|
|
5157
|
+
if (n === void 0) return;
|
|
5158
|
+
const v = parseInt(n, 10);
|
|
5159
|
+
if (v >= 1 && v <= 10) set.add(`A${v}`);
|
|
5160
|
+
}
|
|
5161
|
+
/**
|
|
5162
|
+
* Combines CWE-derived and OWASP-derived NIST controls. The SA-11 default
|
|
5163
|
+
* applies only when neither source yields a mapping.
|
|
5164
|
+
*/
|
|
5165
|
+
function foldNistControls(cweIds, owaspIds) {
|
|
5166
|
+
const set = /* @__PURE__ */ new Set();
|
|
5167
|
+
for (const c of mapCWEToNIST(cweIds, [])) set.add(c);
|
|
5168
|
+
for (const id of owaspIds) {
|
|
5169
|
+
const ctrl = getOwaspNistControl(id);
|
|
5170
|
+
if (ctrl) for (const c of ctrl.split("|")) set.add(c);
|
|
5171
|
+
}
|
|
5172
|
+
if (set.size === 0) return [...DEFAULT_NIST_TAGS];
|
|
5173
|
+
return Array.from(set).sort();
|
|
5174
|
+
}
|
|
4507
5175
|
/**
|
|
4508
5176
|
* Convert SonarQube issues JSON to HDF format
|
|
4509
5177
|
*
|
|
@@ -4593,8 +5261,8 @@ function convertRuleToRequirement(ruleKey, issues, componentMap, ruleMap) {
|
|
|
4593
5261
|
const description = extractDescription(rule);
|
|
4594
5262
|
const firstIssue = issues[0];
|
|
4595
5263
|
const { severity, source: severitySource, impact } = selectSeverity(firstIssue);
|
|
4596
|
-
const { cweIds,
|
|
4597
|
-
const nistControls =
|
|
5264
|
+
const { cweIds, owaspIds, allTags } = extractTags(rule, issues);
|
|
5265
|
+
const nistControls = foldNistControls(cweIds, owaspIds);
|
|
4598
5266
|
const cciControls = nistToCci(nistControls);
|
|
4599
5267
|
const results = issues.map((issue) => createResultFromIssue(issue, componentMap));
|
|
4600
5268
|
const issueWithLocation = issues.find((i) => i.line !== void 0);
|
|
@@ -4604,7 +5272,7 @@ function convertRuleToRequirement(ruleKey, issues, componentMap, ruleMap) {
|
|
|
4604
5272
|
severitySource,
|
|
4605
5273
|
type: firstIssue.type.toLowerCase(),
|
|
4606
5274
|
cwe: cweIds,
|
|
4607
|
-
owasp:
|
|
5275
|
+
owasp: owaspIds,
|
|
4608
5276
|
nist: nistControls,
|
|
4609
5277
|
cci: cciControls,
|
|
4610
5278
|
...firstIssue.effort ? { effort: firstIssue.effort } : {},
|
|
@@ -4635,11 +5303,11 @@ function convertRuleToRequirement(ruleKey, issues, componentMap, ruleMap) {
|
|
|
4635
5303
|
function extractDescription(rule) {
|
|
4636
5304
|
if (!rule) return "";
|
|
4637
5305
|
if (rule.mdDesc) return rule.mdDesc;
|
|
4638
|
-
if (rule.htmlDesc) return rule.htmlDesc
|
|
5306
|
+
if (rule.htmlDesc) return stripHtml(rule.htmlDesc);
|
|
4639
5307
|
if (rule.descriptionSections && rule.descriptionSections.length > 0) {
|
|
4640
5308
|
const rootCause = rule.descriptionSections.find((s) => s.key === "root_cause");
|
|
4641
|
-
if (rootCause) return rootCause.content
|
|
4642
|
-
const parts = rule.descriptionSections.map((s) => s.content
|
|
5309
|
+
if (rootCause) return stripHtml(rootCause.content);
|
|
5310
|
+
const parts = rule.descriptionSections.map((s) => stripHtml(s.content)).filter((s) => s.length > 0);
|
|
4643
5311
|
if (parts.length > 0) return parts.join("\n\n");
|
|
4644
5312
|
}
|
|
4645
5313
|
return rule.name || "";
|
|
@@ -4653,7 +5321,7 @@ function extractTags(rule, issues) {
|
|
|
4653
5321
|
for (const tag of ruleTags) {
|
|
4654
5322
|
const lowerTag = tag.toLowerCase();
|
|
4655
5323
|
if (lowerTag.startsWith("cwe-") || lowerTag.includes("cwe")) for (const id of extractCWEIDs(tag)) cweSet.add(`CWE-${id}`);
|
|
4656
|
-
|
|
5324
|
+
for (const m of tag.matchAll(OWASP_TAG_PATTERN)) addOwaspId(owaspSet, m[1]);
|
|
4657
5325
|
const parts = tag.split(":");
|
|
4658
5326
|
if (parts.length === 2) {
|
|
4659
5327
|
const [category, value] = parts;
|
|
@@ -4663,20 +5331,24 @@ function extractTags(rule, issues) {
|
|
|
4663
5331
|
}
|
|
4664
5332
|
}
|
|
4665
5333
|
for (const issue of issues) if (issue.tags) for (const tag of issue.tags) {
|
|
4666
|
-
const
|
|
4667
|
-
|
|
4668
|
-
if (lowerTag.includes("owasp")) owaspSet.add(tag);
|
|
5334
|
+
if (tag.toLowerCase().startsWith("cwe-")) for (const id of extractCWEIDs(tag)) cweSet.add(`CWE-${id}`);
|
|
5335
|
+
for (const m of tag.matchAll(OWASP_TAG_PATTERN)) addOwaspId(owaspSet, m[1]);
|
|
4669
5336
|
}
|
|
4670
5337
|
if (rule?.htmlDesc || rule?.mdDesc) {
|
|
4671
5338
|
const desc = (rule.htmlDesc || "") + (rule.mdDesc || "");
|
|
4672
5339
|
for (const id of extractCWEIDs(desc)) cweSet.add(`CWE-${id}`);
|
|
4673
5340
|
}
|
|
4674
5341
|
if (rule?.descriptionSections) for (const section of rule.descriptionSections) for (const id of extractCWEIDs(section.content)) cweSet.add(`CWE-${id}`);
|
|
5342
|
+
if (rule) {
|
|
5343
|
+
const texts = [rule.htmlDesc || "", rule.mdDesc || ""];
|
|
5344
|
+
for (const section of rule.descriptionSections ?? []) texts.push(section.content);
|
|
5345
|
+
for (const text of texts) for (const m of text.matchAll(OWASP_2017_PATTERN)) addOwaspId(owaspSet, m[1]);
|
|
5346
|
+
}
|
|
4675
5347
|
const allTags = {};
|
|
4676
5348
|
for (const [category, values] of allTagsMap) allTags[category] = Array.from(values);
|
|
4677
5349
|
return {
|
|
4678
5350
|
cweIds: Array.from(cweSet),
|
|
4679
|
-
|
|
5351
|
+
owaspIds: Array.from(owaspSet).sort((a, b) => parseInt(a.slice(1), 10) - parseInt(b.slice(1), 10)),
|
|
4680
5352
|
allTags
|
|
4681
5353
|
};
|
|
4682
5354
|
}
|
|
@@ -4941,6 +5613,7 @@ function buildRequirement$14(checkId, group, scanTime) {
|
|
|
4941
5613
|
const checkTypes = [...new Set(group.map((c) => c.checkType).filter((t) => t !== ""))].sort();
|
|
4942
5614
|
if (checkTypes.length > 0) tags["check_type"] = checkTypes;
|
|
4943
5615
|
if (rep.bc_check_id) tags["bc_check_id"] = rep.bc_check_id;
|
|
5616
|
+
markUnratedSeverity(tags, rep.severity);
|
|
4944
5617
|
const descriptions = [{
|
|
4945
5618
|
label: "default",
|
|
4946
5619
|
data: rep.check_name
|
|
@@ -5033,14 +5706,6 @@ async function convertCheckovToHdf(input, converterVersion = "1.0.0") {
|
|
|
5033
5706
|
//#endregion
|
|
5034
5707
|
//#region converters/gosec-to-hdf/typescript/converter.ts
|
|
5035
5708
|
/**
|
|
5036
|
-
* Severity to HDF impact mapping for gosec.
|
|
5037
|
-
*/
|
|
5038
|
-
const IMPACT_MAPPING$6 = {
|
|
5039
|
-
HIGH: .7,
|
|
5040
|
-
MEDIUM: .5,
|
|
5041
|
-
LOW: .3
|
|
5042
|
-
};
|
|
5043
|
-
/**
|
|
5044
5709
|
* Returns the first-class CWE identifiers for an issue in "CWE-N" form,
|
|
5045
5710
|
* or an empty array when the issue carries no CWE.
|
|
5046
5711
|
*/
|
|
@@ -5104,10 +5769,11 @@ function issueToResult(issue, scanTime) {
|
|
|
5104
5769
|
*/
|
|
5105
5770
|
function buildRequirement$13(ruleId, issues, scanTime) {
|
|
5106
5771
|
const rep = issues[0];
|
|
5107
|
-
const impact =
|
|
5772
|
+
const impact = severityToImpact(rep.severity);
|
|
5108
5773
|
const nist = mapCWEToNIST([rep.cwe.id], DEFAULT_REMEDIATION_NIST_TAGS$1);
|
|
5109
5774
|
const tags = { nist };
|
|
5110
5775
|
if (rep.confidence) tags.confidence = rep.confidence;
|
|
5776
|
+
markUnratedSeverity(tags, rep.severity);
|
|
5111
5777
|
const results = issues.map((issue) => issueToResult(issue, scanTime));
|
|
5112
5778
|
const descriptions = [{
|
|
5113
5779
|
label: "default",
|
|
@@ -5357,7 +6023,7 @@ function buildSourceLocation$3(alert) {
|
|
|
5357
6023
|
return { ref: inst.uri };
|
|
5358
6024
|
}
|
|
5359
6025
|
const REF_URL_RE = /https?:\/\/[^\s<>"']+/g;
|
|
5360
|
-
function buildRefs$
|
|
6026
|
+
function buildRefs$8(reference) {
|
|
5361
6027
|
if (!reference) return;
|
|
5362
6028
|
const urls = reference.match(REF_URL_RE);
|
|
5363
6029
|
if (!urls || urls.length === 0) return;
|
|
@@ -5432,7 +6098,7 @@ function buildSiteRequirements(site, startTime) {
|
|
|
5432
6098
|
if (code !== void 0) req.code = code;
|
|
5433
6099
|
const sourceLocation = buildSourceLocation$3(alert);
|
|
5434
6100
|
if (sourceLocation !== void 0) req.sourceLocation = sourceLocation;
|
|
5435
|
-
const refs = buildRefs$
|
|
6101
|
+
const refs = buildRefs$8(alert.reference);
|
|
5436
6102
|
if (refs !== void 0) req.refs = refs;
|
|
5437
6103
|
requirements.push(req);
|
|
5438
6104
|
}
|
|
@@ -6052,13 +6718,7 @@ function stringSlice(v) {
|
|
|
6052
6718
|
* the canonical worst-wins ordering from @mitre/hdf-utilities.
|
|
6053
6719
|
*/
|
|
6054
6720
|
function worstOfResults(req) {
|
|
6055
|
-
|
|
6056
|
-
const statuses = [];
|
|
6057
|
-
for (const rRaw of results) {
|
|
6058
|
-
const r = asMap(rRaw);
|
|
6059
|
-
if (r) statuses.push(getStr(r, "status"));
|
|
6060
|
-
}
|
|
6061
|
-
return worstStatus(statuses);
|
|
6721
|
+
return worstStatus(resultStatuses(req));
|
|
6062
6722
|
}
|
|
6063
6723
|
/**
|
|
6064
6724
|
* Whether an HDF Result_Status is a failing verdict. Only 'failed' fails;
|
|
@@ -6069,20 +6729,46 @@ function worstOfResults(req) {
|
|
|
6069
6729
|
function isFailing(status) {
|
|
6070
6730
|
return status === "failed";
|
|
6071
6731
|
}
|
|
6072
|
-
/**
|
|
6732
|
+
/**
|
|
6733
|
+
* Resolve the status context for a requirement via the canonical
|
|
6734
|
+
* effective-status ladder — the stored effectiveStatus field is never read
|
|
6735
|
+
* (see status-determination.md; the field is an output cache).
|
|
6736
|
+
*/
|
|
6073
6737
|
function statusOf(req) {
|
|
6074
6738
|
const raw = worstOfResults(req);
|
|
6075
|
-
const
|
|
6076
|
-
const
|
|
6077
|
-
|
|
6739
|
+
const overrides = asArr(req.statusOverrides) ?? [];
|
|
6740
|
+
const overrideInputs = overrides.flatMap((oRaw) => {
|
|
6741
|
+
const o = asMap(oRaw);
|
|
6742
|
+
if (!o) return [];
|
|
6743
|
+
return [{
|
|
6744
|
+
status: getStr(o, "status") || void 0,
|
|
6745
|
+
appliedAt: getStr(o, "appliedAt") || void 0,
|
|
6746
|
+
expiresAt: getStr(o, "expiresAt") || void 0
|
|
6747
|
+
}];
|
|
6748
|
+
});
|
|
6749
|
+
const rollup = computeEffectiveStatus({
|
|
6750
|
+
impact: typeof req.impact === "number" ? req.impact : 1,
|
|
6751
|
+
resultStatuses: resultStatuses(req),
|
|
6752
|
+
overrides: overrideInputs
|
|
6753
|
+
});
|
|
6754
|
+
const governed = governingStatusOverride(overrideInputs) !== void 0;
|
|
6078
6755
|
return {
|
|
6079
6756
|
raw,
|
|
6080
|
-
effective,
|
|
6081
6757
|
rollup,
|
|
6082
|
-
overridden: overrides
|
|
6083
|
-
suppressed: isFailing(raw) && !isFailing(rollup)
|
|
6758
|
+
overridden: overrides.length > 0,
|
|
6759
|
+
suppressed: governed && isFailing(raw) && !isFailing(rollup)
|
|
6084
6760
|
};
|
|
6085
6761
|
}
|
|
6762
|
+
/** Lists results[].status losslessly for the canonical roll-up. */
|
|
6763
|
+
function resultStatuses(req) {
|
|
6764
|
+
const results = asArr(req.results) ?? [];
|
|
6765
|
+
const statuses = [];
|
|
6766
|
+
for (const rRaw of results) {
|
|
6767
|
+
const r = asMap(rRaw);
|
|
6768
|
+
if (r) statuses.push(getStr(r, "status"));
|
|
6769
|
+
}
|
|
6770
|
+
return statuses;
|
|
6771
|
+
}
|
|
6086
6772
|
function firstComponent(doc) {
|
|
6087
6773
|
const comps = asArr(doc.components);
|
|
6088
6774
|
if (!comps || comps.length === 0) return void 0;
|
|
@@ -6238,10 +6924,7 @@ function stringifyLine(v) {
|
|
|
6238
6924
|
* the missing-baselines error and drives validateInputSize.
|
|
6239
6925
|
*/
|
|
6240
6926
|
function runExport(input, converterName, build) {
|
|
6241
|
-
|
|
6242
|
-
const doc = parseHdf(input);
|
|
6243
|
-
const baselines = asArr(doc.baselines);
|
|
6244
|
-
if (!baselines) throw new Error(`${converterName}: invalid HDF structure: missing baselines field`);
|
|
6927
|
+
const { doc, items: baselines } = requireHdfResults(input, converterName);
|
|
6245
6928
|
const docTimestamp = getStr(doc, "timestamp");
|
|
6246
6929
|
const tool = asMap(doc.tool);
|
|
6247
6930
|
const generator = asMap(doc.generator);
|
|
@@ -6299,6 +6982,19 @@ const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
|
6299
6982
|
"CVSSv4"
|
|
6300
6983
|
]);
|
|
6301
6984
|
/**
|
|
6985
|
+
* Reports whether no rating carries rating information: no CVSS score and no
|
|
6986
|
+
* rated severity token. Covers the empty-ratings default and the fabricated-
|
|
6987
|
+
* "medium" (empty severity, no score) paths, both of which land on impact 0.5
|
|
6988
|
+
* without the source having asserted a severity.
|
|
6989
|
+
*/
|
|
6990
|
+
function vulnIsUnrated(ratings) {
|
|
6991
|
+
for (const rating of ratings) {
|
|
6992
|
+
if (rating.method && CVSS_METHODS.has(rating.method) && rating.score !== void 0 && rating.score !== null) return false;
|
|
6993
|
+
if (!isUnratedSeverity(rating.severity)) return false;
|
|
6994
|
+
}
|
|
6995
|
+
return true;
|
|
6996
|
+
}
|
|
6997
|
+
/**
|
|
6302
6998
|
* Computes the maximum impact across all ratings for a vulnerability.
|
|
6303
6999
|
* Prefers CVSS score/10 when available, falls back to severityToImpact().
|
|
6304
7000
|
*/
|
|
@@ -6328,7 +7024,7 @@ function cvssVersionFromMethod(method, vector) {
|
|
|
6328
7024
|
default: return cvssVersionFromVector(vector);
|
|
6329
7025
|
}
|
|
6330
7026
|
}
|
|
6331
|
-
function buildCvssEntries$
|
|
7027
|
+
function buildCvssEntries$5(ratings) {
|
|
6332
7028
|
const entries = [];
|
|
6333
7029
|
for (const r of ratings) {
|
|
6334
7030
|
const hasCvssMethod = r.method !== void 0 && CVSS_METHODS.has(r.method);
|
|
@@ -6349,7 +7045,7 @@ function buildCvssEntries$4(ratings) {
|
|
|
6349
7045
|
* de-duplicated across all three in first-seen order. Returns undefined when the
|
|
6350
7046
|
* vulnerability carries no links.
|
|
6351
7047
|
*/
|
|
6352
|
-
function buildRefs$
|
|
7048
|
+
function buildRefs$7(vuln) {
|
|
6353
7049
|
const seen = /* @__PURE__ */ new Set();
|
|
6354
7050
|
const refs = [];
|
|
6355
7051
|
const add = (url) => {
|
|
@@ -6633,8 +7329,9 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
|
6633
7329
|
nist,
|
|
6634
7330
|
cci: nistToCci(nist)
|
|
6635
7331
|
};
|
|
7332
|
+
if (vulnIsUnrated(ratings)) markUnratedSeverity(tags, "");
|
|
6636
7333
|
const cweIds = cwes.map((c) => `CWE-${c}`);
|
|
6637
|
-
const cvssEntries = buildCvssEntries$
|
|
7334
|
+
const cvssEntries = buildCvssEntries$5(ratings);
|
|
6638
7335
|
const descriptions = [];
|
|
6639
7336
|
const defaultParts = [];
|
|
6640
7337
|
if (vuln.description) defaultParts.push(`Description: ${vuln.description}`);
|
|
@@ -6667,7 +7364,7 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
|
6667
7364
|
req.code = buildVulnCode$1(vuln);
|
|
6668
7365
|
if (cvssEntries.length > 0) req.cvss = cvssEntries;
|
|
6669
7366
|
if (cweIds.length > 0) req.cwe = cweIds;
|
|
6670
|
-
const refs = buildRefs$
|
|
7367
|
+
const refs = buildRefs$7(vuln);
|
|
6671
7368
|
if (refs !== void 0) req.refs = refs;
|
|
6672
7369
|
const controlType = deriveControlTypeFromTags(nist);
|
|
6673
7370
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -6750,7 +7447,7 @@ function createRow(baseline, requirement, target) {
|
|
|
6750
7447
|
const message = firstResult?.message || "";
|
|
6751
7448
|
const nistControls = extractArrayFromTags(requirement.tags, "nist");
|
|
6752
7449
|
const cciControls = extractArrayFromTags(requirement.tags, "cci");
|
|
6753
|
-
const effectiveStatus =
|
|
7450
|
+
const effectiveStatus = requirementEffectiveStatus(requirement);
|
|
6754
7451
|
const effectiveImpact = (requirement.effectiveImpact ?? requirement.impact).toFixed(1);
|
|
6755
7452
|
const disposition = requirement.disposition ? String(requirement.disposition) : "";
|
|
6756
7453
|
return {
|
|
@@ -7904,7 +8601,7 @@ async function convertSplunkToHdf(input, converterVersion = "1.0.0") {
|
|
|
7904
8601
|
}) : [];
|
|
7905
8602
|
const options = { tags: control.tags ?? {} };
|
|
7906
8603
|
if (control.source_location) options.sourceLocation = control.source_location;
|
|
7907
|
-
const req = createRequirement(control.id, control.title, descriptions, control.impact, results, options);
|
|
8604
|
+
const req = createRequirement(control.id, control.title, descriptions, control.impact ?? 0, results, options);
|
|
7908
8605
|
if (!req.title) delete req.title;
|
|
7909
8606
|
if (control.code) req.code = control.code;
|
|
7910
8607
|
const nistTagsRaw = control.tags?.["nist"];
|
|
@@ -8050,7 +8747,7 @@ function convertHdfToXml(input) {
|
|
|
8050
8747
|
}
|
|
8051
8748
|
//#endregion
|
|
8052
8749
|
//#region converters/gitlab-to-hdf/typescript/converter.ts
|
|
8053
|
-
function buildRefs$
|
|
8750
|
+
function buildRefs$6(vuln) {
|
|
8054
8751
|
const refs = [];
|
|
8055
8752
|
const seen = /* @__PURE__ */ new Set();
|
|
8056
8753
|
const appendUrl = (url) => {
|
|
@@ -8190,6 +8887,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
8190
8887
|
const extras = {};
|
|
8191
8888
|
for (const [key, values] of Object.entries(idTags)) extras[key] = values;
|
|
8192
8889
|
const tags = buildNistCciTags(nistTags, cciTags, Object.keys(extras).length > 0 ? extras : void 0);
|
|
8890
|
+
markUnratedSeverity(tags, vuln.severity);
|
|
8193
8891
|
const descriptions = [];
|
|
8194
8892
|
if (vuln.description) descriptions.push({
|
|
8195
8893
|
label: "default",
|
|
@@ -8224,7 +8922,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
8224
8922
|
descriptions,
|
|
8225
8923
|
verificationMethod: VerificationMethodEnum.Automated
|
|
8226
8924
|
};
|
|
8227
|
-
const refs = buildRefs$
|
|
8925
|
+
const refs = buildRefs$6(vuln);
|
|
8228
8926
|
if (refs) req.refs = refs;
|
|
8229
8927
|
const controlType = deriveControlTypeFromTags(nistTags);
|
|
8230
8928
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -8266,6 +8964,307 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
8266
8964
|
return serializeHdf(hdf);
|
|
8267
8965
|
}
|
|
8268
8966
|
//#endregion
|
|
8967
|
+
//#region converters/trivy-to-hdf/typescript/converter.ts
|
|
8968
|
+
const BASELINE_NAME = "Trivy Scan";
|
|
8969
|
+
const STATIC_CCI = nistToCci(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
8970
|
+
const CWE_ID_PATTERN$1 = /^CWE-[1-9]\d*$/;
|
|
8971
|
+
const SEVERITY_ALIASES = { critical: .9 };
|
|
8972
|
+
/**
|
|
8973
|
+
* Route Trivy output to HDF Results: native Trivy JSON (SchemaVersion 2) is
|
|
8974
|
+
* parsed here; SARIF / CycloneDX / ASFF / GitLab are delegated to their
|
|
8975
|
+
* converters. TypeScript peer of shared/go/../trivy-to-hdf converter.go.
|
|
8976
|
+
*/
|
|
8977
|
+
async function convertTrivyToHdf(input, converterVersion = "1.0.0") {
|
|
8978
|
+
validateInputSize(input, "trivy");
|
|
8979
|
+
if (!input?.trim()) throw new Error("trivy: empty input");
|
|
8980
|
+
const probe = parseJSON(input);
|
|
8981
|
+
if (typeof probe !== "object" || probe === null || Array.isArray(probe)) throw new Error("trivy: input is not a JSON object");
|
|
8982
|
+
if (isNativeTrivy(probe)) return convertNative(input, converterVersion);
|
|
8983
|
+
if (probe.bomFormat === "CycloneDX") return convertCyclonedxToHdf(input, converterVersion);
|
|
8984
|
+
if ("runs" in probe && "version" in probe) return convertSarifToHdf(input, converterVersion);
|
|
8985
|
+
if ("Findings" in probe || "ProductArn" in probe) return convertAsffToHdf(input, converterVersion);
|
|
8986
|
+
if ("vulnerabilities" in probe) return convertGitlabToHdf(input, converterVersion);
|
|
8987
|
+
throw new Error("trivy: not a recognized Trivy output format (native JSON, SARIF, CycloneDX, ASFF, or GitLab)");
|
|
8988
|
+
}
|
|
8989
|
+
function isNativeTrivy(m) {
|
|
8990
|
+
return "SchemaVersion" in m && "ArtifactName" in m && "ArtifactType" in m;
|
|
8991
|
+
}
|
|
8992
|
+
async function convertNative(input, converterVersion) {
|
|
8993
|
+
const report = parseJSON(input);
|
|
8994
|
+
const resultsChecksum = await inputChecksum(input);
|
|
8995
|
+
const scanTime = report.CreatedAt ? parseTimestamp(report.CreatedAt) : null;
|
|
8996
|
+
const startTime = scanTime ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
8997
|
+
const requirements = [];
|
|
8998
|
+
for (const res of report.Results ?? []) {
|
|
8999
|
+
for (const v of res.Vulnerabilities ?? []) requirements.push(convertVuln(v, res, startTime));
|
|
9000
|
+
for (const m of res.Misconfigurations ?? []) requirements.push(convertMisconf(m, res, startTime));
|
|
9001
|
+
for (const s of res.Secrets ?? []) requirements.push(convertSecret(s, res, startTime));
|
|
9002
|
+
for (const l of res.Licenses ?? []) requirements.push(convertLicense(l, res, startTime));
|
|
9003
|
+
}
|
|
9004
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("trivy-no-findings", `Trivy scanned ${report.ArtifactName ?? "the target"} and reported zero findings.`, startTime));
|
|
9005
|
+
const baseline = createMinimalBaseline(BASELINE_NAME, requirements, { resultsChecksum });
|
|
9006
|
+
if (report.ArtifactName) baseline.title = report.ArtifactName;
|
|
9007
|
+
const component = buildComponent$1(report);
|
|
9008
|
+
return buildHdfResults({
|
|
9009
|
+
generatorName: "trivy-to-hdf",
|
|
9010
|
+
converterVersion,
|
|
9011
|
+
toolName: "Trivy",
|
|
9012
|
+
toolVersion: report.Trivy?.Version,
|
|
9013
|
+
baselines: [baseline],
|
|
9014
|
+
components: component ? [component] : void 0,
|
|
9015
|
+
timestamp: scanTime ?? void 0
|
|
9016
|
+
});
|
|
9017
|
+
}
|
|
9018
|
+
function convertVuln(v, res, startTime) {
|
|
9019
|
+
const descriptions = [{
|
|
9020
|
+
label: "default",
|
|
9021
|
+
data: firstNonEmpty(v.Description ?? "", v.Title ?? "", v.VulnerabilityID ?? "")
|
|
9022
|
+
}];
|
|
9023
|
+
if (v.FixedVersion) descriptions.push({
|
|
9024
|
+
label: "fix",
|
|
9025
|
+
data: `Fixed in version ${v.FixedVersion}.`
|
|
9026
|
+
});
|
|
9027
|
+
const extras = { class: res.Class };
|
|
9028
|
+
putIf(extras, "trivy_type", res.Type);
|
|
9029
|
+
putIf(extras, "severity_source", v.SeveritySource);
|
|
9030
|
+
putIf(extras, "data_source", v.DataSource?.Name);
|
|
9031
|
+
putIf(extras, "published_date", v.PublishedDate);
|
|
9032
|
+
if (v.VendorSeverity && Object.keys(v.VendorSeverity).length > 0) extras.vendor_severity = v.VendorSeverity;
|
|
9033
|
+
const tags = buildTags$2(extras);
|
|
9034
|
+
markUnratedSeverity(tags, v.Severity);
|
|
9035
|
+
const req = {
|
|
9036
|
+
id: `Trivy/${v.VulnerabilityID ?? ""}`,
|
|
9037
|
+
title: `Trivy found ${v.VulnerabilityID ?? ""} in ${pkgLabel(v.PkgName, v.InstalledVersion)}`,
|
|
9038
|
+
descriptions,
|
|
9039
|
+
impact: severityToImpactWithAliases(v.Severity, SEVERITY_ALIASES, .5),
|
|
9040
|
+
tags,
|
|
9041
|
+
cwe: filterCwes(v.CweIDs),
|
|
9042
|
+
cvss: buildCvssEntries$4(v.CVSS),
|
|
9043
|
+
refs: buildRefs$5([v.PrimaryURL, ...v.References ?? []]),
|
|
9044
|
+
code: JSON.stringify(v, null, 2),
|
|
9045
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9046
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9047
|
+
results: [{
|
|
9048
|
+
status: ResultStatus.Failed,
|
|
9049
|
+
codeDesc: buildVulnCodeDesc(v, res),
|
|
9050
|
+
startTime,
|
|
9051
|
+
message: `Severity: ${firstNonEmpty(v.Severity ?? "", "UNKNOWN")}`
|
|
9052
|
+
}]
|
|
9053
|
+
};
|
|
9054
|
+
const ap = buildAffectedPackage$1(v);
|
|
9055
|
+
if (ap.purl) req.affectedPackages = [ap];
|
|
9056
|
+
if (v.PkgPath) req.sourceLocation = { ref: v.PkgPath };
|
|
9057
|
+
return req;
|
|
9058
|
+
}
|
|
9059
|
+
function convertMisconf(m, res, startTime) {
|
|
9060
|
+
const descriptions = [{
|
|
9061
|
+
label: "default",
|
|
9062
|
+
data: firstNonEmpty(m.Description ?? "", m.Message ?? "", m.Title ?? "")
|
|
9063
|
+
}];
|
|
9064
|
+
if (m.Resolution) descriptions.push({
|
|
9065
|
+
label: "fix",
|
|
9066
|
+
data: m.Resolution
|
|
9067
|
+
});
|
|
9068
|
+
const tags = buildTags$2({
|
|
9069
|
+
class: res.Class,
|
|
9070
|
+
...m.Type ? { misconfig_type: m.Type } : {}
|
|
9071
|
+
});
|
|
9072
|
+
markUnratedSeverity(tags, m.Severity);
|
|
9073
|
+
const req = {
|
|
9074
|
+
id: `Trivy/${firstNonEmpty(m.ID ?? "", m.AVDID ?? "")}`,
|
|
9075
|
+
title: m.Title,
|
|
9076
|
+
descriptions,
|
|
9077
|
+
impact: severityToImpactWithAliases(m.Severity, SEVERITY_ALIASES, .5),
|
|
9078
|
+
tags,
|
|
9079
|
+
refs: buildRefs$5([m.PrimaryURL, ...m.References ?? []]),
|
|
9080
|
+
code: JSON.stringify(m, null, 2),
|
|
9081
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9082
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9083
|
+
results: [{
|
|
9084
|
+
status: misconfStatus(m.Status),
|
|
9085
|
+
codeDesc: firstNonEmpty(m.Message ?? "", m.Title ?? ""),
|
|
9086
|
+
startTime
|
|
9087
|
+
}]
|
|
9088
|
+
};
|
|
9089
|
+
if (res.Target) {
|
|
9090
|
+
const sl = { ref: res.Target };
|
|
9091
|
+
if (m.CauseMetadata?.StartLine && m.CauseMetadata.StartLine > 0) sl.line = m.CauseMetadata.StartLine;
|
|
9092
|
+
req.sourceLocation = sl;
|
|
9093
|
+
}
|
|
9094
|
+
return req;
|
|
9095
|
+
}
|
|
9096
|
+
function convertSecret(s, res, startTime) {
|
|
9097
|
+
const tags = buildTags$2({
|
|
9098
|
+
class: res.Class,
|
|
9099
|
+
...s.Category ? { secret_category: s.Category } : {}
|
|
9100
|
+
});
|
|
9101
|
+
markUnratedSeverity(tags, s.Severity);
|
|
9102
|
+
const req = {
|
|
9103
|
+
id: `Trivy/secret/${s.RuleID ?? ""}@${res.Target ?? ""}:${s.StartLine ?? 0}`,
|
|
9104
|
+
title: s.Title,
|
|
9105
|
+
descriptions: [{
|
|
9106
|
+
label: "default",
|
|
9107
|
+
data: `${firstNonEmpty(s.Title ?? "", s.RuleID ?? "")} detected in ${res.Target ?? ""} (value redacted by Trivy).`
|
|
9108
|
+
}],
|
|
9109
|
+
impact: severityToImpactWithAliases(s.Severity, SEVERITY_ALIASES, .5),
|
|
9110
|
+
tags,
|
|
9111
|
+
code: JSON.stringify(s, null, 2),
|
|
9112
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9113
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9114
|
+
results: [{
|
|
9115
|
+
status: ResultStatus.Failed,
|
|
9116
|
+
codeDesc: s.Match ?? "",
|
|
9117
|
+
startTime
|
|
9118
|
+
}]
|
|
9119
|
+
};
|
|
9120
|
+
if (res.Target) {
|
|
9121
|
+
const sl = { ref: res.Target };
|
|
9122
|
+
if (s.StartLine && s.StartLine > 0) sl.line = s.StartLine;
|
|
9123
|
+
req.sourceLocation = sl;
|
|
9124
|
+
}
|
|
9125
|
+
return req;
|
|
9126
|
+
}
|
|
9127
|
+
function convertLicense(l, res, startTime) {
|
|
9128
|
+
const extras = { class: res.Class };
|
|
9129
|
+
putIf(extras, "license_category", l.Category);
|
|
9130
|
+
putIf(extras, "package", l.PkgName);
|
|
9131
|
+
if (l.Confidence && l.Confidence > 0) extras.confidence = l.Confidence;
|
|
9132
|
+
const tags = buildTags$2(extras);
|
|
9133
|
+
markUnratedSeverity(tags, l.Severity);
|
|
9134
|
+
const req = {
|
|
9135
|
+
id: `Trivy/license/${l.PkgName ?? ""}/${l.Name ?? ""}`,
|
|
9136
|
+
title: `${l.Name ?? ""} (${l.Category ?? ""})`,
|
|
9137
|
+
descriptions: [{
|
|
9138
|
+
label: "default",
|
|
9139
|
+
data: `Package ${l.PkgName ?? ""} uses the ${l.Name ?? ""} license (category: ${l.Category ?? ""}).`
|
|
9140
|
+
}],
|
|
9141
|
+
impact: severityToImpactWithAliases(l.Severity, SEVERITY_ALIASES, .5),
|
|
9142
|
+
tags,
|
|
9143
|
+
refs: buildRefs$5([l.Link]),
|
|
9144
|
+
code: JSON.stringify(l, null, 2),
|
|
9145
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9146
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9147
|
+
results: [{
|
|
9148
|
+
status: ResultStatus.Failed,
|
|
9149
|
+
codeDesc: `${l.PkgName ?? ""}: ${l.Name ?? ""} license`,
|
|
9150
|
+
startTime
|
|
9151
|
+
}]
|
|
9152
|
+
};
|
|
9153
|
+
if (l.FilePath) req.sourceLocation = { ref: l.FilePath };
|
|
9154
|
+
return req;
|
|
9155
|
+
}
|
|
9156
|
+
function buildComponent$1(report) {
|
|
9157
|
+
if (!report.ArtifactName) return void 0;
|
|
9158
|
+
if (report.ArtifactType !== "container_image") return {
|
|
9159
|
+
name: report.ArtifactName,
|
|
9160
|
+
type: TargetType.Artifact
|
|
9161
|
+
};
|
|
9162
|
+
const c = {
|
|
9163
|
+
name: report.ArtifactName,
|
|
9164
|
+
type: TargetType.ContainerImage
|
|
9165
|
+
};
|
|
9166
|
+
const md = report.Metadata;
|
|
9167
|
+
if (!md) return c;
|
|
9168
|
+
if (md.ImageID) c.imageId = md.ImageID;
|
|
9169
|
+
if (md.OS?.Family) c.osName = md.OS.Family;
|
|
9170
|
+
if (md.OS?.Name) c.osVersion = md.OS.Name;
|
|
9171
|
+
const repoDigest = md.RepoDigests?.[0];
|
|
9172
|
+
if (repoDigest) {
|
|
9173
|
+
c.image = repoDigest;
|
|
9174
|
+
const integrity = digestToChecksums(digestPart(repoDigest));
|
|
9175
|
+
if (integrity) c.integrity = integrity;
|
|
9176
|
+
}
|
|
9177
|
+
if (md.ImageConfig?.architecture) c.labels = { architecture: md.ImageConfig.architecture };
|
|
9178
|
+
return c;
|
|
9179
|
+
}
|
|
9180
|
+
function buildTags$2(extras) {
|
|
9181
|
+
return buildNistCciTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS, STATIC_CCI, extras);
|
|
9182
|
+
}
|
|
9183
|
+
function misconfStatus(status) {
|
|
9184
|
+
switch ((status ?? "").toUpperCase()) {
|
|
9185
|
+
case "PASS": return ResultStatus.Passed;
|
|
9186
|
+
case "EXCEPTION": return ResultStatus.NotApplicable;
|
|
9187
|
+
default: return ResultStatus.Failed;
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
function filterCwes(cwes) {
|
|
9191
|
+
if (!cwes) return void 0;
|
|
9192
|
+
const out = cwes.filter((c) => CWE_ID_PATTERN$1.test(c));
|
|
9193
|
+
return out.length > 0 ? out : void 0;
|
|
9194
|
+
}
|
|
9195
|
+
function buildCvssEntries$4(m) {
|
|
9196
|
+
if (!m || Object.keys(m).length === 0) return void 0;
|
|
9197
|
+
const entries = [];
|
|
9198
|
+
for (const src of Object.keys(m).sort()) {
|
|
9199
|
+
const c = m[src];
|
|
9200
|
+
if (!c) continue;
|
|
9201
|
+
if (c.V2Vector || c.V2Score !== void 0) entries.push(buildCvss$3({
|
|
9202
|
+
version: Version.The20,
|
|
9203
|
+
baseVector: c.V2Vector,
|
|
9204
|
+
baseScore: c.V2Score,
|
|
9205
|
+
source: src
|
|
9206
|
+
}));
|
|
9207
|
+
if (c.V3Vector || c.V3Score !== void 0) entries.push(buildCvss$3({
|
|
9208
|
+
version: cvssVersionFromVector(c.V3Vector, Version.The31),
|
|
9209
|
+
baseVector: c.V3Vector,
|
|
9210
|
+
baseScore: c.V3Score,
|
|
9211
|
+
source: src
|
|
9212
|
+
}));
|
|
9213
|
+
if (c.V40Vector || c.V40Score !== void 0) entries.push(buildCvss$3({
|
|
9214
|
+
version: Version.The40,
|
|
9215
|
+
baseVector: c.V40Vector,
|
|
9216
|
+
baseScore: c.V40Score,
|
|
9217
|
+
source: src
|
|
9218
|
+
}));
|
|
9219
|
+
}
|
|
9220
|
+
return entries.length > 0 ? entries : void 0;
|
|
9221
|
+
}
|
|
9222
|
+
function buildAffectedPackage$1(v) {
|
|
9223
|
+
const ap = {};
|
|
9224
|
+
if (v.PkgName) ap.name = v.PkgName;
|
|
9225
|
+
if (v.InstalledVersion) ap.version = v.InstalledVersion;
|
|
9226
|
+
if (v.FixedVersion) ap.fixedInVersion = v.FixedVersion;
|
|
9227
|
+
const purl = v.PkgIdentifier?.PURL;
|
|
9228
|
+
if (purl) {
|
|
9229
|
+
ap.purl = purl;
|
|
9230
|
+
const eco = ecosystemFromPurl(purl);
|
|
9231
|
+
if (eco) ap.ecosystem = eco;
|
|
9232
|
+
}
|
|
9233
|
+
return ap;
|
|
9234
|
+
}
|
|
9235
|
+
function ecosystemFromPurl(purl) {
|
|
9236
|
+
const eco = ecosystemFromPurlType(purl.replace(/^pkg:/, "").split(/[/@?]/)[0] ?? "");
|
|
9237
|
+
return eco === Ecosystem.Generic ? void 0 : eco;
|
|
9238
|
+
}
|
|
9239
|
+
function buildRefs$5(urls) {
|
|
9240
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9241
|
+
const refs = [];
|
|
9242
|
+
for (const u of urls) {
|
|
9243
|
+
if (!u || seen.has(u)) continue;
|
|
9244
|
+
seen.add(u);
|
|
9245
|
+
refs.push({ url: u });
|
|
9246
|
+
}
|
|
9247
|
+
return refs.length > 0 ? refs : void 0;
|
|
9248
|
+
}
|
|
9249
|
+
function buildVulnCodeDesc(v, res) {
|
|
9250
|
+
const parts = [`Package: ${v.PkgName ?? ""}@${v.InstalledVersion ?? ""}`];
|
|
9251
|
+
if (res.Type) parts.push(`Type: ${res.Type}`);
|
|
9252
|
+
if (v.PkgPath) parts.push(`Location: ${v.PkgPath}`);
|
|
9253
|
+
if (v.FixedVersion) parts.push(`Fixed: ${v.FixedVersion}`);
|
|
9254
|
+
return parts.join(" | ");
|
|
9255
|
+
}
|
|
9256
|
+
function pkgLabel(name, version) {
|
|
9257
|
+
if (!version) return name ?? "";
|
|
9258
|
+
return `${name ?? ""}@${version}`;
|
|
9259
|
+
}
|
|
9260
|
+
function digestPart(ref) {
|
|
9261
|
+
const at = ref.lastIndexOf("@");
|
|
9262
|
+
return at >= 0 ? ref.slice(at + 1) : ref;
|
|
9263
|
+
}
|
|
9264
|
+
function putIf(m, key, val) {
|
|
9265
|
+
if (val) m[key] = val;
|
|
9266
|
+
}
|
|
9267
|
+
//#endregion
|
|
8269
9268
|
//#region converters/trufflehog-to-hdf/typescript/converter.ts
|
|
8270
9269
|
/** Hardcoded NIST/CCI constants for credential exposure findings. */
|
|
8271
9270
|
const TRUFFLEHOG_NIST = ["IA-5 (7)"];
|
|
@@ -8586,14 +9585,9 @@ async function convertHipcheckToHdf(input, converterVersion = "1.0.0") {
|
|
|
8586
9585
|
}
|
|
8587
9586
|
//#endregion
|
|
8588
9587
|
//#region converters/burpsuite-to-hdf/typescript/converter.ts
|
|
8589
|
-
const
|
|
8590
|
-
"high": .7,
|
|
8591
|
-
"medium": .5,
|
|
8592
|
-
"low": .3,
|
|
8593
|
-
"information": .3
|
|
8594
|
-
};
|
|
9588
|
+
const BURPSUITE_ALIASES = { information: .3 };
|
|
8595
9589
|
function getImpact$7(severity) {
|
|
8596
|
-
return
|
|
9590
|
+
return severityToImpactWithAliases(severity, BURPSUITE_ALIASES, .3);
|
|
8597
9591
|
}
|
|
8598
9592
|
/**
|
|
8599
9593
|
* Extract CWE identifiers from HTML text.
|
|
@@ -8778,13 +9772,6 @@ function buildRequirement$11(issueType, issues, startTime) {
|
|
|
8778
9772
|
}
|
|
8779
9773
|
//#endregion
|
|
8780
9774
|
//#region converters/dbprotect-to-hdf/typescript/converter.ts
|
|
8781
|
-
/** Impact mapping from heimdall2 DBProtect mapper */
|
|
8782
|
-
const IMPACT_MAPPING$4 = {
|
|
8783
|
-
high: .7,
|
|
8784
|
-
medium: .5,
|
|
8785
|
-
low: .3,
|
|
8786
|
-
informational: 0
|
|
8787
|
-
};
|
|
8788
9775
|
const NAMED_ENTITIES$2 = {
|
|
8789
9776
|
amp: "&",
|
|
8790
9777
|
lt: "<",
|
|
@@ -8848,10 +9835,9 @@ function getStatus$2(status) {
|
|
|
8848
9835
|
function getBacktrace(resultStatus) {
|
|
8849
9836
|
return resultStatus === "Failed" ? ["DB Protect Failed Check"] : void 0;
|
|
8850
9837
|
}
|
|
8851
|
-
/** Maps DBProtect risk level to HDF impact
|
|
9838
|
+
/** Maps DBProtect risk level to HDF impact via the shared standard map (Go parity). */
|
|
8852
9839
|
function getImpact$6(riskDV) {
|
|
8853
|
-
|
|
8854
|
-
return impact !== void 0 ? impact : .5;
|
|
9840
|
+
return severityToImpactWithAliases(riskDV, {}, .5);
|
|
8855
9841
|
}
|
|
8856
9842
|
/** Creates a description string from a finding's task and check category */
|
|
8857
9843
|
function formatDesc$1(f) {
|
|
@@ -8967,6 +9953,7 @@ function buildRequirement$10(checkID, findings, hasStatus) {
|
|
|
8967
9953
|
const tags = buildNistCciTags(nist, nistToCci(nist));
|
|
8968
9954
|
const checkCategory = rep["Check Category"] ?? "";
|
|
8969
9955
|
if (checkCategory) tags.check_category = checkCategory;
|
|
9956
|
+
markUnratedSeverity(tags, rep["Risk DV"]);
|
|
8970
9957
|
const descriptions = [{
|
|
8971
9958
|
label: "default",
|
|
8972
9959
|
data: formatDesc$1(rep)
|
|
@@ -9037,21 +10024,12 @@ async function convertDbprotectToHdf(input, converterVersion = "1.0.0") {
|
|
|
9037
10024
|
}
|
|
9038
10025
|
//#endregion
|
|
9039
10026
|
//#region converters/twistlock-to-hdf/typescript/converter.ts
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
*/
|
|
10027
|
+
const TWISTLOCK_ALIASES = {
|
|
10028
|
+
important: .9,
|
|
10029
|
+
moderate: .5
|
|
10030
|
+
};
|
|
9045
10031
|
function twistlockSeverityToImpact(severity) {
|
|
9046
|
-
|
|
9047
|
-
case "critical":
|
|
9048
|
-
case "important": return .9;
|
|
9049
|
-
case "high": return .7;
|
|
9050
|
-
case "medium":
|
|
9051
|
-
case "moderate": return .5;
|
|
9052
|
-
case "low": return .3;
|
|
9053
|
-
default: return .5;
|
|
9054
|
-
}
|
|
10032
|
+
return severityToImpactWithAliases(severity, TWISTLOCK_ALIASES, .5);
|
|
9055
10033
|
}
|
|
9056
10034
|
/**
|
|
9057
10035
|
* Builds the baseline title from scan result data.
|
|
@@ -9247,6 +10225,7 @@ function buildRequirement$9(vuln, packageTypes, distro) {
|
|
|
9247
10225
|
const extras = { cveid: [vuln.id] };
|
|
9248
10226
|
if (typeof vuln.cvss === "number" && vuln.cvss > 0) extras["cvss_base_score"] = vuln.cvss;
|
|
9249
10227
|
const tags = buildNistCciTags(nist, cciTags, extras);
|
|
10228
|
+
markUnratedSeverity(tags, vuln.severity);
|
|
9250
10229
|
const descriptions = [{
|
|
9251
10230
|
label: "default",
|
|
9252
10231
|
data: vuln.description
|
|
@@ -9508,7 +10487,7 @@ function buildAffectedPackageFromComponent(c) {
|
|
|
9508
10487
|
let ecosystem;
|
|
9509
10488
|
if (c.purl) ecosystem = ecosystemFromPurlType(parsePurl(c.purl)?.type);
|
|
9510
10489
|
else if (c.name && c.version) ecosystem = ecosystemFromPurlType(void 0);
|
|
9511
|
-
return buildAffectedPackage$
|
|
10490
|
+
return buildAffectedPackage$2({
|
|
9512
10491
|
name: c.name,
|
|
9513
10492
|
version: c.version,
|
|
9514
10493
|
ecosystem,
|
|
@@ -9726,6 +10705,7 @@ function buildRequirement$7(entryID, entries, scanTime) {
|
|
|
9726
10705
|
cci: nistToCci(nist)
|
|
9727
10706
|
};
|
|
9728
10707
|
if (cveIDs.length > 0) tags["cve"] = cveIDs;
|
|
10708
|
+
markUnratedSeverity(tags, rep.severity);
|
|
9729
10709
|
const descriptions = [{
|
|
9730
10710
|
label: "default",
|
|
9731
10711
|
data: formatDescription(rep)
|
|
@@ -9779,7 +10759,7 @@ function buildAffectedPackageFromEntry(entry) {
|
|
|
9779
10759
|
ecosystem = ecosystemFromXraySource(parsed.scheme);
|
|
9780
10760
|
}
|
|
9781
10761
|
const fixed = entry.component_versions?.fixed_versions?.[0];
|
|
9782
|
-
return buildAffectedPackage$
|
|
10762
|
+
return buildAffectedPackage$2({
|
|
9783
10763
|
name,
|
|
9784
10764
|
version,
|
|
9785
10765
|
ecosystem: ecosystem ?? (name ? Ecosystem.Generic : void 0),
|
|
@@ -10011,7 +10991,7 @@ function buildRequirement$6(vuln, scanTime, ml) {
|
|
|
10011
10991
|
const refs = buildRefs$3(vuln);
|
|
10012
10992
|
if (refs) req.refs = refs;
|
|
10013
10993
|
const cpe23 = (vuln.cpes ?? []).find((c) => /^cpe:2\.3:[aho]:/.test(c));
|
|
10014
|
-
const pkg = buildAffectedPackage$
|
|
10994
|
+
const pkg = buildAffectedPackage$2({
|
|
10015
10995
|
name: vuln.package_name,
|
|
10016
10996
|
version: vuln.package_version,
|
|
10017
10997
|
ecosystem: vuln.package_name && vuln.package_version ? Ecosystem.Generic : void 0,
|
|
@@ -10033,12 +11013,6 @@ function imageTitle(report) {
|
|
|
10033
11013
|
function targetNameFromReport(report) {
|
|
10034
11014
|
return `${report.registry}/${report.repository}:${report.tag}`;
|
|
10035
11015
|
}
|
|
10036
|
-
const DIGEST_ALGORITHMS = {
|
|
10037
|
-
sha256: HashAlgorithm.Sha256,
|
|
10038
|
-
sha384: HashAlgorithm.Sha384,
|
|
10039
|
-
sha512: HashAlgorithm.Sha512,
|
|
10040
|
-
blake3: HashAlgorithm.Blake3
|
|
10041
|
-
};
|
|
10042
11016
|
/**
|
|
10043
11017
|
* Splits NeuVector's base_os "name:version" form (e.g. "alpine:3.12.1") into OS
|
|
10044
11018
|
* name and version. A value with no ":" is the name with no version; an empty
|
|
@@ -10060,28 +11034,6 @@ function splitBaseOS(baseOS) {
|
|
|
10060
11034
|
};
|
|
10061
11035
|
}
|
|
10062
11036
|
/**
|
|
10063
|
-
* Turns the report digest ("sha256:<hex>") into the component's Integrity
|
|
10064
|
-
* checksum, folding the algorithm prefix into Checksum.algorithm. Returns
|
|
10065
|
-
* undefined when the report carries no digest.
|
|
10066
|
-
*/
|
|
10067
|
-
function digestIntegrity(digest) {
|
|
10068
|
-
if (!digest) return;
|
|
10069
|
-
let algorithm = HashAlgorithm.Sha256;
|
|
10070
|
-
let value = digest;
|
|
10071
|
-
const i = digest.indexOf(":");
|
|
10072
|
-
if (i >= 0) {
|
|
10073
|
-
const mapped = DIGEST_ALGORITHMS[digest.slice(0, i)];
|
|
10074
|
-
if (mapped) {
|
|
10075
|
-
algorithm = mapped;
|
|
10076
|
-
value = digest.slice(i + 1);
|
|
10077
|
-
}
|
|
10078
|
-
}
|
|
10079
|
-
return [{
|
|
10080
|
-
algorithm,
|
|
10081
|
-
value
|
|
10082
|
-
}];
|
|
10083
|
-
}
|
|
10084
|
-
/**
|
|
10085
11037
|
* Assembles the scan-wide containerImage component from the report's image
|
|
10086
11038
|
* identity. base_os → osName/osVersion, digest → integrity, plus
|
|
10087
11039
|
* imageId/registry/repository/tag when the report carries them.
|
|
@@ -10104,7 +11056,7 @@ function buildComponent(report) {
|
|
|
10104
11056
|
if (report.registry) component.registry = report.registry;
|
|
10105
11057
|
if (report.repository) component.repository = report.repository;
|
|
10106
11058
|
if (report.tag) component.tag = report.tag;
|
|
10107
|
-
const integrity =
|
|
11059
|
+
const integrity = digestToChecksums(report.digest);
|
|
10108
11060
|
if (integrity) component.integrity = integrity;
|
|
10109
11061
|
return component;
|
|
10110
11062
|
}
|
|
@@ -10323,8 +11275,9 @@ function buildRequirement$5(desc, vulns, snippetMap, startTimeStr) {
|
|
|
10323
11275
|
label: "tips",
|
|
10324
11276
|
data: tipsData
|
|
10325
11277
|
});
|
|
10326
|
-
|
|
10327
|
-
|
|
11278
|
+
const instanceSeverity = parseFloat(vulns[0]?.InstanceInfo?.InstanceSeverity ?? "0");
|
|
11279
|
+
const impact = instanceSeverity > 0 ? roundImpact(instanceSeverity / 5) : 0;
|
|
11280
|
+
if (!(instanceSeverity > 0)) markUnratedSeverity(tags, "");
|
|
10328
11281
|
const { items: limitedVulns, truncated } = limitArray(vulns);
|
|
10329
11282
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
10330
11283
|
if (truncated) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${vulns.length})`);
|
|
@@ -10522,6 +11475,7 @@ function buildRequirement$4(rec, scanTime) {
|
|
|
10522
11475
|
const extras = {};
|
|
10523
11476
|
if (rec["CVE ID"]) extras["cve"] = [rec["CVE ID"]];
|
|
10524
11477
|
const tags = buildNistCciTags(nist, cciTags, Object.keys(extras).length > 0 ? extras : void 0);
|
|
11478
|
+
markUnratedSeverity(tags, rec.Severity);
|
|
10525
11479
|
const descriptions = [{
|
|
10526
11480
|
label: "default",
|
|
10527
11481
|
data: rec.Description
|
|
@@ -10610,7 +11564,7 @@ function buildAffectedPackageFromRecord(rec) {
|
|
|
10610
11564
|
const version = rec["Package Version"];
|
|
10611
11565
|
if (!name || !version) return void 0;
|
|
10612
11566
|
const fixMatch = rec["Fix Status"] ? FIX_VERSION_PATTERN.exec(rec["Fix Status"]) : null;
|
|
10613
|
-
return buildAffectedPackage$
|
|
11567
|
+
return buildAffectedPackage$2({
|
|
10614
11568
|
name,
|
|
10615
11569
|
version,
|
|
10616
11570
|
ecosystem: ecosystemFromDistro(rec.Distro),
|
|
@@ -10680,16 +11634,9 @@ async function convertPrismaToHdf(input, converterVersion = "1.0.0") {
|
|
|
10680
11634
|
}
|
|
10681
11635
|
//#endregion
|
|
10682
11636
|
//#region converters/netsparker-to-hdf/typescript/converter.ts
|
|
10683
|
-
const
|
|
10684
|
-
"critical": 1,
|
|
10685
|
-
"high": .7,
|
|
10686
|
-
"medium": .5,
|
|
10687
|
-
"low": .3,
|
|
10688
|
-
"best_practice": 0,
|
|
10689
|
-
"information": 0
|
|
10690
|
-
};
|
|
11637
|
+
const NETSPARKER_ALIASES = { best_practice: 0 };
|
|
10691
11638
|
function getImpact$2(severity) {
|
|
10692
|
-
return
|
|
11639
|
+
return severityToImpactWithAliases(severity, NETSPARKER_ALIASES, .5);
|
|
10693
11640
|
}
|
|
10694
11641
|
const NETSPARKER_US_DATETIME = /^\d{2}\/\d{2}\/\d{4} \d{1,2}:\d{2} (AM|PM)$/;
|
|
10695
11642
|
function parseNetsparkerTimestamp(s) {
|
|
@@ -10824,6 +11771,7 @@ function buildRequirement$3(vuln, initiated) {
|
|
|
10824
11771
|
const pci32 = vuln.classification?.pci32;
|
|
10825
11772
|
if (pci32) extras.pci32 = pci32;
|
|
10826
11773
|
const tags = buildNistCciTags(nist, cciTags, Object.keys(extras).length > 0 ? extras : void 0);
|
|
11774
|
+
markUnratedSeverity(tags, vuln.severity);
|
|
10827
11775
|
const descriptions = [{
|
|
10828
11776
|
label: "default",
|
|
10829
11777
|
data: formatControlDesc(vuln) || vuln.name || ""
|
|
@@ -11219,14 +12167,16 @@ function buildRequirementFromResult(result, filename) {
|
|
|
11219
12167
|
const startTime = (startTimeStr ? parseTimestamp(startTimeStr) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
11220
12168
|
const runTime = computeRunTime(result.response.milestones?.service_started, result.response.milestones?.service_completed);
|
|
11221
12169
|
const score = result.result.score;
|
|
11222
|
-
const
|
|
11223
|
-
const
|
|
12170
|
+
const scoreless = score === void 0 || score === null;
|
|
12171
|
+
const status = scoreless ? ResultStatus.NotReviewed : determineStatus(score);
|
|
12172
|
+
const message = scoreless ? "Conveyor result carries no score; verdict not evaluated." : void 0;
|
|
12173
|
+
const toResult = (codeDesc) => createResult(status, message, {
|
|
11224
12174
|
codeDesc,
|
|
11225
12175
|
startTime,
|
|
11226
12176
|
runTime
|
|
11227
12177
|
});
|
|
11228
12178
|
const results = result.result.sections.length > 0 ? result.result.sections.map((section) => toResult(buildCodeDesc$1(section, scannerName))) : [toResult(`No sections reported by ${scannerName}`)];
|
|
11229
|
-
const req = createRequirement(result.sha256, filename, descriptions, scoreToImpact(score), results, { tags });
|
|
12179
|
+
const req = createRequirement(result.sha256, filename, descriptions, scoreless ? 0 : scoreToImpact(score), results, { tags });
|
|
11230
12180
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
11231
12181
|
const controlType = deriveControlTypeFromTags([...nist]);
|
|
11232
12182
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -11301,17 +12251,17 @@ async function convertConveyorToHdf(input, converterVersion = "1.0.0") {
|
|
|
11301
12251
|
*/
|
|
11302
12252
|
/** Attribute prefix used by fast-xml-parser — all XML attributes are accessed via `@_name`. */
|
|
11303
12253
|
const A = "@_";
|
|
11304
|
-
/** Veracode severity level (0-5)
|
|
11305
|
-
const
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
12254
|
+
/** Veracode severity level (0-5) aliases; mirrors Go's veracodeAliases. */
|
|
12255
|
+
const VERACODE_ALIASES = {
|
|
12256
|
+
"5": .9,
|
|
12257
|
+
"4": .7,
|
|
12258
|
+
"3": .5,
|
|
12259
|
+
"2": .3,
|
|
12260
|
+
"1": .1,
|
|
12261
|
+
"0": 0
|
|
12262
|
+
};
|
|
11313
12263
|
function veracodeSeverityToImpact(severity) {
|
|
11314
|
-
return
|
|
12264
|
+
return severityToImpactWithAliases(severity, VERACODE_ALIASES, .1);
|
|
11315
12265
|
}
|
|
11316
12266
|
/** Get an attribute from a parsed XML node, entity-decoded. */
|
|
11317
12267
|
function attr(node, name) {
|
|
@@ -11556,12 +12506,12 @@ function buildCWERequirements(severities, firstBuildDate) {
|
|
|
11556
12506
|
for (const sev of severities) {
|
|
11557
12507
|
const impact = veracodeSeverityToImpact(attr(sev, "level"));
|
|
11558
12508
|
const categories = ensureArray(sev.category);
|
|
11559
|
-
for (const cat of categories) requirements.push(buildCWERequirement(cat, impact, firstBuildDate));
|
|
12509
|
+
for (const cat of categories) requirements.push(buildCWERequirement(cat, attr(sev, "level"), impact, firstBuildDate));
|
|
11560
12510
|
}
|
|
11561
12511
|
return requirements;
|
|
11562
12512
|
}
|
|
11563
12513
|
/** Build a single CWE-based requirement from a category. */
|
|
11564
|
-
function buildCWERequirement(cat, impact, firstBuildDate) {
|
|
12514
|
+
function buildCWERequirement(cat, sevLevel, impact, firstBuildDate) {
|
|
11565
12515
|
const cwes = ensureArray(cat.cwe);
|
|
11566
12516
|
const cweIDs = cwes.map((c) => attr(c, "cweid")).filter(Boolean);
|
|
11567
12517
|
const nist = mapCWEToNIST(cweIDs, DEFAULT_REMEDIATION_NIST_TAGS$1);
|
|
@@ -11574,6 +12524,7 @@ function buildCWERequirement(cat, impact, firstBuildDate) {
|
|
|
11574
12524
|
if (values.length > 0) extras[key] = values;
|
|
11575
12525
|
}
|
|
11576
12526
|
const tags = buildNistCciTags(nist, cciTags, extras);
|
|
12527
|
+
markUnratedSeverity(tags, sevLevel);
|
|
11577
12528
|
const cweList = cweIDs.map((id) => `CWE-${id}`);
|
|
11578
12529
|
const descriptions = [{
|
|
11579
12530
|
label: "default",
|
|
@@ -11685,6 +12636,7 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
|
|
|
11685
12636
|
else nist = DEFAULT_REMEDIATION_NIST_TAGS$1;
|
|
11686
12637
|
const cciTags = nistToCci(nist);
|
|
11687
12638
|
const tags = buildNistCciTags(nist, cciTags, {});
|
|
12639
|
+
markUnratedSeverity(tags, attr(vuln, "severity"));
|
|
11688
12640
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
11689
12641
|
const results = components.map((comp) => createResult(ResultStatus.Failed, void 0, {
|
|
11690
12642
|
codeDesc: formatSCACodeDesc(comp),
|
|
@@ -12017,14 +12969,6 @@ function repoNameFromURI(uri) {
|
|
|
12017
12969
|
//#endregion
|
|
12018
12970
|
//#region converters/msft-defender-cloud-to-hdf/typescript/converter.ts
|
|
12019
12971
|
/**
|
|
12020
|
-
* Severity to HDF impact mapping.
|
|
12021
|
-
*/
|
|
12022
|
-
const IMPACT_MAPPING$1 = {
|
|
12023
|
-
high: .7,
|
|
12024
|
-
medium: .5,
|
|
12025
|
-
low: .3
|
|
12026
|
-
};
|
|
12027
|
-
/**
|
|
12028
12972
|
* Maps Azure status code to HDF ResultStatus.
|
|
12029
12973
|
*/
|
|
12030
12974
|
function mapStatus(code) {
|
|
@@ -12052,7 +12996,7 @@ function extractSubscriptionID(resourcePath) {
|
|
|
12052
12996
|
function buildRequirement(assessmentID, assessments, scanTime) {
|
|
12053
12997
|
const rep = assessments[0];
|
|
12054
12998
|
const meta = rep.properties.metadata;
|
|
12055
|
-
const impact =
|
|
12999
|
+
const impact = severityToImpact(meta.severity);
|
|
12056
13000
|
const tags = {};
|
|
12057
13001
|
if (meta.categories.length > 0) tags["categories"] = meta.categories;
|
|
12058
13002
|
if (meta.tactics.length > 0) tags["tactics"] = meta.tactics;
|
|
@@ -12144,21 +13088,6 @@ async function convertMsftDefenderCloudToHdf(input, converterVersion = "1.0.0")
|
|
|
12144
13088
|
//#endregion
|
|
12145
13089
|
//#region converters/msft-defender-endpoint-to-hdf/typescript/converter.ts
|
|
12146
13090
|
/**
|
|
12147
|
-
* Severity to HDF impact mapping.
|
|
12148
|
-
*/
|
|
12149
|
-
const IMPACT_MAPPING = {
|
|
12150
|
-
high: .7,
|
|
12151
|
-
medium: .5,
|
|
12152
|
-
low: .3,
|
|
12153
|
-
informational: 0
|
|
12154
|
-
};
|
|
12155
|
-
/**
|
|
12156
|
-
* Maps MDE severity to HDF impact value.
|
|
12157
|
-
*/
|
|
12158
|
-
function severityToImpact$2(severity) {
|
|
12159
|
-
return IMPACT_MAPPING[severity.toLowerCase()] ?? .5;
|
|
12160
|
-
}
|
|
12161
|
-
/**
|
|
12162
13091
|
* An MDE alert is a detection that fired, so every alert is a raw Failed result.
|
|
12163
13092
|
* Consumer triage (falsePositive, expected-activity) never flips the raw status —
|
|
12164
13093
|
* it rides in a structured Status_Override that carries effectiveStatus + full
|
|
@@ -12262,7 +13191,7 @@ function formatMessage(alert) {
|
|
|
12262
13191
|
const parts = [];
|
|
12263
13192
|
parts.push(`Alert: ${alert.title}`);
|
|
12264
13193
|
parts.push(`Status: ${alert.status}`);
|
|
12265
|
-
parts.push(`Severity: ${alert.severity}`);
|
|
13194
|
+
parts.push(`Severity: ${alert.severity ?? ""}`);
|
|
12266
13195
|
if (alert.threatDisplayName) parts.push(`Threat: ${alert.threatDisplayName}`);
|
|
12267
13196
|
if (alert.alertWebUrl) parts.push(`URL: ${alert.alertWebUrl}`);
|
|
12268
13197
|
return parts.join("\n");
|
|
@@ -12357,7 +13286,7 @@ function deriveScanTimestamp(alerts) {
|
|
|
12357
13286
|
* Converts a single MDE alert to an HDF EvaluatedRequirement.
|
|
12358
13287
|
*/
|
|
12359
13288
|
function alertToRequirement(alert, scanTime) {
|
|
12360
|
-
const impact = severityToImpact
|
|
13289
|
+
const impact = severityToImpact(alert.severity);
|
|
12361
13290
|
const codeDesc = formatEvidence$1(alert.evidence ?? []);
|
|
12362
13291
|
const message = formatMessage(alert);
|
|
12363
13292
|
const startTime = resolveStartTime(alert, scanTime);
|
|
@@ -12644,7 +13573,9 @@ function buildTestResultObj(hdfData, baseline) {
|
|
|
12644
13573
|
for (const req of baseline.requirements) {
|
|
12645
13574
|
const ruleIdRef = sanitizeXccdfId("xccdf_hdf_rule_" + req.id + "_rule");
|
|
12646
13575
|
const stigId = tagString(req.tags, "stig_id");
|
|
12647
|
-
const
|
|
13576
|
+
const statusInput = requirementStatusInput(req);
|
|
13577
|
+
const resolved = computeEffectiveStatus(statusInput);
|
|
13578
|
+
const effective = resolved !== worstStatus(statusInput.resultStatuses ?? []) ? hdfStatusToXccdf(resolved) : void 0;
|
|
12648
13579
|
for (const result of req.results) {
|
|
12649
13580
|
const status = effective ?? hdfStatusToXccdf(result.status);
|
|
12650
13581
|
if (status === "pass") {
|
|
@@ -12829,11 +13760,69 @@ function toKebabCase(title, fallback) {
|
|
|
12829
13760
|
if (!title) return fallback;
|
|
12830
13761
|
let name = title.toLowerCase();
|
|
12831
13762
|
name = name.replace(/[^a-z0-9]/g, "-");
|
|
12832
|
-
|
|
12833
|
-
|
|
13763
|
+
name = name.replace(/-+/g, "-");
|
|
13764
|
+
let start = 0;
|
|
13765
|
+
while (start < name.length && name[start] === "-") start++;
|
|
13766
|
+
let end = name.length;
|
|
13767
|
+
while (end > start && name[end - 1] === "-") end--;
|
|
13768
|
+
name = name.slice(start, end);
|
|
12834
13769
|
if (name.length > 80) name = name.slice(0, 80);
|
|
12835
13770
|
return name;
|
|
12836
13771
|
}
|
|
13772
|
+
/**
|
|
13773
|
+
* Encode an arbitrary identifier into OSCAL's TokenDatatype shape:
|
|
13774
|
+
* `^(\p{L}|_)(\p{L}|\p{N}|[.\-_])*$`
|
|
13775
|
+
*
|
|
13776
|
+
* HDF requirement ids come from whatever the source tool numbers its rules with,
|
|
13777
|
+
* and only some of those shapes are already tokens. Measured across this
|
|
13778
|
+
* package's converter fixtures, 46% are not (57% of the distinct ids): package-style ids
|
|
13779
|
+
* carrying '/', CIS control numbers starting with a digit, advisory ids carrying
|
|
13780
|
+
* ':'. Copying one of those into a token-typed OSCAL field produces a document
|
|
13781
|
+
* the target schema rejects while the converter resolves successfully.
|
|
13782
|
+
*
|
|
13783
|
+
* The kept set is deliberately ASCII — [A-Za-z0-9._-] — rather than the wider
|
|
13784
|
+
* \p{L}/\p{N} the pattern permits. Delegating to each platform's Unicode tables
|
|
13785
|
+
* looked equivalent and was not: V8's \p{L}/\p{N} and Go's unicode package are
|
|
13786
|
+
* built from different Unicode versions, and comparing the two implementations
|
|
13787
|
+
* across the whole code-point range turned up 4657 characters they disagree on.
|
|
13788
|
+
* An explicit ASCII set has no such dependency and is identical in both languages
|
|
13789
|
+
* by construction. It costs nothing on real data: no requirement id in this
|
|
13790
|
+
* package's converter fixtures is non-ASCII, and a non-ASCII one
|
|
13791
|
+
* is preserved in full by the caller's source-id prop regardless.
|
|
13792
|
+
*
|
|
13793
|
+
* Every character outside that set becomes '_', and a leading '_' is prepended
|
|
13794
|
+
* when the result would not start with a letter or '_'. An id built only from
|
|
13795
|
+
* the kept characters is returned unchanged; note that this is narrower than
|
|
13796
|
+
* token shape, so a token-valid non-ASCII id such as "café" is still rewritten.
|
|
13797
|
+
*
|
|
13798
|
+
* Two different ids can encode to the same token ('a/b' and 'a:b' both yield
|
|
13799
|
+
* 'a_b'), which is why callers must also record the source id in the emitted
|
|
13800
|
+
* document — for SAR that is a prop on the finding, trimmed because OSCAL's
|
|
13801
|
+
* StringDatatype forbids a padded value.
|
|
13802
|
+
*/
|
|
13803
|
+
function oscalToken(s) {
|
|
13804
|
+
if (s === "") return "";
|
|
13805
|
+
const out = [...s].map((ch) => /[A-Za-z0-9.\-_]/.test(ch) ? ch : "_").join("");
|
|
13806
|
+
const [first = ""] = [...out];
|
|
13807
|
+
return /[A-Za-z_]/.test(first) ? out : `_${out}`;
|
|
13808
|
+
}
|
|
13809
|
+
/**
|
|
13810
|
+
* Render a value for a field OSCAL types as StringDatatype, whose pattern
|
|
13811
|
+
* `^\S(.*\S)?$` forbids an empty value and any leading or trailing whitespace.
|
|
13812
|
+
* HDF constrains none of the strings that feed those fields — no minLength
|
|
13813
|
+
* anywhere in hdf-amendments — so a padded or empty value is valid HDF that
|
|
13814
|
+
* would otherwise produce a document the target schema rejects at exit 0.
|
|
13815
|
+
*
|
|
13816
|
+
* Returns '' when nothing survives trimming, which callers treat as "omit this
|
|
13817
|
+
* field" rather than substituting a placeholder: OSCAL marks most of these
|
|
13818
|
+
* fields optional, so leaving one out says exactly as much as the source did.
|
|
13819
|
+
* The companion for token-typed fields is oscalToken.
|
|
13820
|
+
*
|
|
13821
|
+
* Mirrors OSCALString in the Go peer.
|
|
13822
|
+
*/
|
|
13823
|
+
function oscalString(s) {
|
|
13824
|
+
return s.trim();
|
|
13825
|
+
}
|
|
12837
13826
|
//#endregion
|
|
12838
13827
|
//#region converters/hdf-to-oscal-sar/typescript/converter.ts
|
|
12839
13828
|
/**
|
|
@@ -12896,12 +13885,18 @@ function buildOSCALDocument(hdfResults) {
|
|
|
12896
13885
|
else importAP = { href: "#" };
|
|
12897
13886
|
const subjects = buildSubjects(hdfResults.components);
|
|
12898
13887
|
const results = [];
|
|
12899
|
-
|
|
13888
|
+
const resources = [];
|
|
13889
|
+
for (const baseline of hdfResults.baselines) {
|
|
13890
|
+
const { result, resources: baselineResources } = baselineToResult(baseline, timestamp, toolActorUuid, subjects);
|
|
13891
|
+
results.push(result);
|
|
13892
|
+
resources.push(...baselineResources);
|
|
13893
|
+
}
|
|
12900
13894
|
return { "assessment-results": {
|
|
12901
13895
|
uuid: crypto.randomUUID(),
|
|
12902
13896
|
metadata,
|
|
12903
13897
|
"import-ap": importAP,
|
|
12904
|
-
results
|
|
13898
|
+
results,
|
|
13899
|
+
...resources.length > 0 ? { "back-matter": { resources } } : {}
|
|
12905
13900
|
} };
|
|
12906
13901
|
}
|
|
12907
13902
|
/** Earliest startTime across the results, or undefined if none carry one. */
|
|
@@ -12933,7 +13928,8 @@ function assessmentStart(baseline, fallback) {
|
|
|
12933
13928
|
return formatAssessmentTime(earliest, fallback);
|
|
12934
13929
|
}
|
|
12935
13930
|
/**
|
|
12936
|
-
* Converts a single EvaluatedBaseline to an OSCAL Result
|
|
13931
|
+
* Converts a single EvaluatedBaseline to an OSCAL Result plus any back-matter
|
|
13932
|
+
* resources (embedded check source code) its findings link to.
|
|
12937
13933
|
*/
|
|
12938
13934
|
function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
12939
13935
|
let title = baseline.name;
|
|
@@ -12948,14 +13944,18 @@ function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
|
12948
13944
|
const findings = [];
|
|
12949
13945
|
const observations = [];
|
|
12950
13946
|
const risks = [];
|
|
13947
|
+
const resources = [];
|
|
12951
13948
|
const includeControls = [];
|
|
12952
13949
|
const seenControl = /* @__PURE__ */ new Set();
|
|
12953
13950
|
for (const req of baseline.requirements) {
|
|
12954
|
-
const
|
|
13951
|
+
const nistId = nistTagToControlId(req.id);
|
|
13952
|
+
if (nistId === "") continue;
|
|
13953
|
+
const { finding, observation, risk, resource } = requirementToFindingSet(req, timestamp, toolActorUuid, subjects);
|
|
12955
13954
|
findings.push(finding);
|
|
12956
13955
|
if (observation) observations.push(observation);
|
|
12957
13956
|
if (risk) risks.push(risk);
|
|
12958
|
-
|
|
13957
|
+
if (resource) resources.push(resource);
|
|
13958
|
+
const cid = oscalToken(nistId);
|
|
12959
13959
|
if (cid !== "" && !seenControl.has(cid)) {
|
|
12960
13960
|
seenControl.add(cid);
|
|
12961
13961
|
includeControls.push({ "control-id": cid });
|
|
@@ -12963,15 +13963,18 @@ function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
|
12963
13963
|
}
|
|
12964
13964
|
const controlSelection = includeControls.length > 0 ? { "include-controls": includeControls } : {};
|
|
12965
13965
|
return {
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
13966
|
+
result: {
|
|
13967
|
+
uuid: crypto.randomUUID(),
|
|
13968
|
+
title,
|
|
13969
|
+
description,
|
|
13970
|
+
start: assessmentStart(baseline, timestamp),
|
|
13971
|
+
...resultProps.length > 0 ? { props: resultProps } : {},
|
|
13972
|
+
"reviewed-controls": { "control-selections": [controlSelection] },
|
|
13973
|
+
findings,
|
|
13974
|
+
observations,
|
|
13975
|
+
risks
|
|
13976
|
+
},
|
|
13977
|
+
resources
|
|
12975
13978
|
};
|
|
12976
13979
|
}
|
|
12977
13980
|
/**
|
|
@@ -12992,18 +13995,35 @@ function buildSubjects(components) {
|
|
|
12992
13995
|
* Converts an EvaluatedRequirement into a Finding, optional Observation, and optional Risk.
|
|
12993
13996
|
*/
|
|
12994
13997
|
function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
12995
|
-
const controlID = nistTagToControlId(req.id);
|
|
13998
|
+
const controlID = oscalToken(nistTagToControlId(req.id));
|
|
12996
13999
|
const results = req.results ?? [];
|
|
12997
14000
|
const descriptions = req.descriptions ?? [];
|
|
12998
|
-
const { state, reason } = effectiveState(req
|
|
14001
|
+
const { state, reason } = effectiveState(req);
|
|
12999
14002
|
const findingDesc = extractDefaultDescription(descriptions);
|
|
13000
|
-
const props = [
|
|
14003
|
+
const props = [{
|
|
14004
|
+
name: "hdf-requirement-id",
|
|
14005
|
+
value: oscalString(req.id)
|
|
14006
|
+
}];
|
|
13001
14007
|
const addProp = (name, value) => {
|
|
13002
14008
|
if (value !== "") props.push({
|
|
13003
14009
|
name,
|
|
13004
14010
|
value
|
|
13005
14011
|
});
|
|
13006
14012
|
};
|
|
14013
|
+
const addProseProp = (name, text) => {
|
|
14014
|
+
const preview = previewLine(text);
|
|
14015
|
+
if (preview === "") return;
|
|
14016
|
+
const p = {
|
|
14017
|
+
name,
|
|
14018
|
+
value: preview
|
|
14019
|
+
};
|
|
14020
|
+
if (preview !== text) p.remarks = text;
|
|
14021
|
+
props.push(p);
|
|
14022
|
+
};
|
|
14023
|
+
const descriptionByLabel = (label) => {
|
|
14024
|
+
const d = descriptions.find((x) => x.label === label);
|
|
14025
|
+
return d ? d.data : "";
|
|
14026
|
+
};
|
|
13007
14027
|
const pushTagValues = (key) => {
|
|
13008
14028
|
const raw = req.tags?.[key];
|
|
13009
14029
|
if (Array.isArray(raw)) {
|
|
@@ -13012,15 +14032,9 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13012
14032
|
};
|
|
13013
14033
|
pushTagValues("nist");
|
|
13014
14034
|
pushTagValues("cci");
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
"fix",
|
|
13019
|
-
"rationale"
|
|
13020
|
-
]) {
|
|
13021
|
-
const d = descriptions.find((x) => x.label === label);
|
|
13022
|
-
addProp(label, d ? d.data : "");
|
|
13023
|
-
}
|
|
14035
|
+
addProseProp("check", descriptionByLabel("check"));
|
|
14036
|
+
addProseProp("rationale", descriptionByLabel("rationale"));
|
|
14037
|
+
if (req.impact <= 0) addProseProp("fix", descriptionByLabel("fix"));
|
|
13024
14038
|
if (req.controlType) addProp("control-type", req.controlType);
|
|
13025
14039
|
if (req.verificationMethod) addProp("verification-method", req.verificationMethod);
|
|
13026
14040
|
if (req.applicability) addProp("applicability", req.applicability);
|
|
@@ -13048,10 +14062,7 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13048
14062
|
href: o.uri,
|
|
13049
14063
|
rel: "reference"
|
|
13050
14064
|
});
|
|
13051
|
-
else if (typeof o.ref === "string")
|
|
13052
|
-
name: "reference",
|
|
13053
|
-
value: o.ref
|
|
13054
|
-
});
|
|
14065
|
+
else if (typeof o.ref === "string") addProseProp("reference", o.ref);
|
|
13055
14066
|
}
|
|
13056
14067
|
for (const er of req.externalReferences ?? []) if (typeof er.href === "string" && er.href !== "") links.push({
|
|
13057
14068
|
href: er.href,
|
|
@@ -13059,6 +14070,22 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13059
14070
|
});
|
|
13060
14071
|
let title = req.id;
|
|
13061
14072
|
if (req.title && req.title !== "") title = req.title;
|
|
14073
|
+
let resource;
|
|
14074
|
+
if (req.code != null && req.code.trim() !== "") {
|
|
14075
|
+
const resourceUuid = crypto.randomUUID();
|
|
14076
|
+
resource = {
|
|
14077
|
+
uuid: resourceUuid,
|
|
14078
|
+
title: `Check source code for ${req.id}`,
|
|
14079
|
+
base64: {
|
|
14080
|
+
value: Buffer.from(req.code, "utf-8").toString("base64"),
|
|
14081
|
+
"media-type": "text/plain"
|
|
14082
|
+
}
|
|
14083
|
+
};
|
|
14084
|
+
links.push({
|
|
14085
|
+
href: `#${resourceUuid}`,
|
|
14086
|
+
rel: "code"
|
|
14087
|
+
});
|
|
14088
|
+
}
|
|
13062
14089
|
const targetStatus = { state };
|
|
13063
14090
|
if (reason) targetStatus.reason = reason;
|
|
13064
14091
|
const remarks = overrideRemarks(req);
|
|
@@ -13127,16 +14154,32 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13127
14154
|
return {
|
|
13128
14155
|
finding,
|
|
13129
14156
|
observation,
|
|
13130
|
-
risk
|
|
14157
|
+
risk,
|
|
14158
|
+
resource
|
|
13131
14159
|
};
|
|
13132
14160
|
}
|
|
13133
14161
|
/**
|
|
13134
|
-
*
|
|
13135
|
-
*
|
|
14162
|
+
* Reduces prose to a single line legal as an OSCAL StringDatatype prop value:
|
|
14163
|
+
* the first non-empty line, trimmed, truncated to 120 code points.
|
|
14164
|
+
*/
|
|
14165
|
+
function previewLine(text) {
|
|
14166
|
+
for (let line of text.split("\n")) {
|
|
14167
|
+
line = line.trim();
|
|
14168
|
+
if (line === "") continue;
|
|
14169
|
+
const maxRunes = 120;
|
|
14170
|
+
const runes = Array.from(line);
|
|
14171
|
+
if (runes.length > maxRunes) line = `${runes.slice(0, maxRunes - 3).join("").trim()}...`;
|
|
14172
|
+
return line;
|
|
14173
|
+
}
|
|
14174
|
+
return "";
|
|
14175
|
+
}
|
|
14176
|
+
/**
|
|
14177
|
+
* Derives the OSCAL finding target state/reason from the canonical ladder —
|
|
14178
|
+
* the stored effectiveStatus field is never read (it is an output cache; see
|
|
14179
|
+
* status-determination.md).
|
|
13136
14180
|
*/
|
|
13137
|
-
function effectiveState(req
|
|
13138
|
-
|
|
13139
|
-
return aggregateStatus(results);
|
|
14181
|
+
function effectiveState(req) {
|
|
14182
|
+
return oscalStateFromStatus(requirementEffectiveStatus(req));
|
|
13140
14183
|
}
|
|
13141
14184
|
/** Maps a single HDF result status to an OSCAL finding target state/reason. */
|
|
13142
14185
|
function oscalStateFromStatus(status) {
|
|
@@ -13268,62 +14311,6 @@ function formatFloat(n) {
|
|
|
13268
14311
|
return String(n);
|
|
13269
14312
|
}
|
|
13270
14313
|
/**
|
|
13271
|
-
* Determines the overall finding status from requirement results.
|
|
13272
|
-
*/
|
|
13273
|
-
function aggregateStatus(results) {
|
|
13274
|
-
if (results.length === 0) return {
|
|
13275
|
-
state: "not-satisfied",
|
|
13276
|
-
reason: "other"
|
|
13277
|
-
};
|
|
13278
|
-
let hasFailed = false;
|
|
13279
|
-
let hasError = false;
|
|
13280
|
-
let hasNotReviewed = false;
|
|
13281
|
-
let hasNotApplicable = false;
|
|
13282
|
-
let allPassed = true;
|
|
13283
|
-
for (const r of results) switch (r.status) {
|
|
13284
|
-
case "passed": break;
|
|
13285
|
-
case "failed":
|
|
13286
|
-
hasFailed = true;
|
|
13287
|
-
allPassed = false;
|
|
13288
|
-
break;
|
|
13289
|
-
case "error":
|
|
13290
|
-
hasError = true;
|
|
13291
|
-
allPassed = false;
|
|
13292
|
-
break;
|
|
13293
|
-
case "notReviewed":
|
|
13294
|
-
hasNotReviewed = true;
|
|
13295
|
-
allPassed = false;
|
|
13296
|
-
break;
|
|
13297
|
-
case "notApplicable":
|
|
13298
|
-
hasNotApplicable = true;
|
|
13299
|
-
allPassed = false;
|
|
13300
|
-
break;
|
|
13301
|
-
default:
|
|
13302
|
-
allPassed = false;
|
|
13303
|
-
break;
|
|
13304
|
-
}
|
|
13305
|
-
if (allPassed) return {
|
|
13306
|
-
state: "satisfied",
|
|
13307
|
-
reason: ""
|
|
13308
|
-
};
|
|
13309
|
-
if (hasFailed || hasError) return {
|
|
13310
|
-
state: "not-satisfied",
|
|
13311
|
-
reason: ""
|
|
13312
|
-
};
|
|
13313
|
-
if (hasNotReviewed) return {
|
|
13314
|
-
state: "not-satisfied",
|
|
13315
|
-
reason: "other"
|
|
13316
|
-
};
|
|
13317
|
-
if (hasNotApplicable) return {
|
|
13318
|
-
state: "not-satisfied",
|
|
13319
|
-
reason: "not-applicable"
|
|
13320
|
-
};
|
|
13321
|
-
return {
|
|
13322
|
-
state: "not-satisfied",
|
|
13323
|
-
reason: ""
|
|
13324
|
-
};
|
|
13325
|
-
}
|
|
13326
|
-
/**
|
|
13327
14314
|
* Finds the "default" labeled description.
|
|
13328
14315
|
*/
|
|
13329
14316
|
function extractDefaultDescription(descriptions) {
|
|
@@ -14522,7 +15509,11 @@ function advisoryURI(publisher, tracking) {
|
|
|
14522
15509
|
/* c8 ignore next */
|
|
14523
15510
|
const id = tracking.id ?? "";
|
|
14524
15511
|
const ns = publisher.namespace;
|
|
14525
|
-
if (ns && id)
|
|
15512
|
+
if (ns && id) {
|
|
15513
|
+
let end = ns.length;
|
|
15514
|
+
while (end > 0 && ns[end - 1] === "/") end--;
|
|
15515
|
+
return `${ns.slice(0, end)}/${id}`;
|
|
15516
|
+
}
|
|
14526
15517
|
return id;
|
|
14527
15518
|
}
|
|
14528
15519
|
function overlap(ids, scope) {
|
|
@@ -17017,6 +18008,6 @@ function capStixExternalRefs(container, label) {
|
|
|
17017
18008
|
container.externalReferences = [...other, ...capped];
|
|
17018
18009
|
}
|
|
17019
18010
|
//#endregion
|
|
17020
|
-
export { convertAsffToHdf, convertAwsConfigToHdf, convertBurpsuiteToHdf, convertCheckovToHdf, convertCklToHdf, convertCklbToHdf, convertConveyorToHdf, convertCsafVexToHdf, convertCyclonedxToHdf, convertCyclonedxVexToHdf, convertDbprotectToHdf, convertDefectDojoToHdf, convertDeptrackToHdf, convertFortifyToHdf, convertGitlabToHdf, convertGosecToHdf, convertGrypeToHdf, convertHdfToAsff, convertHdfToCkl, convertHdfToCklb, convertHdfToCsafVex, convertHdfToCsv, convertHdfToCyclonedxVex, convertHdfToEcs, convertHdfToOcsf, convertHdfToOpenVex, convertHdfToOscalPoam, convertHdfToOscalSar, convertHdfToSplunk, convertHdfToXccdf, convertHdfToXml, convertHipcheckToHdf, convertIonchannelToHdf, convertJfrogXrayToHdf, convertJunitToHdf, convertMsftDefenderCloudToHdf, convertMsftDefenderDevopsToHdf, convertMsftDefenderEndpointToHdf, convertMsftSecureScoreToHdf, convertNessusToHdf, convertNetsparkerToHdf, convertNeuvectorToHdf, convertNiktoToHdf, convertOpenVexToHdf, convertOscalCatalogToHdf, convertOscalComponentToHdf, convertOscalPoamToHdf, convertOscalProfileToHdf, convertOscalSapToHdf, convertOscalSarToHdf, convertOscalSspToHdf, convertPrismaToHdf, convertSarifToHdf, convertScoutsuiteToHdf, convertSnykToHdf, convertSonarqubeToHdf, convertSpdxVexToHdf, convertSplunkToHdf, convertTrufflehogToHdf, convertTwistlockToHdf,
|
|
18011
|
+
export { convertAsffToHdf, convertAwsConfigToHdf, convertBurpsuiteToHdf, convertCheckovToHdf, convertCklToHdf, convertCklbToHdf, convertConveyorToHdf, convertCsafVexToHdf, convertCyclonedxToHdf, convertCyclonedxVexToHdf, convertDbprotectToHdf, convertDefectDojoToHdf, convertDeptrackToHdf, convertFortifyToHdf, convertGitlabToHdf, convertGosecToHdf, convertGrypeToHdf, convertHdfToAsff, convertHdfToCkl, convertHdfToCklb, convertHdfToCsafVex, convertHdfToCsv, convertHdfToCyclonedxVex, convertHdfToEcs, convertHdfToOcsf, convertHdfToOpenVex, convertHdfToOscalPoam, convertHdfToOscalSar, convertHdfToSplunk, convertHdfToXccdf, convertHdfToXml, convertHipcheckToHdf, convertIonchannelToHdf, convertJfrogXrayToHdf, convertJunitToHdf, convertKicsToHdf, convertLegacyHdf, convertLegacyHdf as convertV1ToV2, convertMsftDefenderCloudToHdf, convertMsftDefenderDevopsToHdf, convertMsftDefenderEndpointToHdf, convertMsftSecureScoreToHdf, convertNessusToHdf, convertNetsparkerToHdf, convertNeuvectorToHdf, convertNiktoToHdf, convertOpenVexToHdf, convertOscalCatalogToHdf, convertOscalComponentToHdf, convertOscalPoamToHdf, convertOscalProfileToHdf, convertOscalSapToHdf, convertOscalSarToHdf, convertOscalSspToHdf, convertPrismaToHdf, convertSarifToHdf, convertScoutsuiteToHdf, convertSemgrepToHdf, convertSnykToHdf, convertSonarqubeToHdf, convertSpdxVexToHdf, convertSplunkToHdf, convertTrivyToHdf, convertTrufflehogToHdf, convertTwistlockToHdf, convertVeracodeToHdf, convertXccdfResultsToHdf, convertZapToHdf, detectOscalDocumentType, detectStixBundle, enrichStix, isLegacyHdf as isHDFV1, isLegacyHdf, parseStixBundle };
|
|
17021
18012
|
|
|
17022
18013
|
//# sourceMappingURL=index.js.map
|