@mitre/hdf-converters 3.5.1 → 3.6.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +1365 -346
- 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
|
*
|
|
@@ -165,6 +191,37 @@ function parseHdf(input) {
|
|
|
165
191
|
return parseJSON(normalizeHdfTimestamps(input));
|
|
166
192
|
}
|
|
167
193
|
/**
|
|
194
|
+
* The shared structural-input guard for HDF Results exporters: the prologue
|
|
195
|
+
* every exporter needs — empty guard, input-size validation, parse, and a
|
|
196
|
+
* top-level `baselines` array check — returning the parsed document and its
|
|
197
|
+
* baselines. Extracted from exportmap.runExport so non-exportmap exporters stop
|
|
198
|
+
* reinventing (and skipping) it. The "missing baselines field" wording matches
|
|
199
|
+
* exportmap and hdf-to-oscal-sar, so there is no consumer-visible message churn.
|
|
200
|
+
*/
|
|
201
|
+
function requireHdfResults(input, converterName) {
|
|
202
|
+
return requireHdfStructure(input, converterName, "baselines");
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Reject malformed HDF input (empty, oversized, non-JSON, top-level array/null,
|
|
206
|
+
* or a missing/wrong-typed structural key) before a converter zero-fills it into
|
|
207
|
+
* a typed document. A parsed JSON `null` (or array, or primitive) is NOT an HDF
|
|
208
|
+
* document object, so it produces the canonical missing-field error rather than
|
|
209
|
+
* a TypeError — matching Go's nil-map no-op on `null`.
|
|
210
|
+
*/
|
|
211
|
+
function requireHdfStructure(input, converterName, field) {
|
|
212
|
+
if (input.length === 0) throw new Error(`${converterName}: empty input`);
|
|
213
|
+
validateInputSize(input, converterName);
|
|
214
|
+
const parsed = parseHdf(input);
|
|
215
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${converterName}: invalid HDF structure: missing ${field} field`);
|
|
216
|
+
const doc = parsed;
|
|
217
|
+
const items = doc[field];
|
|
218
|
+
if (!Array.isArray(items)) throw new Error(`${converterName}: invalid HDF structure: missing ${field} field`);
|
|
219
|
+
return {
|
|
220
|
+
doc,
|
|
221
|
+
items
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
168
225
|
* Read a timestamp field off a parsed HDF document.
|
|
169
226
|
*
|
|
170
227
|
* The generated schema types these as `Date` (startTime, appliedAt, ...), but
|
|
@@ -241,7 +298,7 @@ function ecosystemFromPurlType(type) {
|
|
|
241
298
|
* - purl alone
|
|
242
299
|
* - cpe alone
|
|
243
300
|
*/
|
|
244
|
-
function buildAffectedPackage$
|
|
301
|
+
function buildAffectedPackage$2(opts) {
|
|
245
302
|
const pkg = {};
|
|
246
303
|
if (opts.purl) pkg.purl = opts.purl;
|
|
247
304
|
if (opts.cpe) pkg.cpe = opts.cpe;
|
|
@@ -444,6 +501,16 @@ function buildNoFindingsRequirement(id, codeDesc, startTime) {
|
|
|
444
501
|
tags: {}
|
|
445
502
|
};
|
|
446
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* Shared marker converters emit when a source severity is unrated
|
|
506
|
+
* (isUnratedSeverity), so a defaulted impact stays distinguishable from a
|
|
507
|
+
* genuine rated medium. TS peer of MarkUnratedSeverity in converterutil.go.
|
|
508
|
+
*/
|
|
509
|
+
const UNRATED_SEVERITY_TAG = "severity_rating";
|
|
510
|
+
const UNRATED_SEVERITY_VALUE = "unrated";
|
|
511
|
+
function markUnratedSeverity(tags, severity) {
|
|
512
|
+
if (isUnratedSeverity(severity)) tags[UNRATED_SEVERITY_TAG] = UNRATED_SEVERITY_VALUE;
|
|
513
|
+
}
|
|
447
514
|
//#endregion
|
|
448
515
|
//#region converters/legacyhdf-to-hdf/typescript/converter.ts
|
|
449
516
|
/**
|
|
@@ -457,7 +524,7 @@ function buildNoFindingsRequirement(id, codeDesc, startTime) {
|
|
|
457
524
|
* - Overlay flattening: merge overlay/wrapper baselines so every requirement has results
|
|
458
525
|
*/
|
|
459
526
|
/** Valid severity values per HDF v2.0 schema (matches the Go converter). */
|
|
460
|
-
const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
527
|
+
const VALID_SEVERITIES$1 = /* @__PURE__ */ new Set([
|
|
461
528
|
"critical",
|
|
462
529
|
"high",
|
|
463
530
|
"medium",
|
|
@@ -471,7 +538,7 @@ const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
|
471
538
|
function tagSeverityToSeverity(tagSeverity) {
|
|
472
539
|
if (typeof tagSeverity !== "string") return null;
|
|
473
540
|
const normalized = tagSeverity.toLowerCase().trim();
|
|
474
|
-
return VALID_SEVERITIES.has(normalized) ? normalized : null;
|
|
541
|
+
return VALID_SEVERITIES$1.has(normalized) ? normalized : null;
|
|
475
542
|
}
|
|
476
543
|
/**
|
|
477
544
|
* Normalize status values from v1.0 to v2.0 format.
|
|
@@ -489,8 +556,9 @@ function normalizeStatus$1(status) {
|
|
|
489
556
|
}
|
|
490
557
|
/**
|
|
491
558
|
* 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 >
|
|
559
|
+
* worst-wins ordering in @mitre/hdf-utilities (impact=0 → notApplicable
|
|
560
|
+
* unless the roll-up is error; error > failed > passed > notApplicable >
|
|
561
|
+
* notReviewed).
|
|
494
562
|
*/
|
|
495
563
|
function computeEffectiveStatus$1(impact, results) {
|
|
496
564
|
return computeEffectiveStatus({
|
|
@@ -499,6 +567,18 @@ function computeEffectiveStatus$1(impact, results) {
|
|
|
499
567
|
});
|
|
500
568
|
}
|
|
501
569
|
/**
|
|
570
|
+
* Encodes a control-level execution error the source tool reported but did not
|
|
571
|
+
* record as a result, reusing the earliest recorded result time (matching the
|
|
572
|
+
* Go converter).
|
|
573
|
+
*/
|
|
574
|
+
function synthesizedErrorResult(results) {
|
|
575
|
+
return {
|
|
576
|
+
status: "error",
|
|
577
|
+
codeDesc: "Synthesized from the source control-level error status: the tool reported an execution error not reflected in any recorded result",
|
|
578
|
+
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"
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
502
582
|
* Convert v1.0 result to v2.0 result.
|
|
503
583
|
* Transforms snake_case field names to camelCase.
|
|
504
584
|
*/
|
|
@@ -592,9 +672,10 @@ function convertControl(v1Control) {
|
|
|
592
672
|
if (refs) v2Req.refs = refs;
|
|
593
673
|
}
|
|
594
674
|
if (v1Control.source_location !== void 0) v2Req.sourceLocation = v1Control.source_location;
|
|
595
|
-
|
|
675
|
+
const controlLevelError = v1Control.status !== void 0 && normalizeStatus$1(v1Control.status) === "error";
|
|
596
676
|
if (v1Control.results && Array.isArray(v1Control.results)) v2Req.results = v1Control.results.map(convertResult);
|
|
597
|
-
if (!v2Req.
|
|
677
|
+
if (controlLevelError && !(v2Req.results ?? []).some((r) => r.status === "error")) v2Req.results = [...v2Req.results ?? [], synthesizedErrorResult(v2Req.results ?? [])];
|
|
678
|
+
v2Req.effectiveStatus = computeEffectiveStatus$1(v1Control.impact, v2Req.results ?? []);
|
|
598
679
|
v2Req.severity = tagSeverityToSeverity(v1Control.tags?.severity) ?? impactToSeverity(v1Control.impact);
|
|
599
680
|
const rawNist = v1Control.tags?.nist;
|
|
600
681
|
if (Array.isArray(rawNist)) {
|
|
@@ -774,7 +855,7 @@ function documentTimestamp(v1Data) {
|
|
|
774
855
|
}
|
|
775
856
|
return latest ? formatTimestamp(latest) : void 0;
|
|
776
857
|
}
|
|
777
|
-
function
|
|
858
|
+
function convertLegacyHdf(v1Data, converterVersion = "1.0.0") {
|
|
778
859
|
validateInputSize(JSON.stringify(v1Data), "legacyhdf-to-hdf");
|
|
779
860
|
const v2 = {
|
|
780
861
|
baselines: (v1Data.profiles || []).map(convertProfile),
|
|
@@ -820,7 +901,7 @@ function convertV1ToV2(v1Data, converterVersion = "1.0.0") {
|
|
|
820
901
|
* @param data - Unknown data to validate
|
|
821
902
|
* @returns true if data looks like HDF v1.0
|
|
822
903
|
*/
|
|
823
|
-
function
|
|
904
|
+
function isLegacyHdf(data) {
|
|
824
905
|
if (typeof data !== "object" || data === null) return false;
|
|
825
906
|
const obj = data;
|
|
826
907
|
return typeof obj.version === "string" && Array.isArray(obj.profiles) && typeof obj.platform === "object" && obj.platform !== null;
|
|
@@ -1194,7 +1275,7 @@ function trivyLocation(o) {
|
|
|
1194
1275
|
if (el && el !== "0" && el !== sl) loc += `-${el}`;
|
|
1195
1276
|
return loc;
|
|
1196
1277
|
}
|
|
1197
|
-
function buildRequirement$
|
|
1278
|
+
function buildRequirement$19(id, group) {
|
|
1198
1279
|
const primary = group[0];
|
|
1199
1280
|
const impact = Math.max(...group.map(findingImpact));
|
|
1200
1281
|
const descriptions = [{
|
|
@@ -1241,7 +1322,7 @@ function buildBaseline$1(name, findings, resultsChecksum) {
|
|
|
1241
1322
|
groups.set(id, [f]);
|
|
1242
1323
|
}
|
|
1243
1324
|
}
|
|
1244
|
-
return createMinimalBaseline(name, order.map((id) => buildRequirement$
|
|
1325
|
+
return createMinimalBaseline(name, order.map((id) => buildRequirement$19(id, groups.get(id))), { resultsChecksum });
|
|
1245
1326
|
}
|
|
1246
1327
|
/** Converts an ASFF document to HDF Results JSON. */
|
|
1247
1328
|
async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
@@ -1289,7 +1370,7 @@ async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
|
1289
1370
|
}
|
|
1290
1371
|
//#endregion
|
|
1291
1372
|
//#region converters/sarif-to-hdf/typescript/converter.ts
|
|
1292
|
-
const
|
|
1373
|
+
const SARIF_ALIASES = {
|
|
1293
1374
|
error: .7,
|
|
1294
1375
|
warning: .5,
|
|
1295
1376
|
note: .3
|
|
@@ -1354,26 +1435,28 @@ function convertRun(run, version, resultsChecksum, timestamp) {
|
|
|
1354
1435
|
}
|
|
1355
1436
|
function buildRuleMap(run) {
|
|
1356
1437
|
const map = /* @__PURE__ */ new Map();
|
|
1357
|
-
|
|
1438
|
+
for (const ext of run.tool?.extensions ?? []) for (const rule of ext.rules ?? []) map.set(rule.id, rule);
|
|
1439
|
+
for (const rule of run.tool?.driver?.rules ?? []) map.set(rule.id, rule);
|
|
1358
1440
|
return map;
|
|
1359
1441
|
}
|
|
1360
1442
|
function convertResultGroup(ruleId, rule, sarifResults, timestamp) {
|
|
1361
1443
|
const firstResult = sarifResults[0];
|
|
1362
1444
|
const { title, description } = deriveMetadata(firstResult, rule);
|
|
1363
1445
|
let cweIds = extractCweFromRule(rule);
|
|
1364
|
-
if (cweIds.length === 0) cweIds = extractCweIds(resolveMessageText(firstResult.message, rule));
|
|
1446
|
+
if (cweIds.length === 0) cweIds = extractCweIds$1(resolveMessageText(firstResult.message, rule));
|
|
1365
1447
|
const nistControls = mapCWEToNIST(cweIds, DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
1366
1448
|
const cciControls = nistToCci(nistControls);
|
|
1367
1449
|
const ruleLevel = resolveRuleLevel(rule, sarifResults);
|
|
1368
|
-
const impact =
|
|
1450
|
+
const impact = (securitySeverityImpact(rule) ?? severityToImpactWithAliases(ruleLevel, SARIF_ALIASES, 0)) || .1;
|
|
1369
1451
|
const sourceLocation = firstResult.locations && firstResult.locations.length > 0 ? extractSourceLocation$1(firstResult.locations[0]) : void 0;
|
|
1370
1452
|
const results = [];
|
|
1371
1453
|
for (const sr of sarifResults) results.push(...convertSarifResultToHDFResults(sr));
|
|
1372
1454
|
const descriptions = buildDescriptions$2(description, rule, firstResult);
|
|
1373
1455
|
const allSuppressions = sarifResults.flatMap((sr) => sr.suppressions ?? []);
|
|
1374
|
-
const options = { tags: buildTags$
|
|
1456
|
+
const options = { tags: buildTags$4(firstResult, rule, ruleLevel, cweIds, nistControls, cciControls, allSuppressions) };
|
|
1375
1457
|
if (sourceLocation) options.sourceLocation = sourceLocation;
|
|
1376
1458
|
const req = createRequirement(ruleId, title, descriptions, impact, results, options);
|
|
1459
|
+
req.severity = impactToSeverity(impact);
|
|
1377
1460
|
const controlType = deriveControlTypeFromTags(nistControls);
|
|
1378
1461
|
if (controlType !== void 0) req.controlType = controlType;
|
|
1379
1462
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
@@ -1431,7 +1514,7 @@ function packageFromSarifProperties(props) {
|
|
|
1431
1514
|
if (props.purl) ecosystem = ecosystemFromPurlType(parsePurl(props.purl)?.type);
|
|
1432
1515
|
else if (props.ecosystem) ecosystem = ecosystemFromPurlType(props.ecosystem);
|
|
1433
1516
|
else if (name && version) ecosystem = Ecosystem.Generic;
|
|
1434
|
-
return buildAffectedPackage$
|
|
1517
|
+
return buildAffectedPackage$2({
|
|
1435
1518
|
name,
|
|
1436
1519
|
version,
|
|
1437
1520
|
ecosystem,
|
|
@@ -1440,6 +1523,20 @@ function packageFromSarifProperties(props) {
|
|
|
1440
1523
|
fixedInVersion: props.fixedInVersion
|
|
1441
1524
|
});
|
|
1442
1525
|
}
|
|
1526
|
+
const DECIMAL_SCORE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
1527
|
+
function securitySeverityImpact(rule) {
|
|
1528
|
+
const raw = rule?.properties?.["security-severity"];
|
|
1529
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
1530
|
+
let score;
|
|
1531
|
+
if (typeof raw === "string") {
|
|
1532
|
+
const trimmed = raw.trim();
|
|
1533
|
+
if (!DECIMAL_SCORE.test(trimmed)) return void 0;
|
|
1534
|
+
score = Number(trimmed);
|
|
1535
|
+
} else if (typeof raw === "number") score = raw;
|
|
1536
|
+
else return;
|
|
1537
|
+
if (!Number.isFinite(score) || score < 0 || score > 10) return void 0;
|
|
1538
|
+
return roundImpact(score / 10);
|
|
1539
|
+
}
|
|
1443
1540
|
function resolveRuleLevel(rule, results) {
|
|
1444
1541
|
if (rule?.defaultConfiguration?.level) return rule.defaultConfiguration.level;
|
|
1445
1542
|
for (const r of results) if (!r.kind || r.kind === "fail") {
|
|
@@ -1520,7 +1617,7 @@ function extractCweFromRule(rule) {
|
|
|
1520
1617
|
}
|
|
1521
1618
|
return [];
|
|
1522
1619
|
}
|
|
1523
|
-
function extractCweIds(text) {
|
|
1620
|
+
function extractCweIds$1(text) {
|
|
1524
1621
|
return extractCWEIDs(text).map((id) => `CWE-${id}`);
|
|
1525
1622
|
}
|
|
1526
1623
|
function mapKindToStatus(kind) {
|
|
@@ -1602,7 +1699,7 @@ function buildDescriptions$2(defaultDesc, rule, result) {
|
|
|
1602
1699
|
if (result.fixes && result.fixes.length > 0 && result.fixes[0]?.description?.text) descriptions.push(createDescription("fix", result.fixes[0].description.text));
|
|
1603
1700
|
return descriptions;
|
|
1604
1701
|
}
|
|
1605
|
-
function buildTags$
|
|
1702
|
+
function buildTags$4(result, rule, resolvedLevel, cweIds, nistControls, cciControls, allSuppressions) {
|
|
1606
1703
|
const tags = {
|
|
1607
1704
|
severity: resolvedLevel,
|
|
1608
1705
|
cwe: cweIds,
|
|
@@ -2181,7 +2278,7 @@ function ruleResultToRequirement(rr, ruleIndex, groupIndex, scanTime) {
|
|
|
2181
2278
|
const req = createRequirement(id, title, descriptions, impact, [result], { tags });
|
|
2182
2279
|
const code = buildCheckCode(check);
|
|
2183
2280
|
if (code) req.code = code;
|
|
2184
|
-
const refs = buildRefs$
|
|
2281
|
+
const refs = buildRefs$10(ruleDef?.reference);
|
|
2185
2282
|
if (refs.length > 0) req.refs = refs;
|
|
2186
2283
|
const hdfSeverity = xccdfSeverityToHdf(severity);
|
|
2187
2284
|
if (hdfSeverity) req.severity = hdfSeverity;
|
|
@@ -2338,7 +2435,7 @@ function checkDescriptionData(check) {
|
|
|
2338
2435
|
* href, when present, populates url. References that carry nothing are skipped.
|
|
2339
2436
|
* Inner keys are inserted alphabetically to match Go's map JSON ordering.
|
|
2340
2437
|
*/
|
|
2341
|
-
function buildRefs$
|
|
2438
|
+
function buildRefs$10(refs) {
|
|
2342
2439
|
if (!refs) return [];
|
|
2343
2440
|
const out = [];
|
|
2344
2441
|
for (const r of refs) {
|
|
@@ -2863,6 +2960,13 @@ function cklbTitle(cl) {
|
|
|
2863
2960
|
//#endregion
|
|
2864
2961
|
//#region shared/typescript/checklist/to-hdf.ts
|
|
2865
2962
|
const CONVERTER_VERSION = "1.0.0";
|
|
2963
|
+
const VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
2964
|
+
"critical",
|
|
2965
|
+
"high",
|
|
2966
|
+
"medium",
|
|
2967
|
+
"low",
|
|
2968
|
+
"informational"
|
|
2969
|
+
]);
|
|
2866
2970
|
/**
|
|
2867
2971
|
* Map the format-neutral Checklist model to an HDF Results object.
|
|
2868
2972
|
* controlType is derived per-Vuln from CCI->NIST; verificationMethod and
|
|
@@ -2934,7 +3038,7 @@ function vulnToRequirement(v, scanTime) {
|
|
|
2934
3038
|
if (v.legacyIDs && v.legacyIDs.length) tags["legacy_ids"] = v.legacyIDs;
|
|
2935
3039
|
if (v.extra && Object.keys(v.extra).length > 0) tags["cklMetadata"] = { ...v.extra };
|
|
2936
3040
|
const req = createRequirement(v.vulnNum, v.ruleTitle ?? v.vulnNum, descriptions, impact, [result], { tags });
|
|
2937
|
-
if (severity) req.severity = severity;
|
|
3041
|
+
if (VALID_SEVERITIES.has(severity)) req.severity = severity;
|
|
2938
3042
|
const controlType = deriveControlTypeFromTags(nistTags);
|
|
2939
3043
|
if (controlType !== void 0) req.controlType = controlType;
|
|
2940
3044
|
return req;
|
|
@@ -2985,6 +3089,36 @@ function setIf$1(m, key, val) {
|
|
|
2985
3089
|
if (val) m[key] = val;
|
|
2986
3090
|
}
|
|
2987
3091
|
//#endregion
|
|
3092
|
+
//#region shared/typescript/status.ts
|
|
3093
|
+
/**
|
|
3094
|
+
* Bridge from schema-typed requirements onto the canonical effective-status
|
|
3095
|
+
* helper in @mitre/hdf-utilities, so every consumer computes status through
|
|
3096
|
+
* the single shared implementation (twin of shared/go/status.go). The stored
|
|
3097
|
+
* effectiveStatus field is never read — it is an output cache (see
|
|
3098
|
+
* status-determination.md).
|
|
3099
|
+
*/
|
|
3100
|
+
/** RFC3339 string from a schema timestamp (quicktype Date or raw string). */
|
|
3101
|
+
function stamp(v) {
|
|
3102
|
+
if (v instanceof Date) return v.toISOString();
|
|
3103
|
+
if (typeof v === "string" && v !== "") return v;
|
|
3104
|
+
}
|
|
3105
|
+
/** Maps a requirement onto the canonical effective-status input shape. */
|
|
3106
|
+
function requirementStatusInput(req) {
|
|
3107
|
+
return {
|
|
3108
|
+
impact: req.impact,
|
|
3109
|
+
resultStatuses: (req.results ?? []).map((r) => String(r.status)),
|
|
3110
|
+
overrides: (req.statusOverrides ?? []).map((o) => ({
|
|
3111
|
+
status: o.status ? String(o.status) : void 0,
|
|
3112
|
+
appliedAt: stamp(o.appliedAt),
|
|
3113
|
+
expiresAt: stamp(o.expiresAt)
|
|
3114
|
+
}))
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
/** The requirement's canonical effective status via the shared ladder. */
|
|
3118
|
+
function requirementEffectiveStatus(req) {
|
|
3119
|
+
return computeEffectiveStatus(requirementStatusInput(req));
|
|
3120
|
+
}
|
|
3121
|
+
//#endregion
|
|
2988
3122
|
//#region shared/typescript/checklist/from-hdf.ts
|
|
2989
3123
|
/**
|
|
2990
3124
|
* Map HDF Results back to the format-neutral Checklist model. When the HDF
|
|
@@ -3077,7 +3211,7 @@ function requirementToVuln(req) {
|
|
|
3077
3211
|
};
|
|
3078
3212
|
}
|
|
3079
3213
|
function effectiveOrRawStatus(req) {
|
|
3080
|
-
return req
|
|
3214
|
+
return requirementEffectiveStatus(req);
|
|
3081
3215
|
}
|
|
3082
3216
|
function composeFindingDetails(req) {
|
|
3083
3217
|
const results = req.results ?? [];
|
|
@@ -3252,6 +3386,527 @@ function convertHdfToCklb(input) {
|
|
|
3252
3386
|
return serializeCklb(hdfToChecklist(input));
|
|
3253
3387
|
}
|
|
3254
3388
|
//#endregion
|
|
3389
|
+
//#region converters/kics-to-hdf/typescript/mapping-data.ts
|
|
3390
|
+
const kicsMappingData = {};
|
|
3391
|
+
//#endregion
|
|
3392
|
+
//#region converters/kics-to-hdf/typescript/converter.ts
|
|
3393
|
+
/**
|
|
3394
|
+
* KICS publishes five severities. SARIF collapses CRITICAL and HIGH into
|
|
3395
|
+
* `error`; converting the native format keeps them apart, which is a large part
|
|
3396
|
+
* of why this converter exists. The shared standard map covers four of them,
|
|
3397
|
+
* with INFO at the canonical 0.0 tier like every other converter: the
|
|
3398
|
+
* effective-status layer maps impact-0 requirements to notApplicable, so
|
|
3399
|
+
* info-tier findings stay visible in the output without entering the
|
|
3400
|
+
* compliance ratio. TRACE is KICS-specific and joins the info tier as an
|
|
3401
|
+
* alias — the Go peer of this table is kicsSeverityAliases.
|
|
3402
|
+
*/
|
|
3403
|
+
const KICS_SEVERITY_ALIASES = { trace: 0 };
|
|
3404
|
+
const DEFAULT_IMPACT$1 = .5;
|
|
3405
|
+
/**
|
|
3406
|
+
* Records which tier resolved a requirement's NIST tags, so a reviewed mapping
|
|
3407
|
+
* stays distinguishable from a CWE-derived guess and from a static default.
|
|
3408
|
+
* Follows the precedent set by UNRATED_SEVERITY_TAG in converterutil, which
|
|
3409
|
+
* exists so a defaulted impact stays distinguishable from a genuine rated one.
|
|
3410
|
+
*
|
|
3411
|
+
* The per-query table is authoritative, matching how the Checkov and AWS Config
|
|
3412
|
+
* mappers in the v1 line work: the rule-to-control decision is reviewed before
|
|
3413
|
+
* shipping rather than computed at conversion time, which is also what lets
|
|
3414
|
+
* those tables carry control enhancements. CWE is a fallback because it is a
|
|
3415
|
+
* lossy proxy — only 30 of the 102 CWEs KICS uses resolve against the CWE→NIST
|
|
3416
|
+
* table, 52% of queries by volume.
|
|
3417
|
+
*/
|
|
3418
|
+
const NIST_MAPPING_TAG = "nistMapping";
|
|
3419
|
+
const NIST_MAPPING_TABLE = "mapped";
|
|
3420
|
+
const NIST_MAPPING_CWE = "cwe-derived";
|
|
3421
|
+
const NIST_MAPPING_FALLBACK = "static-fallback";
|
|
3422
|
+
function impactFor$1(severity) {
|
|
3423
|
+
return severityToImpactWithAliases(severity, KICS_SEVERITY_ALIASES, DEFAULT_IMPACT$1);
|
|
3424
|
+
}
|
|
3425
|
+
/** KICS emits the bare number, e.g. "778". Normalize before the lookup. */
|
|
3426
|
+
function cweIdentifiers(query) {
|
|
3427
|
+
const raw = query.cwe;
|
|
3428
|
+
if (raw === void 0 || raw === null || String(raw).trim() === "") return [];
|
|
3429
|
+
const id = String(raw).trim().replace(/^CWE-/i, "");
|
|
3430
|
+
return /^\d+$/.test(id) ? [`CWE-${id}`] : [];
|
|
3431
|
+
}
|
|
3432
|
+
function locationFor(file) {
|
|
3433
|
+
const parts = [`File: ${file.file_name || "unknown"}`];
|
|
3434
|
+
if (typeof file.line === "number" && file.line > 0) parts.push(`Line: ${file.line}`);
|
|
3435
|
+
if (file.resource_type) parts.push(`Resource type: ${file.resource_type}`);
|
|
3436
|
+
if (file.resource_name && file.resource_name !== "unknown") parts.push(`Resource: ${file.resource_name}`);
|
|
3437
|
+
if (file.search_key) parts.push(`Key: ${file.search_key}`);
|
|
3438
|
+
if (file.similarity_id) parts.push(`Similarity ID: ${file.similarity_id}`);
|
|
3439
|
+
return parts.join("\n");
|
|
3440
|
+
}
|
|
3441
|
+
/**
|
|
3442
|
+
* The remediation pair. SARIF keeps only `actual_value` inside its message, so
|
|
3443
|
+
* a SARIF-derived control can say what the configuration is but never what it
|
|
3444
|
+
* should be.
|
|
3445
|
+
*/
|
|
3446
|
+
function evidenceFor(file) {
|
|
3447
|
+
const parts = [];
|
|
3448
|
+
if (file.expected_value) parts.push(`Expected: ${file.expected_value}`);
|
|
3449
|
+
if (file.actual_value) parts.push(`Actual: ${file.actual_value}`);
|
|
3450
|
+
if (file.issue_type) parts.push(`Issue type: ${file.issue_type}`);
|
|
3451
|
+
if (file.search_value) parts.push(`Search value: ${file.search_value}`);
|
|
3452
|
+
return parts.join("\n");
|
|
3453
|
+
}
|
|
3454
|
+
/**
|
|
3455
|
+
* Resolution order: the reviewed per-query table, then the query's CWE, then the
|
|
3456
|
+
* static-analysis defaults. `table` is a parameter so all three tiers are
|
|
3457
|
+
* testable without shipping unreviewed rows in the table itself.
|
|
3458
|
+
*/
|
|
3459
|
+
function resolveControls(query, table = kicsMappingData) {
|
|
3460
|
+
const entry = typeof query.query_id === "string" && Object.prototype.hasOwnProperty.call(table, query.query_id) ? table[query.query_id] : void 0;
|
|
3461
|
+
if (entry !== void 0 && entry.nist.length > 0) return {
|
|
3462
|
+
nist: entry.nist,
|
|
3463
|
+
cci: entry.cci,
|
|
3464
|
+
source: NIST_MAPPING_TABLE
|
|
3465
|
+
};
|
|
3466
|
+
const fromCwe = mapCWEToNIST(cweIdentifiers(query), []);
|
|
3467
|
+
if (fromCwe.length > 0) return {
|
|
3468
|
+
nist: fromCwe,
|
|
3469
|
+
cci: nistToCci(fromCwe),
|
|
3470
|
+
source: NIST_MAPPING_CWE
|
|
3471
|
+
};
|
|
3472
|
+
const fallback = [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS];
|
|
3473
|
+
return {
|
|
3474
|
+
nist: fallback,
|
|
3475
|
+
cci: nistToCci(fallback),
|
|
3476
|
+
source: NIST_MAPPING_FALLBACK
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
function tagsFor$1(query) {
|
|
3480
|
+
const cwe = cweIdentifiers(query);
|
|
3481
|
+
const { nist, cci, source } = resolveControls(query);
|
|
3482
|
+
const extras = { [NIST_MAPPING_TAG]: source };
|
|
3483
|
+
if (cwe.length > 0) extras.cwe = cwe;
|
|
3484
|
+
if (query.severity) extras.severity = query.severity;
|
|
3485
|
+
if (query.platform) extras.platform = query.platform;
|
|
3486
|
+
if (query.cloud_provider) extras.cloudProvider = query.cloud_provider;
|
|
3487
|
+
if (query.category) extras.category = query.category;
|
|
3488
|
+
if (query.query_url) extras.queryUrl = query.query_url;
|
|
3489
|
+
if (query.risk_score != null) extras.riskScore = String(query.risk_score);
|
|
3490
|
+
if (query.description_id) extras.descriptionId = query.description_id;
|
|
3491
|
+
if (query.experimental) extras.experimental = true;
|
|
3492
|
+
const issueTypes = [...new Set((query.files ?? []).map((f) => f.issue_type).filter(Boolean))];
|
|
3493
|
+
if (issueTypes.length > 0) extras.issueType = issueTypes;
|
|
3494
|
+
const resourceTypes = [...new Set((query.files ?? []).map((f) => f.resource_type).filter(Boolean))];
|
|
3495
|
+
if (resourceTypes.length > 0) extras.resourceType = resourceTypes;
|
|
3496
|
+
markUnratedSeverity(extras, query.severity);
|
|
3497
|
+
return buildNistCciTags(nist, cci, extras);
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* KICS reports violations only. Its output carries no record of the queries
|
|
3501
|
+
* that ran without finding anything — `queries[]` contains only those that
|
|
3502
|
+
* fired — so no passing requirement can be derived from it, and a converted
|
|
3503
|
+
* profile is failures-only by construction.
|
|
3504
|
+
*
|
|
3505
|
+
* That makes the compliance percentage misleading on its own: a scan where 72
|
|
3506
|
+
* of 2,034 queries fired renders as 100% failed. This requirement carries the
|
|
3507
|
+
* denominator so the ratio is legible.
|
|
3508
|
+
*
|
|
3509
|
+
* Impact 0 and status notApplicable both keep it out of the score: the
|
|
3510
|
+
* effective-status layer (computeEffectiveStatus) maps impact-0 requirements
|
|
3511
|
+
* to notApplicable before statuses are counted, and notApplicable is the one
|
|
3512
|
+
* status the compliance rollup excludes from both numerator and denominator.
|
|
3513
|
+
* Emitting notApplicable as the raw status too keeps raw-status consumers
|
|
3514
|
+
* agreeing with the effective view — Passed would export to CKL as
|
|
3515
|
+
* NotAFinding, a compliant-looking control that was never checked, and count
|
|
3516
|
+
* as a free pass in raw status rollups.
|
|
3517
|
+
*/
|
|
3518
|
+
function buildCoverageRequirement$1(report, startTime) {
|
|
3519
|
+
const executed = report.queries_total ?? 0;
|
|
3520
|
+
const fired = (report.queries ?? []).filter((q) => (q.files ?? []).length > 0).length;
|
|
3521
|
+
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.`;
|
|
3522
|
+
return {
|
|
3523
|
+
id: "kics-scan-coverage",
|
|
3524
|
+
title: "KICS scan coverage",
|
|
3525
|
+
impact: 0,
|
|
3526
|
+
descriptions: [{
|
|
3527
|
+
label: "default",
|
|
3528
|
+
data: summary
|
|
3529
|
+
}],
|
|
3530
|
+
results: [{
|
|
3531
|
+
status: ResultStatus.NotApplicable,
|
|
3532
|
+
codeDesc: summary,
|
|
3533
|
+
startTime
|
|
3534
|
+
}],
|
|
3535
|
+
tags: {
|
|
3536
|
+
queriesExecuted: executed,
|
|
3537
|
+
queriesWithFindings: fired,
|
|
3538
|
+
filesScanned: report.files_scanned ?? 0,
|
|
3539
|
+
filesParsed: report.files_parsed ?? 0,
|
|
3540
|
+
filesFailedToScan: report.files_failed_to_scan ?? 0,
|
|
3541
|
+
queriesFailedToExecute: report.queries_failed_to_execute ?? 0
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
function buildRequirement$18(query, startTime) {
|
|
3546
|
+
const tags = tagsFor$1(query);
|
|
3547
|
+
const descriptions = [{
|
|
3548
|
+
label: "default",
|
|
3549
|
+
data: query.description ?? ""
|
|
3550
|
+
}];
|
|
3551
|
+
const results = (query.files ?? []).map((file) => ({
|
|
3552
|
+
status: ResultStatus.Failed,
|
|
3553
|
+
codeDesc: locationFor(file),
|
|
3554
|
+
message: evidenceFor(file),
|
|
3555
|
+
startTime
|
|
3556
|
+
}));
|
|
3557
|
+
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 });
|
|
3558
|
+
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3559
|
+
const controlType = deriveControlTypeFromTags(tags.nist);
|
|
3560
|
+
if (controlType !== void 0) requirement.controlType = controlType;
|
|
3561
|
+
return requirement;
|
|
3562
|
+
}
|
|
3563
|
+
/**
|
|
3564
|
+
* Converts native `kics scan --report-formats json` output to HDF.
|
|
3565
|
+
*
|
|
3566
|
+
* KICS also emits SARIF, which carries its CWE taxonomy properly. What SARIF
|
|
3567
|
+
* drops is the remediation pair (`expected_value`), the add-a-block versus
|
|
3568
|
+
* fix-a-value distinction (`issue_type`), the stable finding fingerprint
|
|
3569
|
+
* (`similarity_id`), and one level of severity granularity.
|
|
3570
|
+
*
|
|
3571
|
+
* @param input - KICS JSON report string
|
|
3572
|
+
* @returns HDF JSON string
|
|
3573
|
+
*/
|
|
3574
|
+
async function convertKicsToHdf(input, converterVersion = "1.0.0") {
|
|
3575
|
+
validateInputSize(input, "kics");
|
|
3576
|
+
if (!input || input.trim().length === 0) throw new Error("kics: empty input");
|
|
3577
|
+
const report = parseJSON(input);
|
|
3578
|
+
if (!Array.isArray(report.queries) || typeof report.kics_version !== "string") throw new Error("kics: input does not look like a KICS report");
|
|
3579
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
3580
|
+
const requirements = report.queries.filter((q) => Array.isArray(q.files) && q.files.length > 0).map((q) => buildRequirement$18(q, startTime));
|
|
3581
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("kics-no-findings", `KICS scanned ${report.files_scanned ?? 0} file(s) and reported no findings.`, startTime));
|
|
3582
|
+
requirements.push(buildCoverageRequirement$1(report, startTime));
|
|
3583
|
+
const baseline = createMinimalBaseline("KICS Scan", requirements, {
|
|
3584
|
+
title: "KICS infrastructure-as-code scan",
|
|
3585
|
+
resultsChecksum: await inputChecksum(input)
|
|
3586
|
+
});
|
|
3587
|
+
return buildHdfResults({
|
|
3588
|
+
generatorName: "kics-to-hdf",
|
|
3589
|
+
converterVersion,
|
|
3590
|
+
toolName: "KICS",
|
|
3591
|
+
toolVersion: report.kics_version,
|
|
3592
|
+
toolFormat: "json",
|
|
3593
|
+
baselines: [baseline],
|
|
3594
|
+
timestamp: startTime
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
//#endregion
|
|
3598
|
+
//#region converters/semgrep-to-hdf/typescript/converter.ts
|
|
3599
|
+
/**
|
|
3600
|
+
* Semgrep's native three-level scale mapped onto the canonical impact tiers,
|
|
3601
|
+
* mirroring the sarif converter's error/warning/note aliases. INFO is
|
|
3602
|
+
* deliberately 0.3 (an actionable low, semgrep's analogue of SARIF 'note'),
|
|
3603
|
+
* not the canonical 0.0 info tier. Supply-chain severities
|
|
3604
|
+
* (critical/high/medium/low) resolve through the shared canonical map.
|
|
3605
|
+
*/
|
|
3606
|
+
const SEMGREP_SEVERITY_ALIASES = {
|
|
3607
|
+
error: .7,
|
|
3608
|
+
warning: .5,
|
|
3609
|
+
info: .3
|
|
3610
|
+
};
|
|
3611
|
+
/**
|
|
3612
|
+
* An unrecognized or absent severity is treated as moderate; the absent case
|
|
3613
|
+
* additionally carries the shared unrated marker so consumers can tell a
|
|
3614
|
+
* defaulted 0.5 from a genuine medium.
|
|
3615
|
+
*/
|
|
3616
|
+
const DEFAULT_IMPACT = .5;
|
|
3617
|
+
/** Fields Semgrep redacts in unauthenticated (OSS) scans. */
|
|
3618
|
+
const REDACTED_PLACEHOLDER = "requires login";
|
|
3619
|
+
const SCAN_ERRORS_ID = "semgrep-scan-errors";
|
|
3620
|
+
const COVERAGE_ID = "semgrep-scan-coverage";
|
|
3621
|
+
function isPresent(value) {
|
|
3622
|
+
return typeof value === "string" && value.length > 0 && value !== REDACTED_PLACEHOLDER;
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Array entries are filtered per-entry (non-string members dropped); a bare
|
|
3626
|
+
* empty string is treated as absent, matching the Go StringOrSlice decoder.
|
|
3627
|
+
*/
|
|
3628
|
+
function normalizeToArray(value) {
|
|
3629
|
+
if (Array.isArray(value)) return value.filter((entry) => typeof entry === "string");
|
|
3630
|
+
return typeof value === "string" && value !== "" ? [value] : [];
|
|
3631
|
+
}
|
|
3632
|
+
function lineOf(position) {
|
|
3633
|
+
const line = position?.line;
|
|
3634
|
+
return typeof line === "number" && line > 0 ? line : void 0;
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Semgrep emits CWEs in prose form -- 'CWE-89: Improper Neutralization of ...'
|
|
3638
|
+
* -- while the mapping keys on the bare number.
|
|
3639
|
+
*/
|
|
3640
|
+
function extractCweIds(metadata) {
|
|
3641
|
+
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));
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* The parsed CWE ids in the CWE-N catalog form the schema's cwe[] field asks
|
|
3645
|
+
* for, deduplicated in source order.
|
|
3646
|
+
*/
|
|
3647
|
+
function cweCatalogFor(metadata) {
|
|
3648
|
+
return [...new Set(extractCweIds(metadata).map((id) => `CWE-${id}`))];
|
|
3649
|
+
}
|
|
3650
|
+
function nistControlsFor(metadata) {
|
|
3651
|
+
const controls = extractCweIds(metadata).map((cweId) => getCweNistControl(cweId)).filter(isPresent);
|
|
3652
|
+
const deduped = [...new Set(controls)];
|
|
3653
|
+
return deduped.length > 0 ? deduped : [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1];
|
|
3654
|
+
}
|
|
3655
|
+
function impactFor(result) {
|
|
3656
|
+
const severity = result.extra?.severity;
|
|
3657
|
+
if (!isPresent(severity)) return DEFAULT_IMPACT;
|
|
3658
|
+
return severityToImpactWithAliases(severity, SEMGREP_SEVERITY_ALIASES, DEFAULT_IMPACT);
|
|
3659
|
+
}
|
|
3660
|
+
/**
|
|
3661
|
+
* Semgrep rule ids are dotted paths whose final segment is the rule name. The
|
|
3662
|
+
* JSON output carries no human-readable rule title anywhere -- unlike the SARIF
|
|
3663
|
+
* output, whose rule objects have `name` and `shortDescription` -- so one is
|
|
3664
|
+
* derived. Separator runs collapse and only the first character of each word
|
|
3665
|
+
* is uppercased, matching the Go twin exactly.
|
|
3666
|
+
*/
|
|
3667
|
+
function titleFor(checkId) {
|
|
3668
|
+
const segments = checkId.split(".").filter((segment) => segment.length > 0);
|
|
3669
|
+
return (segments[segments.length - 1] ?? checkId).split(/[-_]+/).filter((word) => word.length > 0).map((word) => word[0].toUpperCase() + word.slice(1)).join(" ");
|
|
3670
|
+
}
|
|
3671
|
+
function codeDescFor(result) {
|
|
3672
|
+
const path = isPresent(result.path) ? result.path : "unknown";
|
|
3673
|
+
const startLine = lineOf(result.start);
|
|
3674
|
+
const endLine = lineOf(result.end);
|
|
3675
|
+
if (startLine === void 0) return `Path: ${path}`;
|
|
3676
|
+
return `Path: ${path}, ${endLine === void 0 || endLine === startLine ? `line ${startLine}` : `lines ${startLine}-${endLine}`}`;
|
|
3677
|
+
}
|
|
3678
|
+
/**
|
|
3679
|
+
* Points at the first occurrence of the rule; per-occurrence locations remain
|
|
3680
|
+
* on each result's codeDesc.
|
|
3681
|
+
*/
|
|
3682
|
+
function sourceLocationFor(result) {
|
|
3683
|
+
if (!isPresent(result.path)) return;
|
|
3684
|
+
const location = { ref: result.path };
|
|
3685
|
+
const line = lineOf(result.start);
|
|
3686
|
+
if (line !== void 0) location.line = line;
|
|
3687
|
+
return location;
|
|
3688
|
+
}
|
|
3689
|
+
function messageFor(result) {
|
|
3690
|
+
const parts = [];
|
|
3691
|
+
if (isPresent(result.extra?.lines)) parts.push(`Matched code:\n${result.extra.lines}`);
|
|
3692
|
+
if (isPresent(result.extra?.fix)) parts.push(`Suggested fix -- replace the matched code with:\n${result.extra.fix}`);
|
|
3693
|
+
return parts.join("\n\n");
|
|
3694
|
+
}
|
|
3695
|
+
/** Documentation and cross-framework links, deduplicated. */
|
|
3696
|
+
function referencesFor(metadata) {
|
|
3697
|
+
const urls = [
|
|
3698
|
+
...normalizeToArray(metadata.references),
|
|
3699
|
+
metadata.source,
|
|
3700
|
+
metadata.shortlink,
|
|
3701
|
+
metadata["source-rule-url"],
|
|
3702
|
+
typeof metadata.asvs?.control_url === "string" ? metadata.asvs.control_url : void 0
|
|
3703
|
+
].filter(isPresent);
|
|
3704
|
+
return [...new Set(urls)];
|
|
3705
|
+
}
|
|
3706
|
+
/** The deduplicated reference URLs in refs[], their structured HDF home. */
|
|
3707
|
+
function refsFor(metadata) {
|
|
3708
|
+
return referencesFor(metadata).map((url) => ({ url }));
|
|
3709
|
+
}
|
|
3710
|
+
function asvsOf(metadata) {
|
|
3711
|
+
const asvs = metadata.asvs;
|
|
3712
|
+
if (asvs && typeof asvs === "object" && !Array.isArray(asvs) && Object.keys(asvs).length > 0) return asvs;
|
|
3713
|
+
}
|
|
3714
|
+
function tagsFor(metadata, checkId, severity) {
|
|
3715
|
+
const nist = [...new Set(nistControlsFor(metadata))];
|
|
3716
|
+
const cci = nistToCci(nist);
|
|
3717
|
+
const extras = { checkId };
|
|
3718
|
+
const cwe = normalizeToArray(metadata.cwe);
|
|
3719
|
+
if (cwe.length > 0) extras.cwe = cwe;
|
|
3720
|
+
const owasp = normalizeToArray(metadata.owasp);
|
|
3721
|
+
if (owasp.length > 0) extras.owasp = owasp;
|
|
3722
|
+
const subcategory = normalizeToArray(metadata.subcategory);
|
|
3723
|
+
if (subcategory.length > 0) extras.subcategory = subcategory;
|
|
3724
|
+
const technology = normalizeToArray(metadata.technology);
|
|
3725
|
+
if (technology.length > 0) extras.technology = technology;
|
|
3726
|
+
const vulnerabilityClass = normalizeToArray(metadata.vulnerability_class);
|
|
3727
|
+
if (vulnerabilityClass.length > 0) extras.vulnerabilityClass = vulnerabilityClass;
|
|
3728
|
+
if (isPresent(severity)) extras.severity = severity;
|
|
3729
|
+
if (isPresent(metadata.confidence)) extras.confidence = metadata.confidence;
|
|
3730
|
+
if (isPresent(metadata.likelihood)) extras.likelihood = metadata.likelihood;
|
|
3731
|
+
if (isPresent(metadata.impact)) extras.semgrepImpact = metadata.impact;
|
|
3732
|
+
if (isPresent(metadata.category)) extras.category = metadata.category;
|
|
3733
|
+
if (isPresent(metadata["bandit-code"])) extras.banditCode = metadata["bandit-code"];
|
|
3734
|
+
const asvs = asvsOf(metadata);
|
|
3735
|
+
if (asvs) extras.asvs = asvs;
|
|
3736
|
+
markUnratedSeverity(extras, isPresent(severity) ? severity : "");
|
|
3737
|
+
return buildNistCciTags(nist, cci, extras);
|
|
3738
|
+
}
|
|
3739
|
+
function codePositionFor(position) {
|
|
3740
|
+
const line = lineOf(position);
|
|
3741
|
+
if (line === void 0) return;
|
|
3742
|
+
const out = { line };
|
|
3743
|
+
const col = position?.col;
|
|
3744
|
+
if (typeof col === "number" && col > 0) out.col = col;
|
|
3745
|
+
return out;
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Serializes the curated match envelope into requirement.code for the CODE
|
|
3749
|
+
* tab: the rule source itself is not present in semgrep's JSON output, and the
|
|
3750
|
+
* raw finding bytes are not byte-stable across the Go/TS pair (escape forms
|
|
3751
|
+
* differ), so both languages serialize this envelope field-for-field in the
|
|
3752
|
+
* same order. Rule metadata is deliberately excluded — it is already carried
|
|
3753
|
+
* structurally in tags, cwe[], and refs[] — and redacted fields are filtered
|
|
3754
|
+
* per the converter's redaction policy.
|
|
3755
|
+
*/
|
|
3756
|
+
function codeFor(result) {
|
|
3757
|
+
const envelope = { check_id: result.check_id };
|
|
3758
|
+
if (isPresent(result.path)) envelope.path = result.path;
|
|
3759
|
+
const start = codePositionFor(result.start);
|
|
3760
|
+
if (start) envelope.start = start;
|
|
3761
|
+
const end = codePositionFor(result.end);
|
|
3762
|
+
if (end) envelope.end = end;
|
|
3763
|
+
const extra = {};
|
|
3764
|
+
if (isPresent(result.extra?.message)) extra.message = result.extra.message;
|
|
3765
|
+
if (isPresent(result.extra?.severity)) extra.severity = result.extra.severity;
|
|
3766
|
+
if (isPresent(result.extra?.lines)) extra.lines = result.extra.lines;
|
|
3767
|
+
if (isPresent(result.extra?.fix)) extra.fix = result.extra.fix;
|
|
3768
|
+
if (Object.keys(extra).length > 0) envelope.extra = extra;
|
|
3769
|
+
return JSON.stringify(envelope, null, 2);
|
|
3770
|
+
}
|
|
3771
|
+
/** Every occurrence of one rule becomes a result under a single requirement. */
|
|
3772
|
+
function buildRequirement$17(checkId, results, startTime) {
|
|
3773
|
+
const representative = results[0];
|
|
3774
|
+
const metadata = representative.extra?.metadata ?? {};
|
|
3775
|
+
const message = representative.extra?.message;
|
|
3776
|
+
const descriptions = [{
|
|
3777
|
+
label: "default",
|
|
3778
|
+
data: typeof message === "string" ? message : ""
|
|
3779
|
+
}];
|
|
3780
|
+
const requirementResults = results.map((result) => ({
|
|
3781
|
+
status: ResultStatus.Failed,
|
|
3782
|
+
codeDesc: codeDescFor(result),
|
|
3783
|
+
message: messageFor(result),
|
|
3784
|
+
startTime
|
|
3785
|
+
}));
|
|
3786
|
+
const tags = tagsFor(metadata, checkId, representative.extra?.severity);
|
|
3787
|
+
const cwe = cweCatalogFor(metadata);
|
|
3788
|
+
const refs = refsFor(metadata);
|
|
3789
|
+
const sourceLocation = sourceLocationFor(representative);
|
|
3790
|
+
const requirement = createRequirement(checkId, titleFor(checkId), descriptions, impactFor(representative), requirementResults, {
|
|
3791
|
+
tags,
|
|
3792
|
+
code: codeFor(representative),
|
|
3793
|
+
...cwe.length > 0 ? { cwe } : {},
|
|
3794
|
+
...refs.length > 0 ? { refs } : {},
|
|
3795
|
+
...sourceLocation ? { sourceLocation } : {}
|
|
3796
|
+
});
|
|
3797
|
+
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3798
|
+
const controlType = deriveControlTypeFromTags(tags.nist);
|
|
3799
|
+
if (controlType !== void 0) requirement.controlType = controlType;
|
|
3800
|
+
return requirement;
|
|
3801
|
+
}
|
|
3802
|
+
/**
|
|
3803
|
+
* Scan failures become their own requirement so a file that failed to parse is
|
|
3804
|
+
* visible rather than buried: absence of findings in it is not evidence of
|
|
3805
|
+
* compliance. errors[].level drives the status: level 'error' entries are scan
|
|
3806
|
+
* failures (status error), while level 'warn' entries are advisory (e.g.
|
|
3807
|
+
* PartialParsing — the file was partially analyzed), a genuine non-evaluation
|
|
3808
|
+
* of those paths that must not dominate worst-wins rollups, so they map to
|
|
3809
|
+
* notReviewed.
|
|
3810
|
+
*/
|
|
3811
|
+
function buildErrorsRequirement(errors, startTime) {
|
|
3812
|
+
const results = errors.map((error) => {
|
|
3813
|
+
const kind = Array.isArray(error.type) ? String(error.type[0]) : String(error.type ?? "Unknown");
|
|
3814
|
+
const level = isPresent(error.level) ? error.level : "";
|
|
3815
|
+
const message = typeof error.message === "string" ? error.message : "";
|
|
3816
|
+
return {
|
|
3817
|
+
status: level.toLowerCase() === "warn" ? ResultStatus.NotReviewed : ResultStatus.Error,
|
|
3818
|
+
codeDesc: `Path: ${isPresent(error.path) ? error.path : "unknown"}`,
|
|
3819
|
+
message: level === "" ? `${kind}: ${message}` : `${kind} [${level}]: ${message}`,
|
|
3820
|
+
startTime
|
|
3821
|
+
};
|
|
3822
|
+
});
|
|
3823
|
+
const tags = buildNistCciTags([...DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1], []);
|
|
3824
|
+
markUnratedSeverity(tags, "");
|
|
3825
|
+
const requirement = createRequirement(SCAN_ERRORS_ID, "Semgrep scan errors", [{
|
|
3826
|
+
label: "default",
|
|
3827
|
+
data: "Errors reported by Semgrep while scanning. A file that failed to parse was not fully analyzed."
|
|
3828
|
+
}], DEFAULT_IMPACT, results, { tags });
|
|
3829
|
+
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3830
|
+
return requirement;
|
|
3831
|
+
}
|
|
3832
|
+
/**
|
|
3833
|
+
* Records the scan's denominator. Semgrep reports violations only, so a
|
|
3834
|
+
* converted profile is failures-only by construction and its compliance ratio
|
|
3835
|
+
* is not a pass rate; this record carries the scanned count and the caveat.
|
|
3836
|
+
* Impact 0 makes the effective-status layer derive notApplicable — the only
|
|
3837
|
+
* status the compliance rollup excludes — and the raw status matches so
|
|
3838
|
+
* raw-status consumers (and CKL export, where Passed would render NotAFinding)
|
|
3839
|
+
* agree with the effective view. Mirrors kics-scan-coverage.
|
|
3840
|
+
*/
|
|
3841
|
+
function scannedCount(report) {
|
|
3842
|
+
return Array.isArray(report.paths?.scanned) ? report.paths.scanned.length : 0;
|
|
3843
|
+
}
|
|
3844
|
+
function buildCoverageRequirement(report, ruleCount, startTime) {
|
|
3845
|
+
const filesScanned = scannedCount(report);
|
|
3846
|
+
const errorCount = report.errors?.length ?? 0;
|
|
3847
|
+
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.`;
|
|
3848
|
+
return createRequirement(COVERAGE_ID, "Semgrep scan coverage", [{
|
|
3849
|
+
label: "default",
|
|
3850
|
+
data: summary
|
|
3851
|
+
}], 0, [{
|
|
3852
|
+
status: ResultStatus.NotApplicable,
|
|
3853
|
+
codeDesc: summary,
|
|
3854
|
+
startTime
|
|
3855
|
+
}], { tags: {
|
|
3856
|
+
filesScanned,
|
|
3857
|
+
rulesWithFindings: ruleCount,
|
|
3858
|
+
scanErrors: errorCount
|
|
3859
|
+
} });
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Converts native `semgrep scan --json` output to HDF Results. SARIF input is
|
|
3863
|
+
* detected and delegated to the SARIF converter.
|
|
3864
|
+
*
|
|
3865
|
+
* Native JSON is converted here because SARIF keeps the rule metadata only as
|
|
3866
|
+
* untyped prose tags on the rule object and drops impact, likelihood, the ASVS
|
|
3867
|
+
* control mapping, reference URLs and vulnerability_class outright.
|
|
3868
|
+
*
|
|
3869
|
+
* @param input - Semgrep JSON report string
|
|
3870
|
+
* @returns HDF JSON string
|
|
3871
|
+
*/
|
|
3872
|
+
async function convertSemgrepToHdf(input, converterVersion = "1.0.0") {
|
|
3873
|
+
validateInputSize(input, "semgrep");
|
|
3874
|
+
if (!input || input.trim().length === 0) throw new Error("semgrep: empty input");
|
|
3875
|
+
registerAllFingerprints();
|
|
3876
|
+
const detected = detectConverter(input);
|
|
3877
|
+
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
3878
|
+
const report = parseJSON(input);
|
|
3879
|
+
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");
|
|
3880
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
3881
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3882
|
+
for (const result of report.results) {
|
|
3883
|
+
if (typeof result?.check_id !== "string" || result.check_id === "") continue;
|
|
3884
|
+
const existing = groups.get(result.check_id);
|
|
3885
|
+
if (existing) existing.push(result);
|
|
3886
|
+
else groups.set(result.check_id, [result]);
|
|
3887
|
+
}
|
|
3888
|
+
const requirements = [];
|
|
3889
|
+
for (const [checkId, results] of groups) requirements.push(buildRequirement$17(checkId, results, startTime));
|
|
3890
|
+
if (groups.size === 0) {
|
|
3891
|
+
const scanned = scannedCount(report);
|
|
3892
|
+
requirements.push(buildNoFindingsRequirement("semgrep-no-findings", `Semgrep scanned ${scanned} file(s) and reported no findings.`, startTime));
|
|
3893
|
+
}
|
|
3894
|
+
if (report.errors.length > 0) requirements.push(buildErrorsRequirement(report.errors, startTime));
|
|
3895
|
+
requirements.push(buildCoverageRequirement(report, groups.size, startTime));
|
|
3896
|
+
const baseline = createMinimalBaseline("Semgrep Scan", requirements, {
|
|
3897
|
+
title: "Semgrep static analysis scan",
|
|
3898
|
+
resultsChecksum: await inputChecksum(input)
|
|
3899
|
+
});
|
|
3900
|
+
return buildHdfResults({
|
|
3901
|
+
generatorName: "semgrep-to-hdf",
|
|
3902
|
+
converterVersion,
|
|
3903
|
+
toolName: "Semgrep",
|
|
3904
|
+
toolVersion: typeof report.version === "string" ? report.version : void 0,
|
|
3905
|
+
baselines: [baseline],
|
|
3906
|
+
timestamp: startTime
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3909
|
+
//#endregion
|
|
3255
3910
|
//#region converters/snyk-to-hdf/typescript/converter.ts
|
|
3256
3911
|
/**
|
|
3257
3912
|
* Re-serialize the parsed vulnerability into indented JSON for requirement.code
|
|
@@ -3418,6 +4073,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
3418
4073
|
};
|
|
3419
4074
|
if (rep.identifiers.CVE && rep.identifiers.CVE.length > 0) tags["cve"] = rep.identifiers.CVE;
|
|
3420
4075
|
if (rep.identifiers.GHSA && rep.identifiers.GHSA.length > 0) tags["ghsaid"] = rep.identifiers.GHSA;
|
|
4076
|
+
markUnratedSeverity(tags, rep.severity);
|
|
3421
4077
|
const descriptions = [{
|
|
3422
4078
|
label: "default",
|
|
3423
4079
|
data: rep.description
|
|
@@ -3435,7 +4091,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
3435
4091
|
delete result.message;
|
|
3436
4092
|
return result;
|
|
3437
4093
|
});
|
|
3438
|
-
const req = createRequirement(vulnID, rep.title, descriptions, severityToImpact$1(rep.severity), results, { tags });
|
|
4094
|
+
const req = createRequirement(vulnID, rep.title, descriptions, severityToImpact$1(rep.severity ?? ""), results, { tags });
|
|
3439
4095
|
const controlType = deriveControlTypeFromTags(nist);
|
|
3440
4096
|
if (controlType !== void 0) req.controlType = controlType;
|
|
3441
4097
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
@@ -3450,7 +4106,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
3450
4106
|
const version = rep.version;
|
|
3451
4107
|
if (name && version) {
|
|
3452
4108
|
const ecosystem = ecosystemFromSnykPackageManager(packageManager);
|
|
3453
|
-
const pkg = buildAffectedPackage$
|
|
4109
|
+
const pkg = buildAffectedPackage$2({
|
|
3454
4110
|
name,
|
|
3455
4111
|
version,
|
|
3456
4112
|
ecosystem,
|
|
@@ -3531,17 +4187,12 @@ async function convertSnykToHdf(input, converterVersion = "1.0.0") {
|
|
|
3531
4187
|
}
|
|
3532
4188
|
//#endregion
|
|
3533
4189
|
//#region converters/grype-to-hdf/typescript/converter.ts
|
|
3534
|
-
const
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
["low", .3],
|
|
3539
|
-
["negligible", 0],
|
|
3540
|
-
["unknown", .5]
|
|
3541
|
-
]);
|
|
4190
|
+
const GRYPE_ALIASES = {
|
|
4191
|
+
critical: .9,
|
|
4192
|
+
negligible: 0
|
|
4193
|
+
};
|
|
3542
4194
|
function getImpact$9(severity) {
|
|
3543
|
-
|
|
3544
|
-
return IMPACT_MAPPING$8.get(severity.toLowerCase()) ?? .5;
|
|
4195
|
+
return severityToImpactWithAliases(severity, GRYPE_ALIASES, .5);
|
|
3545
4196
|
}
|
|
3546
4197
|
function isNegligibleOrUnknown(severity) {
|
|
3547
4198
|
if (!severity) return true;
|
|
@@ -3593,7 +4244,7 @@ function buildCodeDesc$9(match) {
|
|
|
3593
4244
|
}
|
|
3594
4245
|
return parts.join(" | ");
|
|
3595
4246
|
}
|
|
3596
|
-
function buildCvssEntries$
|
|
4247
|
+
function buildCvssEntries$7(vuln) {
|
|
3597
4248
|
if (!vuln.cvss || vuln.cvss.length === 0) return;
|
|
3598
4249
|
const entries = [];
|
|
3599
4250
|
for (const c of vuln.cvss) {
|
|
@@ -3636,10 +4287,10 @@ function buildAffectedPackages(match) {
|
|
|
3636
4287
|
if (fix && fix.state === "fixed" && fix.versions && fix.versions.length > 0 && fix.versions[0]) pkg.fixedInVersion = fix.versions[0];
|
|
3637
4288
|
return [pkg];
|
|
3638
4289
|
}
|
|
3639
|
-
const CWE_ID_PATTERN$
|
|
4290
|
+
const CWE_ID_PATTERN$2 = /^CWE-[1-9]\d*$/;
|
|
3640
4291
|
function extractCwe(raw) {
|
|
3641
4292
|
if (!raw || raw.length === 0) return void 0;
|
|
3642
|
-
const out = raw.filter((c) => CWE_ID_PATTERN$
|
|
4293
|
+
const out = raw.filter((c) => CWE_ID_PATTERN$2.test(c));
|
|
3643
4294
|
return out.length === 0 ? void 0 : out;
|
|
3644
4295
|
}
|
|
3645
4296
|
function buildEpss$3(entries) {
|
|
@@ -3671,7 +4322,6 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3671
4322
|
const refs = getReferences(vuln, match.relatedVulnerabilities);
|
|
3672
4323
|
let status;
|
|
3673
4324
|
if (isIgnored) status = ResultStatus.NotReviewed;
|
|
3674
|
-
else if (isNegligibleOrUnknown(severity)) status = ResultStatus.NotApplicable;
|
|
3675
4325
|
else status = ResultStatus.Failed;
|
|
3676
4326
|
const messageParts = [];
|
|
3677
4327
|
if (isIgnored) messageParts.push("This vulnerability was ignored by configured rules.");
|
|
@@ -3686,6 +4336,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3686
4336
|
startTime
|
|
3687
4337
|
};
|
|
3688
4338
|
const tags = buildNistCciTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS, nistToCci(DEFAULT_STATIC_ANALYSIS_NIST_TAGS));
|
|
4339
|
+
markUnratedSeverity(tags, severity);
|
|
3689
4340
|
const requirement = {
|
|
3690
4341
|
id: isIgnored ? `Grype-Ignored-Match/${cveId}` : `Grype/${cveId}`,
|
|
3691
4342
|
title: `Grype found a vulnerability to ${cveId} in ${targetName}`,
|
|
@@ -3712,7 +4363,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3712
4363
|
};
|
|
3713
4364
|
const controlType = deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
3714
4365
|
if (controlType !== void 0) requirement.controlType = controlType;
|
|
3715
|
-
const cvss = buildCvssEntries$
|
|
4366
|
+
const cvss = buildCvssEntries$7(vuln);
|
|
3716
4367
|
if (cvss) requirement.cvss = cvss;
|
|
3717
4368
|
requirement.affectedPackages = buildAffectedPackages(match);
|
|
3718
4369
|
const cwe = extractCwe(vuln.cwe);
|
|
@@ -3723,7 +4374,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
3723
4374
|
if (kev) requirement.kev = kev;
|
|
3724
4375
|
return requirement;
|
|
3725
4376
|
}
|
|
3726
|
-
function buildComponent$
|
|
4377
|
+
function buildComponent$2(report, targetName) {
|
|
3727
4378
|
const t = report.source?.target;
|
|
3728
4379
|
const firstRepoDigest = t?.repoDigests?.find((d) => d);
|
|
3729
4380
|
const firstTag = t?.tags?.find((tag) => tag);
|
|
@@ -3741,10 +4392,8 @@ function buildComponent$1(report, targetName) {
|
|
|
3741
4392
|
if (image) component.image = image;
|
|
3742
4393
|
if (report.distro?.name) component.osName = report.distro.name;
|
|
3743
4394
|
if (report.distro?.version) component.osVersion = report.distro.version;
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
value: t.manifestDigest.replace(/^sha256:/, "")
|
|
3747
|
-
}];
|
|
4395
|
+
const integrity = digestToChecksums(t.manifestDigest);
|
|
4396
|
+
if (integrity) component.integrity = integrity;
|
|
3748
4397
|
if (t.architecture) component.labels = { architecture: t.architecture };
|
|
3749
4398
|
return component;
|
|
3750
4399
|
}
|
|
@@ -3776,7 +4425,7 @@ async function convertGrypeToHdf(input, converterVersion = "1.0.0") {
|
|
|
3776
4425
|
toolName: "Grype",
|
|
3777
4426
|
toolVersion: grypeData.descriptor?.version,
|
|
3778
4427
|
baselines: [baseline],
|
|
3779
|
-
components: [buildComponent$
|
|
4428
|
+
components: [buildComponent$2(grypeData, targetName)],
|
|
3780
4429
|
timestamp: scanTime ?? void 0
|
|
3781
4430
|
});
|
|
3782
4431
|
}
|
|
@@ -4024,7 +4673,7 @@ async function convertDefectDojoToHdf(input, converterVersion = "0.1.0") {
|
|
|
4024
4673
|
//#region converters/nessus-to-hdf/typescript/converter.ts
|
|
4025
4674
|
const CVE_SOURCE_RE = /^CVE-\d{4}-\d{4,}$/;
|
|
4026
4675
|
const CWE_PATTERN = /CWE[- ]?(\d+)/gi;
|
|
4027
|
-
const
|
|
4676
|
+
const NESSUS_ALIASES = {
|
|
4028
4677
|
"4": .9,
|
|
4029
4678
|
"3": .7,
|
|
4030
4679
|
"i": .7,
|
|
@@ -4038,7 +4687,7 @@ const IMPACT_MAPPING$7 = {
|
|
|
4038
4687
|
* Strip HTML tags from a string
|
|
4039
4688
|
*/
|
|
4040
4689
|
function parseHtml(html) {
|
|
4041
|
-
return html
|
|
4690
|
+
return replaceHtmlTags(html, "").trim();
|
|
4042
4691
|
}
|
|
4043
4692
|
const NAMED_ENTITIES$3 = {
|
|
4044
4693
|
lt: "<",
|
|
@@ -4168,8 +4817,8 @@ function convertReportItemToRequirement(item, host) {
|
|
|
4168
4817
|
const title = isCompliance ? item["compliance-check-name"] || item["pluginName"] : item["pluginName"];
|
|
4169
4818
|
const descriptions = buildDescriptions(item, isCompliance);
|
|
4170
4819
|
const impact = calculateImpact(item, isCompliance);
|
|
4171
|
-
const tags = buildTags$
|
|
4172
|
-
const refs = buildRefs$
|
|
4820
|
+
const tags = buildTags$3(item, isCompliance);
|
|
4821
|
+
const refs = buildRefs$9(item);
|
|
4173
4822
|
const results = [buildResult$1(item, host, isCompliance)];
|
|
4174
4823
|
const code = JSON.stringify(item, null, 2);
|
|
4175
4824
|
const nistTags = tags["nist"];
|
|
@@ -4188,7 +4837,7 @@ function convertReportItemToRequirement(item, host) {
|
|
|
4188
4837
|
if (controlType !== void 0) req.controlType = controlType;
|
|
4189
4838
|
if (verificationMethod !== void 0) req.verificationMethod = verificationMethod;
|
|
4190
4839
|
if (!isCompliance) {
|
|
4191
|
-
const cvssEntries = buildCvssEntries$
|
|
4840
|
+
const cvssEntries = buildCvssEntries$6(item);
|
|
4192
4841
|
if (cvssEntries.length > 0) req.cvss = cvssEntries;
|
|
4193
4842
|
const cweIDs = buildCweIDs(item);
|
|
4194
4843
|
if (cweIDs.length > 0) req.cwe = cweIDs;
|
|
@@ -4202,7 +4851,7 @@ function convertReportItemToRequirement(item, host) {
|
|
|
4202
4851
|
* the schema models cvss as a multi-entry array (Nessus emits one entry per
|
|
4203
4852
|
* item; multi-vendor convergence may yield more).
|
|
4204
4853
|
*/
|
|
4205
|
-
function buildCvssEntries$
|
|
4854
|
+
function buildCvssEntries$6(item) {
|
|
4206
4855
|
const source = (item.cvss_score_source || "").trim();
|
|
4207
4856
|
if (!source || !CVE_SOURCE_RE.test(source)) return [];
|
|
4208
4857
|
const hasV3 = !!(item.cvss3_vector || item.cvss3_base_score);
|
|
@@ -4342,12 +4991,12 @@ function buildDescriptions(item, isCompliance) {
|
|
|
4342
4991
|
}
|
|
4343
4992
|
function calculateImpact(item, isCompliance) {
|
|
4344
4993
|
if (isCompliance && item["compliance-reference"]) {
|
|
4345
|
-
const
|
|
4346
|
-
return
|
|
4994
|
+
const cat = parseComplianceRef(item["compliance-reference"], "CAT")[0];
|
|
4995
|
+
return cat ? severityToImpactWithAliases(cat, NESSUS_ALIASES, .5) : .5;
|
|
4347
4996
|
}
|
|
4348
|
-
return
|
|
4997
|
+
return severityToImpactWithAliases(item["severity"], NESSUS_ALIASES, 0);
|
|
4349
4998
|
}
|
|
4350
|
-
function buildTags$
|
|
4999
|
+
function buildTags$3(item, isCompliance) {
|
|
4351
5000
|
const tags = { rid: isCompliance ? parseComplianceRef(item["compliance-reference"], "Rule-ID").join(",") : item["pluginID"] };
|
|
4352
5001
|
if (isCompliance && item["compliance-reference"]) {
|
|
4353
5002
|
const cciTags = parseComplianceRef(item["compliance-reference"], "CCI");
|
|
@@ -4370,7 +5019,7 @@ function buildTags$2(item, isCompliance) {
|
|
|
4370
5019
|
if (item.cvss_base_score) tags.cvss_base_score = item.cvss_base_score;
|
|
4371
5020
|
return tags;
|
|
4372
5021
|
}
|
|
4373
|
-
function buildRefs$
|
|
5022
|
+
function buildRefs$9(item) {
|
|
4374
5023
|
const refs = [];
|
|
4375
5024
|
if (item.see_also) for (const url of item.see_also.split(/\s+/).filter(Boolean)) refs.push({ url });
|
|
4376
5025
|
return refs.length > 0 ? refs : void 0;
|
|
@@ -4436,31 +5085,29 @@ function isIPAddress(s) {
|
|
|
4436
5085
|
//#endregion
|
|
4437
5086
|
//#region converters/sonarqube-to-hdf/typescript/converter.ts
|
|
4438
5087
|
/**
|
|
4439
|
-
* Deprecated legacy severity to impact
|
|
5088
|
+
* Deprecated legacy severity aliases to impact scores.
|
|
4440
5089
|
* Canonical reference: heimdall2 sonarqube-mapper.ts IMPACT_MAPPING.
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
5090
|
+
* Note: SonarQube "CRITICAL" maps to 0.7, not the standard 1.0; INFO resolves
|
|
5091
|
+
* to 0.0 via the shared standard map.
|
|
5092
|
+
*/
|
|
5093
|
+
const SONARQUBE_ALIASES = {
|
|
5094
|
+
blocker: 1,
|
|
5095
|
+
critical: .7,
|
|
5096
|
+
major: .5,
|
|
5097
|
+
minor: .3
|
|
4448
5098
|
};
|
|
4449
|
-
/**
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
INFO: 0
|
|
4456
|
-
};
|
|
4457
|
-
/** MQR severities ordered weakest to strongest. */
|
|
5099
|
+
/**
|
|
5100
|
+
* The one MQR severity the standard map lacks; HIGH/MEDIUM/LOW/INFO already
|
|
5101
|
+
* resolve to 0.7/0.5/0.3/0.0 via severityToImpactWithAliases.
|
|
5102
|
+
*/
|
|
5103
|
+
const MQR_ALIASES = { blocker: 1 };
|
|
5104
|
+
/** MQR severities ordered weakest to strongest (lowercase keys; Go parity). */
|
|
4458
5105
|
const MQR_SEVERITY_RANK = {
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
5106
|
+
info: 0,
|
|
5107
|
+
low: 1,
|
|
5108
|
+
medium: 2,
|
|
5109
|
+
high: 3,
|
|
5110
|
+
blocker: 4
|
|
4464
5111
|
};
|
|
4465
5112
|
/**
|
|
4466
5113
|
* Which severity axis drove a requirement's severity/impact. Emitted as the
|
|
@@ -4485,17 +5132,18 @@ const byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
|
4485
5132
|
function selectSeverity(issue) {
|
|
4486
5133
|
const impacts = issue.impacts ?? [];
|
|
4487
5134
|
if (impacts.length > 0) {
|
|
4488
|
-
const
|
|
5135
|
+
const rank = (s) => MQR_SEVERITY_RANK[s.toLowerCase()] ?? 0;
|
|
5136
|
+
const worst = impacts.reduce((a, b) => rank(b.severity) > rank(a.severity) ? b : a);
|
|
4489
5137
|
return {
|
|
4490
5138
|
severity: worst.severity,
|
|
4491
5139
|
source: SEVERITY_SOURCE_MQR,
|
|
4492
|
-
impact:
|
|
5140
|
+
impact: severityToImpactWithAliases(worst.severity, MQR_ALIASES, .5)
|
|
4493
5141
|
};
|
|
4494
5142
|
}
|
|
4495
5143
|
return {
|
|
4496
|
-
severity: issue.severity,
|
|
5144
|
+
severity: issue.severity ?? "",
|
|
4497
5145
|
source: SEVERITY_SOURCE_LEGACY,
|
|
4498
|
-
impact:
|
|
5146
|
+
impact: severityToImpactWithAliases(issue.severity, SONARQUBE_ALIASES, .5)
|
|
4499
5147
|
};
|
|
4500
5148
|
}
|
|
4501
5149
|
/**
|
|
@@ -4504,6 +5152,28 @@ function selectSeverity(issue) {
|
|
|
4504
5152
|
* SonarQube is fundamentally a static analysis tool. Matches heimdall2.
|
|
4505
5153
|
*/
|
|
4506
5154
|
const DEFAULT_NIST_TAGS = ["SA-11"];
|
|
5155
|
+
const OWASP_TAG_PATTERN = /owasp[-:]?a0*(\d{1,2})/gi;
|
|
5156
|
+
const OWASP_2017_PATTERN = /top 10 2017[\s\S]{0,30}?category\s*a0*(\d{1,2})/gi;
|
|
5157
|
+
/** Records a normalized OWASP-2017 category (A1..A10) when n is in range. */
|
|
5158
|
+
function addOwaspId(set, n) {
|
|
5159
|
+
if (n === void 0) return;
|
|
5160
|
+
const v = parseInt(n, 10);
|
|
5161
|
+
if (v >= 1 && v <= 10) set.add(`A${v}`);
|
|
5162
|
+
}
|
|
5163
|
+
/**
|
|
5164
|
+
* Combines CWE-derived and OWASP-derived NIST controls. The SA-11 default
|
|
5165
|
+
* applies only when neither source yields a mapping.
|
|
5166
|
+
*/
|
|
5167
|
+
function foldNistControls(cweIds, owaspIds) {
|
|
5168
|
+
const set = /* @__PURE__ */ new Set();
|
|
5169
|
+
for (const c of mapCWEToNIST(cweIds, [])) set.add(c);
|
|
5170
|
+
for (const id of owaspIds) {
|
|
5171
|
+
const ctrl = getOwaspNistControl(id);
|
|
5172
|
+
if (ctrl) for (const c of ctrl.split("|")) set.add(c);
|
|
5173
|
+
}
|
|
5174
|
+
if (set.size === 0) return [...DEFAULT_NIST_TAGS];
|
|
5175
|
+
return Array.from(set).sort();
|
|
5176
|
+
}
|
|
4507
5177
|
/**
|
|
4508
5178
|
* Convert SonarQube issues JSON to HDF format
|
|
4509
5179
|
*
|
|
@@ -4593,8 +5263,8 @@ function convertRuleToRequirement(ruleKey, issues, componentMap, ruleMap) {
|
|
|
4593
5263
|
const description = extractDescription(rule);
|
|
4594
5264
|
const firstIssue = issues[0];
|
|
4595
5265
|
const { severity, source: severitySource, impact } = selectSeverity(firstIssue);
|
|
4596
|
-
const { cweIds,
|
|
4597
|
-
const nistControls =
|
|
5266
|
+
const { cweIds, owaspIds, allTags } = extractTags(rule, issues);
|
|
5267
|
+
const nistControls = foldNistControls(cweIds, owaspIds);
|
|
4598
5268
|
const cciControls = nistToCci(nistControls);
|
|
4599
5269
|
const results = issues.map((issue) => createResultFromIssue(issue, componentMap));
|
|
4600
5270
|
const issueWithLocation = issues.find((i) => i.line !== void 0);
|
|
@@ -4604,7 +5274,7 @@ function convertRuleToRequirement(ruleKey, issues, componentMap, ruleMap) {
|
|
|
4604
5274
|
severitySource,
|
|
4605
5275
|
type: firstIssue.type.toLowerCase(),
|
|
4606
5276
|
cwe: cweIds,
|
|
4607
|
-
owasp:
|
|
5277
|
+
owasp: owaspIds,
|
|
4608
5278
|
nist: nistControls,
|
|
4609
5279
|
cci: cciControls,
|
|
4610
5280
|
...firstIssue.effort ? { effort: firstIssue.effort } : {},
|
|
@@ -4635,11 +5305,11 @@ function convertRuleToRequirement(ruleKey, issues, componentMap, ruleMap) {
|
|
|
4635
5305
|
function extractDescription(rule) {
|
|
4636
5306
|
if (!rule) return "";
|
|
4637
5307
|
if (rule.mdDesc) return rule.mdDesc;
|
|
4638
|
-
if (rule.htmlDesc) return rule.htmlDesc
|
|
5308
|
+
if (rule.htmlDesc) return stripHtml(rule.htmlDesc);
|
|
4639
5309
|
if (rule.descriptionSections && rule.descriptionSections.length > 0) {
|
|
4640
5310
|
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
|
|
5311
|
+
if (rootCause) return stripHtml(rootCause.content);
|
|
5312
|
+
const parts = rule.descriptionSections.map((s) => stripHtml(s.content)).filter((s) => s.length > 0);
|
|
4643
5313
|
if (parts.length > 0) return parts.join("\n\n");
|
|
4644
5314
|
}
|
|
4645
5315
|
return rule.name || "";
|
|
@@ -4653,7 +5323,7 @@ function extractTags(rule, issues) {
|
|
|
4653
5323
|
for (const tag of ruleTags) {
|
|
4654
5324
|
const lowerTag = tag.toLowerCase();
|
|
4655
5325
|
if (lowerTag.startsWith("cwe-") || lowerTag.includes("cwe")) for (const id of extractCWEIDs(tag)) cweSet.add(`CWE-${id}`);
|
|
4656
|
-
|
|
5326
|
+
for (const m of tag.matchAll(OWASP_TAG_PATTERN)) addOwaspId(owaspSet, m[1]);
|
|
4657
5327
|
const parts = tag.split(":");
|
|
4658
5328
|
if (parts.length === 2) {
|
|
4659
5329
|
const [category, value] = parts;
|
|
@@ -4663,20 +5333,24 @@ function extractTags(rule, issues) {
|
|
|
4663
5333
|
}
|
|
4664
5334
|
}
|
|
4665
5335
|
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);
|
|
5336
|
+
if (tag.toLowerCase().startsWith("cwe-")) for (const id of extractCWEIDs(tag)) cweSet.add(`CWE-${id}`);
|
|
5337
|
+
for (const m of tag.matchAll(OWASP_TAG_PATTERN)) addOwaspId(owaspSet, m[1]);
|
|
4669
5338
|
}
|
|
4670
5339
|
if (rule?.htmlDesc || rule?.mdDesc) {
|
|
4671
5340
|
const desc = (rule.htmlDesc || "") + (rule.mdDesc || "");
|
|
4672
5341
|
for (const id of extractCWEIDs(desc)) cweSet.add(`CWE-${id}`);
|
|
4673
5342
|
}
|
|
4674
5343
|
if (rule?.descriptionSections) for (const section of rule.descriptionSections) for (const id of extractCWEIDs(section.content)) cweSet.add(`CWE-${id}`);
|
|
5344
|
+
if (rule) {
|
|
5345
|
+
const texts = [rule.htmlDesc || "", rule.mdDesc || ""];
|
|
5346
|
+
for (const section of rule.descriptionSections ?? []) texts.push(section.content);
|
|
5347
|
+
for (const text of texts) for (const m of text.matchAll(OWASP_2017_PATTERN)) addOwaspId(owaspSet, m[1]);
|
|
5348
|
+
}
|
|
4675
5349
|
const allTags = {};
|
|
4676
5350
|
for (const [category, values] of allTagsMap) allTags[category] = Array.from(values);
|
|
4677
5351
|
return {
|
|
4678
5352
|
cweIds: Array.from(cweSet),
|
|
4679
|
-
|
|
5353
|
+
owaspIds: Array.from(owaspSet).sort((a, b) => parseInt(a.slice(1), 10) - parseInt(b.slice(1), 10)),
|
|
4680
5354
|
allTags
|
|
4681
5355
|
};
|
|
4682
5356
|
}
|
|
@@ -4941,6 +5615,7 @@ function buildRequirement$14(checkId, group, scanTime) {
|
|
|
4941
5615
|
const checkTypes = [...new Set(group.map((c) => c.checkType).filter((t) => t !== ""))].sort();
|
|
4942
5616
|
if (checkTypes.length > 0) tags["check_type"] = checkTypes;
|
|
4943
5617
|
if (rep.bc_check_id) tags["bc_check_id"] = rep.bc_check_id;
|
|
5618
|
+
markUnratedSeverity(tags, rep.severity);
|
|
4944
5619
|
const descriptions = [{
|
|
4945
5620
|
label: "default",
|
|
4946
5621
|
data: rep.check_name
|
|
@@ -5033,14 +5708,6 @@ async function convertCheckovToHdf(input, converterVersion = "1.0.0") {
|
|
|
5033
5708
|
//#endregion
|
|
5034
5709
|
//#region converters/gosec-to-hdf/typescript/converter.ts
|
|
5035
5710
|
/**
|
|
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
5711
|
* Returns the first-class CWE identifiers for an issue in "CWE-N" form,
|
|
5045
5712
|
* or an empty array when the issue carries no CWE.
|
|
5046
5713
|
*/
|
|
@@ -5104,10 +5771,11 @@ function issueToResult(issue, scanTime) {
|
|
|
5104
5771
|
*/
|
|
5105
5772
|
function buildRequirement$13(ruleId, issues, scanTime) {
|
|
5106
5773
|
const rep = issues[0];
|
|
5107
|
-
const impact =
|
|
5774
|
+
const impact = severityToImpact(rep.severity);
|
|
5108
5775
|
const nist = mapCWEToNIST([rep.cwe.id], DEFAULT_REMEDIATION_NIST_TAGS$1);
|
|
5109
5776
|
const tags = { nist };
|
|
5110
5777
|
if (rep.confidence) tags.confidence = rep.confidence;
|
|
5778
|
+
markUnratedSeverity(tags, rep.severity);
|
|
5111
5779
|
const results = issues.map((issue) => issueToResult(issue, scanTime));
|
|
5112
5780
|
const descriptions = [{
|
|
5113
5781
|
label: "default",
|
|
@@ -5357,7 +6025,7 @@ function buildSourceLocation$3(alert) {
|
|
|
5357
6025
|
return { ref: inst.uri };
|
|
5358
6026
|
}
|
|
5359
6027
|
const REF_URL_RE = /https?:\/\/[^\s<>"']+/g;
|
|
5360
|
-
function buildRefs$
|
|
6028
|
+
function buildRefs$8(reference) {
|
|
5361
6029
|
if (!reference) return;
|
|
5362
6030
|
const urls = reference.match(REF_URL_RE);
|
|
5363
6031
|
if (!urls || urls.length === 0) return;
|
|
@@ -5432,7 +6100,7 @@ function buildSiteRequirements(site, startTime) {
|
|
|
5432
6100
|
if (code !== void 0) req.code = code;
|
|
5433
6101
|
const sourceLocation = buildSourceLocation$3(alert);
|
|
5434
6102
|
if (sourceLocation !== void 0) req.sourceLocation = sourceLocation;
|
|
5435
|
-
const refs = buildRefs$
|
|
6103
|
+
const refs = buildRefs$8(alert.reference);
|
|
5436
6104
|
if (refs !== void 0) req.refs = refs;
|
|
5437
6105
|
requirements.push(req);
|
|
5438
6106
|
}
|
|
@@ -6052,13 +6720,7 @@ function stringSlice(v) {
|
|
|
6052
6720
|
* the canonical worst-wins ordering from @mitre/hdf-utilities.
|
|
6053
6721
|
*/
|
|
6054
6722
|
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);
|
|
6723
|
+
return worstStatus(resultStatuses(req));
|
|
6062
6724
|
}
|
|
6063
6725
|
/**
|
|
6064
6726
|
* Whether an HDF Result_Status is a failing verdict. Only 'failed' fails;
|
|
@@ -6069,20 +6731,46 @@ function worstOfResults(req) {
|
|
|
6069
6731
|
function isFailing(status) {
|
|
6070
6732
|
return status === "failed";
|
|
6071
6733
|
}
|
|
6072
|
-
/**
|
|
6734
|
+
/**
|
|
6735
|
+
* Resolve the status context for a requirement via the canonical
|
|
6736
|
+
* effective-status ladder — the stored effectiveStatus field is never read
|
|
6737
|
+
* (see status-determination.md; the field is an output cache).
|
|
6738
|
+
*/
|
|
6073
6739
|
function statusOf(req) {
|
|
6074
6740
|
const raw = worstOfResults(req);
|
|
6075
|
-
const
|
|
6076
|
-
const
|
|
6077
|
-
|
|
6741
|
+
const overrides = asArr(req.statusOverrides) ?? [];
|
|
6742
|
+
const overrideInputs = overrides.flatMap((oRaw) => {
|
|
6743
|
+
const o = asMap(oRaw);
|
|
6744
|
+
if (!o) return [];
|
|
6745
|
+
return [{
|
|
6746
|
+
status: getStr(o, "status") || void 0,
|
|
6747
|
+
appliedAt: getStr(o, "appliedAt") || void 0,
|
|
6748
|
+
expiresAt: getStr(o, "expiresAt") || void 0
|
|
6749
|
+
}];
|
|
6750
|
+
});
|
|
6751
|
+
const rollup = computeEffectiveStatus({
|
|
6752
|
+
impact: typeof req.impact === "number" ? req.impact : 1,
|
|
6753
|
+
resultStatuses: resultStatuses(req),
|
|
6754
|
+
overrides: overrideInputs
|
|
6755
|
+
});
|
|
6756
|
+
const governed = governingStatusOverride(overrideInputs) !== void 0;
|
|
6078
6757
|
return {
|
|
6079
6758
|
raw,
|
|
6080
|
-
effective,
|
|
6081
6759
|
rollup,
|
|
6082
|
-
overridden: overrides
|
|
6083
|
-
suppressed: isFailing(raw) && !isFailing(rollup)
|
|
6760
|
+
overridden: overrides.length > 0,
|
|
6761
|
+
suppressed: governed && isFailing(raw) && !isFailing(rollup)
|
|
6084
6762
|
};
|
|
6085
6763
|
}
|
|
6764
|
+
/** Lists results[].status losslessly for the canonical roll-up. */
|
|
6765
|
+
function resultStatuses(req) {
|
|
6766
|
+
const results = asArr(req.results) ?? [];
|
|
6767
|
+
const statuses = [];
|
|
6768
|
+
for (const rRaw of results) {
|
|
6769
|
+
const r = asMap(rRaw);
|
|
6770
|
+
if (r) statuses.push(getStr(r, "status"));
|
|
6771
|
+
}
|
|
6772
|
+
return statuses;
|
|
6773
|
+
}
|
|
6086
6774
|
function firstComponent(doc) {
|
|
6087
6775
|
const comps = asArr(doc.components);
|
|
6088
6776
|
if (!comps || comps.length === 0) return void 0;
|
|
@@ -6238,10 +6926,7 @@ function stringifyLine(v) {
|
|
|
6238
6926
|
* the missing-baselines error and drives validateInputSize.
|
|
6239
6927
|
*/
|
|
6240
6928
|
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`);
|
|
6929
|
+
const { doc, items: baselines } = requireHdfResults(input, converterName);
|
|
6245
6930
|
const docTimestamp = getStr(doc, "timestamp");
|
|
6246
6931
|
const tool = asMap(doc.tool);
|
|
6247
6932
|
const generator = asMap(doc.generator);
|
|
@@ -6299,6 +6984,19 @@ const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
|
6299
6984
|
"CVSSv4"
|
|
6300
6985
|
]);
|
|
6301
6986
|
/**
|
|
6987
|
+
* Reports whether no rating carries rating information: no CVSS score and no
|
|
6988
|
+
* rated severity token. Covers the empty-ratings default and the fabricated-
|
|
6989
|
+
* "medium" (empty severity, no score) paths, both of which land on impact 0.5
|
|
6990
|
+
* without the source having asserted a severity.
|
|
6991
|
+
*/
|
|
6992
|
+
function vulnIsUnrated(ratings) {
|
|
6993
|
+
for (const rating of ratings) {
|
|
6994
|
+
if (rating.method && CVSS_METHODS.has(rating.method) && rating.score !== void 0 && rating.score !== null) return false;
|
|
6995
|
+
if (!isUnratedSeverity(rating.severity)) return false;
|
|
6996
|
+
}
|
|
6997
|
+
return true;
|
|
6998
|
+
}
|
|
6999
|
+
/**
|
|
6302
7000
|
* Computes the maximum impact across all ratings for a vulnerability.
|
|
6303
7001
|
* Prefers CVSS score/10 when available, falls back to severityToImpact().
|
|
6304
7002
|
*/
|
|
@@ -6328,7 +7026,7 @@ function cvssVersionFromMethod(method, vector) {
|
|
|
6328
7026
|
default: return cvssVersionFromVector(vector);
|
|
6329
7027
|
}
|
|
6330
7028
|
}
|
|
6331
|
-
function buildCvssEntries$
|
|
7029
|
+
function buildCvssEntries$5(ratings) {
|
|
6332
7030
|
const entries = [];
|
|
6333
7031
|
for (const r of ratings) {
|
|
6334
7032
|
const hasCvssMethod = r.method !== void 0 && CVSS_METHODS.has(r.method);
|
|
@@ -6349,7 +7047,7 @@ function buildCvssEntries$4(ratings) {
|
|
|
6349
7047
|
* de-duplicated across all three in first-seen order. Returns undefined when the
|
|
6350
7048
|
* vulnerability carries no links.
|
|
6351
7049
|
*/
|
|
6352
|
-
function buildRefs$
|
|
7050
|
+
function buildRefs$7(vuln) {
|
|
6353
7051
|
const seen = /* @__PURE__ */ new Set();
|
|
6354
7052
|
const refs = [];
|
|
6355
7053
|
const add = (url) => {
|
|
@@ -6633,8 +7331,9 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
|
6633
7331
|
nist,
|
|
6634
7332
|
cci: nistToCci(nist)
|
|
6635
7333
|
};
|
|
7334
|
+
if (vulnIsUnrated(ratings)) markUnratedSeverity(tags, "");
|
|
6636
7335
|
const cweIds = cwes.map((c) => `CWE-${c}`);
|
|
6637
|
-
const cvssEntries = buildCvssEntries$
|
|
7336
|
+
const cvssEntries = buildCvssEntries$5(ratings);
|
|
6638
7337
|
const descriptions = [];
|
|
6639
7338
|
const defaultParts = [];
|
|
6640
7339
|
if (vuln.description) defaultParts.push(`Description: ${vuln.description}`);
|
|
@@ -6667,7 +7366,7 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
|
6667
7366
|
req.code = buildVulnCode$1(vuln);
|
|
6668
7367
|
if (cvssEntries.length > 0) req.cvss = cvssEntries;
|
|
6669
7368
|
if (cweIds.length > 0) req.cwe = cweIds;
|
|
6670
|
-
const refs = buildRefs$
|
|
7369
|
+
const refs = buildRefs$7(vuln);
|
|
6671
7370
|
if (refs !== void 0) req.refs = refs;
|
|
6672
7371
|
const controlType = deriveControlTypeFromTags(nist);
|
|
6673
7372
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -6750,7 +7449,7 @@ function createRow(baseline, requirement, target) {
|
|
|
6750
7449
|
const message = firstResult?.message || "";
|
|
6751
7450
|
const nistControls = extractArrayFromTags(requirement.tags, "nist");
|
|
6752
7451
|
const cciControls = extractArrayFromTags(requirement.tags, "cci");
|
|
6753
|
-
const effectiveStatus =
|
|
7452
|
+
const effectiveStatus = requirementEffectiveStatus(requirement);
|
|
6754
7453
|
const effectiveImpact = (requirement.effectiveImpact ?? requirement.impact).toFixed(1);
|
|
6755
7454
|
const disposition = requirement.disposition ? String(requirement.disposition) : "";
|
|
6756
7455
|
return {
|
|
@@ -7904,7 +8603,7 @@ async function convertSplunkToHdf(input, converterVersion = "1.0.0") {
|
|
|
7904
8603
|
}) : [];
|
|
7905
8604
|
const options = { tags: control.tags ?? {} };
|
|
7906
8605
|
if (control.source_location) options.sourceLocation = control.source_location;
|
|
7907
|
-
const req = createRequirement(control.id, control.title, descriptions, control.impact, results, options);
|
|
8606
|
+
const req = createRequirement(control.id, control.title, descriptions, control.impact ?? 0, results, options);
|
|
7908
8607
|
if (!req.title) delete req.title;
|
|
7909
8608
|
if (control.code) req.code = control.code;
|
|
7910
8609
|
const nistTagsRaw = control.tags?.["nist"];
|
|
@@ -8050,7 +8749,7 @@ function convertHdfToXml(input) {
|
|
|
8050
8749
|
}
|
|
8051
8750
|
//#endregion
|
|
8052
8751
|
//#region converters/gitlab-to-hdf/typescript/converter.ts
|
|
8053
|
-
function buildRefs$
|
|
8752
|
+
function buildRefs$6(vuln) {
|
|
8054
8753
|
const refs = [];
|
|
8055
8754
|
const seen = /* @__PURE__ */ new Set();
|
|
8056
8755
|
const appendUrl = (url) => {
|
|
@@ -8190,6 +8889,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
8190
8889
|
const extras = {};
|
|
8191
8890
|
for (const [key, values] of Object.entries(idTags)) extras[key] = values;
|
|
8192
8891
|
const tags = buildNistCciTags(nistTags, cciTags, Object.keys(extras).length > 0 ? extras : void 0);
|
|
8892
|
+
markUnratedSeverity(tags, vuln.severity);
|
|
8193
8893
|
const descriptions = [];
|
|
8194
8894
|
if (vuln.description) descriptions.push({
|
|
8195
8895
|
label: "default",
|
|
@@ -8224,7 +8924,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
8224
8924
|
descriptions,
|
|
8225
8925
|
verificationMethod: VerificationMethodEnum.Automated
|
|
8226
8926
|
};
|
|
8227
|
-
const refs = buildRefs$
|
|
8927
|
+
const refs = buildRefs$6(vuln);
|
|
8228
8928
|
if (refs) req.refs = refs;
|
|
8229
8929
|
const controlType = deriveControlTypeFromTags(nistTags);
|
|
8230
8930
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -8266,6 +8966,333 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
8266
8966
|
return serializeHdf(hdf);
|
|
8267
8967
|
}
|
|
8268
8968
|
//#endregion
|
|
8969
|
+
//#region converters/trivy-to-hdf/typescript/converter.ts
|
|
8970
|
+
const BASELINE_NAME = "Trivy Scan";
|
|
8971
|
+
const STATIC_CCI = nistToCci(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
8972
|
+
const CWE_ID_PATTERN$1 = /^CWE-[1-9]\d*$/;
|
|
8973
|
+
const IMPACT_MAP = {
|
|
8974
|
+
critical: .9,
|
|
8975
|
+
high: .7,
|
|
8976
|
+
medium: .5,
|
|
8977
|
+
low: .3,
|
|
8978
|
+
unknown: .5,
|
|
8979
|
+
none: 0,
|
|
8980
|
+
info: 0
|
|
8981
|
+
};
|
|
8982
|
+
function severityToImpact$2(severity) {
|
|
8983
|
+
return IMPACT_MAP[(severity || "").toLowerCase()] ?? .5;
|
|
8984
|
+
}
|
|
8985
|
+
/**
|
|
8986
|
+
* Route Trivy output to HDF Results: native Trivy JSON (SchemaVersion 2) is
|
|
8987
|
+
* parsed here; SARIF / CycloneDX / ASFF / GitLab are delegated to their
|
|
8988
|
+
* converters. TypeScript peer of shared/go/../trivy-to-hdf converter.go.
|
|
8989
|
+
*/
|
|
8990
|
+
async function convertTrivyToHdf(input, converterVersion = "1.0.0") {
|
|
8991
|
+
validateInputSize(input, "trivy");
|
|
8992
|
+
if (!input?.trim()) throw new Error("trivy: empty input");
|
|
8993
|
+
const probe = parseJSON(input);
|
|
8994
|
+
if (typeof probe !== "object" || probe === null || Array.isArray(probe)) throw new Error("trivy: input is not a JSON object");
|
|
8995
|
+
if (isNativeTrivy(probe)) return convertNative(input, converterVersion);
|
|
8996
|
+
if (probe.bomFormat === "CycloneDX") return convertCyclonedxToHdf(input, converterVersion);
|
|
8997
|
+
if ("runs" in probe && "version" in probe) return convertSarifToHdf(input, converterVersion);
|
|
8998
|
+
if ("Findings" in probe || "ProductArn" in probe) return convertAsffToHdf(input, converterVersion);
|
|
8999
|
+
if ("vulnerabilities" in probe) return convertGitlabToHdf(input, converterVersion);
|
|
9000
|
+
throw new Error("trivy: not a recognized Trivy output format (native JSON, SARIF, CycloneDX, ASFF, or GitLab)");
|
|
9001
|
+
}
|
|
9002
|
+
function isNativeTrivy(m) {
|
|
9003
|
+
return "SchemaVersion" in m && "ArtifactName" in m && "ArtifactType" in m;
|
|
9004
|
+
}
|
|
9005
|
+
async function convertNative(input, converterVersion) {
|
|
9006
|
+
const report = parseJSON(input);
|
|
9007
|
+
const resultsChecksum = await inputChecksum(input);
|
|
9008
|
+
const scanTime = report.CreatedAt ? parseTimestamp(report.CreatedAt) : null;
|
|
9009
|
+
const startTime = scanTime ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
9010
|
+
const requirements = [];
|
|
9011
|
+
for (const res of report.Results ?? []) {
|
|
9012
|
+
for (const v of res.Vulnerabilities ?? []) requirements.push(convertVuln(v, res, startTime));
|
|
9013
|
+
for (const m of res.Misconfigurations ?? []) requirements.push(convertMisconf(m, res, startTime));
|
|
9014
|
+
for (const s of res.Secrets ?? []) requirements.push(convertSecret(s, res, startTime));
|
|
9015
|
+
for (const l of res.Licenses ?? []) requirements.push(convertLicense(l, res, startTime));
|
|
9016
|
+
}
|
|
9017
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("trivy-no-findings", `Trivy scanned ${report.ArtifactName ?? "the target"} and reported zero findings.`, startTime));
|
|
9018
|
+
const baseline = createMinimalBaseline(BASELINE_NAME, requirements, { resultsChecksum });
|
|
9019
|
+
if (report.ArtifactName) baseline.title = report.ArtifactName;
|
|
9020
|
+
const component = buildComponent$1(report);
|
|
9021
|
+
return buildHdfResults({
|
|
9022
|
+
generatorName: "trivy-to-hdf",
|
|
9023
|
+
converterVersion,
|
|
9024
|
+
toolName: "Trivy",
|
|
9025
|
+
toolVersion: report.Trivy?.Version,
|
|
9026
|
+
baselines: [baseline],
|
|
9027
|
+
components: component ? [component] : void 0,
|
|
9028
|
+
timestamp: scanTime ?? void 0
|
|
9029
|
+
});
|
|
9030
|
+
}
|
|
9031
|
+
function convertVuln(v, res, startTime) {
|
|
9032
|
+
const descriptions = [{
|
|
9033
|
+
label: "default",
|
|
9034
|
+
data: firstNonEmpty(v.Description, v.Title, v.VulnerabilityID)
|
|
9035
|
+
}];
|
|
9036
|
+
if (v.FixedVersion) descriptions.push({
|
|
9037
|
+
label: "fix",
|
|
9038
|
+
data: `Fixed in version ${v.FixedVersion}.`
|
|
9039
|
+
});
|
|
9040
|
+
const extras = { class: res.Class };
|
|
9041
|
+
putIf(extras, "trivy_type", res.Type);
|
|
9042
|
+
putIf(extras, "severity_source", v.SeveritySource);
|
|
9043
|
+
putIf(extras, "data_source", v.DataSource?.Name);
|
|
9044
|
+
putIf(extras, "published_date", v.PublishedDate);
|
|
9045
|
+
if (v.VendorSeverity && Object.keys(v.VendorSeverity).length > 0) extras.vendor_severity = v.VendorSeverity;
|
|
9046
|
+
const tags = buildTags$2(extras);
|
|
9047
|
+
markUnratedSeverity(tags, v.Severity);
|
|
9048
|
+
const req = {
|
|
9049
|
+
id: `Trivy/${v.VulnerabilityID ?? ""}`,
|
|
9050
|
+
title: `Trivy found ${v.VulnerabilityID ?? ""} in ${pkgLabel(v.PkgName, v.InstalledVersion)}`,
|
|
9051
|
+
descriptions,
|
|
9052
|
+
impact: severityToImpact$2(v.Severity ?? ""),
|
|
9053
|
+
tags,
|
|
9054
|
+
cwe: filterCwes(v.CweIDs),
|
|
9055
|
+
cvss: buildCvssEntries$4(v.CVSS),
|
|
9056
|
+
refs: buildRefs$5([v.PrimaryURL, ...v.References ?? []]),
|
|
9057
|
+
code: JSON.stringify(v, null, 2),
|
|
9058
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9059
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9060
|
+
results: [{
|
|
9061
|
+
status: ResultStatus.Failed,
|
|
9062
|
+
codeDesc: buildVulnCodeDesc(v, res),
|
|
9063
|
+
startTime,
|
|
9064
|
+
message: `Severity: ${firstNonEmpty(v.Severity, "UNKNOWN")}`
|
|
9065
|
+
}]
|
|
9066
|
+
};
|
|
9067
|
+
const ap = buildAffectedPackage$1(v);
|
|
9068
|
+
if (ap.purl) req.affectedPackages = [ap];
|
|
9069
|
+
if (v.PkgPath) req.sourceLocation = { ref: v.PkgPath };
|
|
9070
|
+
return req;
|
|
9071
|
+
}
|
|
9072
|
+
function convertMisconf(m, res, startTime) {
|
|
9073
|
+
const descriptions = [{
|
|
9074
|
+
label: "default",
|
|
9075
|
+
data: firstNonEmpty(m.Description, m.Message, m.Title)
|
|
9076
|
+
}];
|
|
9077
|
+
if (m.Resolution) descriptions.push({
|
|
9078
|
+
label: "fix",
|
|
9079
|
+
data: m.Resolution
|
|
9080
|
+
});
|
|
9081
|
+
const tags = buildTags$2({
|
|
9082
|
+
class: res.Class,
|
|
9083
|
+
...m.Type ? { misconfig_type: m.Type } : {}
|
|
9084
|
+
});
|
|
9085
|
+
markUnratedSeverity(tags, m.Severity);
|
|
9086
|
+
const req = {
|
|
9087
|
+
id: `Trivy/${firstNonEmpty(m.ID, m.AVDID)}`,
|
|
9088
|
+
title: m.Title,
|
|
9089
|
+
descriptions,
|
|
9090
|
+
impact: severityToImpact$2(m.Severity ?? ""),
|
|
9091
|
+
tags,
|
|
9092
|
+
refs: buildRefs$5([m.PrimaryURL, ...m.References ?? []]),
|
|
9093
|
+
code: JSON.stringify(m, null, 2),
|
|
9094
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9095
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9096
|
+
results: [{
|
|
9097
|
+
status: misconfStatus(m.Status),
|
|
9098
|
+
codeDesc: firstNonEmpty(m.Message, m.Title),
|
|
9099
|
+
startTime
|
|
9100
|
+
}]
|
|
9101
|
+
};
|
|
9102
|
+
if (res.Target) {
|
|
9103
|
+
const sl = { ref: res.Target };
|
|
9104
|
+
if (m.CauseMetadata?.StartLine && m.CauseMetadata.StartLine > 0) sl.line = m.CauseMetadata.StartLine;
|
|
9105
|
+
req.sourceLocation = sl;
|
|
9106
|
+
}
|
|
9107
|
+
return req;
|
|
9108
|
+
}
|
|
9109
|
+
function convertSecret(s, res, startTime) {
|
|
9110
|
+
const tags = buildTags$2({
|
|
9111
|
+
class: res.Class,
|
|
9112
|
+
...s.Category ? { secret_category: s.Category } : {}
|
|
9113
|
+
});
|
|
9114
|
+
markUnratedSeverity(tags, s.Severity);
|
|
9115
|
+
const req = {
|
|
9116
|
+
id: `Trivy/secret/${s.RuleID ?? ""}@${res.Target ?? ""}:${s.StartLine ?? 0}`,
|
|
9117
|
+
title: s.Title,
|
|
9118
|
+
descriptions: [{
|
|
9119
|
+
label: "default",
|
|
9120
|
+
data: `${firstNonEmpty(s.Title, s.RuleID)} detected in ${res.Target ?? ""} (value redacted by Trivy).`
|
|
9121
|
+
}],
|
|
9122
|
+
impact: severityToImpact$2(s.Severity ?? ""),
|
|
9123
|
+
tags,
|
|
9124
|
+
code: JSON.stringify(s, null, 2),
|
|
9125
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9126
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9127
|
+
results: [{
|
|
9128
|
+
status: ResultStatus.Failed,
|
|
9129
|
+
codeDesc: s.Match ?? "",
|
|
9130
|
+
startTime
|
|
9131
|
+
}]
|
|
9132
|
+
};
|
|
9133
|
+
if (res.Target) {
|
|
9134
|
+
const sl = { ref: res.Target };
|
|
9135
|
+
if (s.StartLine && s.StartLine > 0) sl.line = s.StartLine;
|
|
9136
|
+
req.sourceLocation = sl;
|
|
9137
|
+
}
|
|
9138
|
+
return req;
|
|
9139
|
+
}
|
|
9140
|
+
function convertLicense(l, res, startTime) {
|
|
9141
|
+
const extras = { class: res.Class };
|
|
9142
|
+
putIf(extras, "license_category", l.Category);
|
|
9143
|
+
putIf(extras, "package", l.PkgName);
|
|
9144
|
+
if (l.Confidence && l.Confidence > 0) extras.confidence = l.Confidence;
|
|
9145
|
+
const tags = buildTags$2(extras);
|
|
9146
|
+
markUnratedSeverity(tags, l.Severity);
|
|
9147
|
+
const req = {
|
|
9148
|
+
id: `Trivy/license/${l.PkgName ?? ""}/${l.Name ?? ""}`,
|
|
9149
|
+
title: `${l.Name ?? ""} (${l.Category ?? ""})`,
|
|
9150
|
+
descriptions: [{
|
|
9151
|
+
label: "default",
|
|
9152
|
+
data: `Package ${l.PkgName ?? ""} uses the ${l.Name ?? ""} license (category: ${l.Category ?? ""}).`
|
|
9153
|
+
}],
|
|
9154
|
+
impact: severityToImpact$2(l.Severity ?? ""),
|
|
9155
|
+
tags,
|
|
9156
|
+
refs: buildRefs$5([l.Link]),
|
|
9157
|
+
code: JSON.stringify(l, null, 2),
|
|
9158
|
+
controlType: deriveControlTypeFromTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS),
|
|
9159
|
+
verificationMethod: VerificationMethodEnum.Automated,
|
|
9160
|
+
results: [{
|
|
9161
|
+
status: ResultStatus.Failed,
|
|
9162
|
+
codeDesc: `${l.PkgName ?? ""}: ${l.Name ?? ""} license`,
|
|
9163
|
+
startTime
|
|
9164
|
+
}]
|
|
9165
|
+
};
|
|
9166
|
+
if (l.FilePath) req.sourceLocation = { ref: l.FilePath };
|
|
9167
|
+
return req;
|
|
9168
|
+
}
|
|
9169
|
+
function buildComponent$1(report) {
|
|
9170
|
+
if (!report.ArtifactName) return void 0;
|
|
9171
|
+
if (report.ArtifactType !== "container_image") return {
|
|
9172
|
+
name: report.ArtifactName,
|
|
9173
|
+
type: TargetType.Artifact
|
|
9174
|
+
};
|
|
9175
|
+
const c = {
|
|
9176
|
+
name: report.ArtifactName,
|
|
9177
|
+
type: TargetType.ContainerImage
|
|
9178
|
+
};
|
|
9179
|
+
const md = report.Metadata;
|
|
9180
|
+
if (!md) return c;
|
|
9181
|
+
if (md.ImageID) c.imageId = md.ImageID;
|
|
9182
|
+
if (md.OS?.Family) c.osName = md.OS.Family;
|
|
9183
|
+
if (md.OS?.Name) c.osVersion = md.OS.Name;
|
|
9184
|
+
const repoDigest = md.RepoDigests?.[0];
|
|
9185
|
+
if (repoDigest) {
|
|
9186
|
+
c.image = repoDigest;
|
|
9187
|
+
const integrity = digestToChecksums(digestPart(repoDigest));
|
|
9188
|
+
if (integrity) c.integrity = integrity;
|
|
9189
|
+
}
|
|
9190
|
+
if (md.ImageConfig?.architecture) c.labels = { architecture: md.ImageConfig.architecture };
|
|
9191
|
+
return c;
|
|
9192
|
+
}
|
|
9193
|
+
function buildTags$2(extras) {
|
|
9194
|
+
return buildNistCciTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS, STATIC_CCI, extras);
|
|
9195
|
+
}
|
|
9196
|
+
function misconfStatus(status) {
|
|
9197
|
+
switch ((status ?? "").toUpperCase()) {
|
|
9198
|
+
case "PASS": return ResultStatus.Passed;
|
|
9199
|
+
case "EXCEPTION": return ResultStatus.NotApplicable;
|
|
9200
|
+
default: return ResultStatus.Failed;
|
|
9201
|
+
}
|
|
9202
|
+
}
|
|
9203
|
+
function filterCwes(cwes) {
|
|
9204
|
+
if (!cwes) return void 0;
|
|
9205
|
+
const out = cwes.filter((c) => CWE_ID_PATTERN$1.test(c));
|
|
9206
|
+
return out.length > 0 ? out : void 0;
|
|
9207
|
+
}
|
|
9208
|
+
function buildCvssEntries$4(m) {
|
|
9209
|
+
if (!m || Object.keys(m).length === 0) return void 0;
|
|
9210
|
+
const entries = [];
|
|
9211
|
+
for (const src of Object.keys(m).sort()) {
|
|
9212
|
+
const c = m[src];
|
|
9213
|
+
if (!c) continue;
|
|
9214
|
+
if (c.V2Vector || c.V2Score !== void 0) entries.push(buildCvss$3({
|
|
9215
|
+
version: Version.The20,
|
|
9216
|
+
baseVector: c.V2Vector,
|
|
9217
|
+
baseScore: c.V2Score,
|
|
9218
|
+
source: src
|
|
9219
|
+
}));
|
|
9220
|
+
if (c.V3Vector || c.V3Score !== void 0) entries.push(buildCvss$3({
|
|
9221
|
+
version: cvssVersionFromVector(c.V3Vector, Version.The31),
|
|
9222
|
+
baseVector: c.V3Vector,
|
|
9223
|
+
baseScore: c.V3Score,
|
|
9224
|
+
source: src
|
|
9225
|
+
}));
|
|
9226
|
+
if (c.V40Vector || c.V40Score !== void 0) entries.push(buildCvss$3({
|
|
9227
|
+
version: Version.The40,
|
|
9228
|
+
baseVector: c.V40Vector,
|
|
9229
|
+
baseScore: c.V40Score,
|
|
9230
|
+
source: src
|
|
9231
|
+
}));
|
|
9232
|
+
}
|
|
9233
|
+
return entries.length > 0 ? entries : void 0;
|
|
9234
|
+
}
|
|
9235
|
+
const PURL_ECOSYSTEMS = {
|
|
9236
|
+
deb: Ecosystem.Deb,
|
|
9237
|
+
rpm: Ecosystem.RPM,
|
|
9238
|
+
maven: Ecosystem.Maven,
|
|
9239
|
+
npm: Ecosystem.Npm,
|
|
9240
|
+
pypi: Ecosystem.Pypi,
|
|
9241
|
+
gem: Ecosystem.Gem,
|
|
9242
|
+
cargo: Ecosystem.Cargo,
|
|
9243
|
+
golang: Ecosystem.Go,
|
|
9244
|
+
nuget: Ecosystem.Nuget
|
|
9245
|
+
};
|
|
9246
|
+
function buildAffectedPackage$1(v) {
|
|
9247
|
+
const ap = {};
|
|
9248
|
+
if (v.PkgName) ap.name = v.PkgName;
|
|
9249
|
+
if (v.InstalledVersion) ap.version = v.InstalledVersion;
|
|
9250
|
+
if (v.FixedVersion) ap.fixedInVersion = v.FixedVersion;
|
|
9251
|
+
const purl = v.PkgIdentifier?.PURL;
|
|
9252
|
+
if (purl) {
|
|
9253
|
+
ap.purl = purl;
|
|
9254
|
+
const eco = ecosystemFromPurl(purl);
|
|
9255
|
+
if (eco) ap.ecosystem = eco;
|
|
9256
|
+
}
|
|
9257
|
+
return ap;
|
|
9258
|
+
}
|
|
9259
|
+
function ecosystemFromPurl(purl) {
|
|
9260
|
+
const type = (purl.replace(/^pkg:/, "").split(/[/@?]/)[0] ?? "").toLowerCase();
|
|
9261
|
+
return PURL_ECOSYSTEMS[type];
|
|
9262
|
+
}
|
|
9263
|
+
function buildRefs$5(urls) {
|
|
9264
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9265
|
+
const refs = [];
|
|
9266
|
+
for (const u of urls) {
|
|
9267
|
+
if (!u || seen.has(u)) continue;
|
|
9268
|
+
seen.add(u);
|
|
9269
|
+
refs.push({ url: u });
|
|
9270
|
+
}
|
|
9271
|
+
return refs.length > 0 ? refs : void 0;
|
|
9272
|
+
}
|
|
9273
|
+
function buildVulnCodeDesc(v, res) {
|
|
9274
|
+
const parts = [`Package: ${v.PkgName ?? ""}@${v.InstalledVersion ?? ""}`];
|
|
9275
|
+
if (res.Type) parts.push(`Type: ${res.Type}`);
|
|
9276
|
+
if (v.PkgPath) parts.push(`Location: ${v.PkgPath}`);
|
|
9277
|
+
if (v.FixedVersion) parts.push(`Fixed: ${v.FixedVersion}`);
|
|
9278
|
+
return parts.join(" | ");
|
|
9279
|
+
}
|
|
9280
|
+
function pkgLabel(name, version) {
|
|
9281
|
+
if (!version) return name ?? "";
|
|
9282
|
+
return `${name ?? ""}@${version}`;
|
|
9283
|
+
}
|
|
9284
|
+
function digestPart(ref) {
|
|
9285
|
+
const at = ref.lastIndexOf("@");
|
|
9286
|
+
return at >= 0 ? ref.slice(at + 1) : ref;
|
|
9287
|
+
}
|
|
9288
|
+
function firstNonEmpty(...vals) {
|
|
9289
|
+
for (const v of vals) if (v) return v;
|
|
9290
|
+
return "";
|
|
9291
|
+
}
|
|
9292
|
+
function putIf(m, key, val) {
|
|
9293
|
+
if (val) m[key] = val;
|
|
9294
|
+
}
|
|
9295
|
+
//#endregion
|
|
8269
9296
|
//#region converters/trufflehog-to-hdf/typescript/converter.ts
|
|
8270
9297
|
/** Hardcoded NIST/CCI constants for credential exposure findings. */
|
|
8271
9298
|
const TRUFFLEHOG_NIST = ["IA-5 (7)"];
|
|
@@ -8586,14 +9613,9 @@ async function convertHipcheckToHdf(input, converterVersion = "1.0.0") {
|
|
|
8586
9613
|
}
|
|
8587
9614
|
//#endregion
|
|
8588
9615
|
//#region converters/burpsuite-to-hdf/typescript/converter.ts
|
|
8589
|
-
const
|
|
8590
|
-
"high": .7,
|
|
8591
|
-
"medium": .5,
|
|
8592
|
-
"low": .3,
|
|
8593
|
-
"information": .3
|
|
8594
|
-
};
|
|
9616
|
+
const BURPSUITE_ALIASES = { information: .3 };
|
|
8595
9617
|
function getImpact$7(severity) {
|
|
8596
|
-
return
|
|
9618
|
+
return severityToImpactWithAliases(severity, BURPSUITE_ALIASES, .3);
|
|
8597
9619
|
}
|
|
8598
9620
|
/**
|
|
8599
9621
|
* Extract CWE identifiers from HTML text.
|
|
@@ -8778,13 +9800,6 @@ function buildRequirement$11(issueType, issues, startTime) {
|
|
|
8778
9800
|
}
|
|
8779
9801
|
//#endregion
|
|
8780
9802
|
//#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
9803
|
const NAMED_ENTITIES$2 = {
|
|
8789
9804
|
amp: "&",
|
|
8790
9805
|
lt: "<",
|
|
@@ -8848,10 +9863,9 @@ function getStatus$2(status) {
|
|
|
8848
9863
|
function getBacktrace(resultStatus) {
|
|
8849
9864
|
return resultStatus === "Failed" ? ["DB Protect Failed Check"] : void 0;
|
|
8850
9865
|
}
|
|
8851
|
-
/** Maps DBProtect risk level to HDF impact
|
|
9866
|
+
/** Maps DBProtect risk level to HDF impact via the shared standard map (Go parity). */
|
|
8852
9867
|
function getImpact$6(riskDV) {
|
|
8853
|
-
|
|
8854
|
-
return impact !== void 0 ? impact : .5;
|
|
9868
|
+
return severityToImpactWithAliases(riskDV, {}, .5);
|
|
8855
9869
|
}
|
|
8856
9870
|
/** Creates a description string from a finding's task and check category */
|
|
8857
9871
|
function formatDesc$1(f) {
|
|
@@ -8967,6 +9981,7 @@ function buildRequirement$10(checkID, findings, hasStatus) {
|
|
|
8967
9981
|
const tags = buildNistCciTags(nist, nistToCci(nist));
|
|
8968
9982
|
const checkCategory = rep["Check Category"] ?? "";
|
|
8969
9983
|
if (checkCategory) tags.check_category = checkCategory;
|
|
9984
|
+
markUnratedSeverity(tags, rep["Risk DV"]);
|
|
8970
9985
|
const descriptions = [{
|
|
8971
9986
|
label: "default",
|
|
8972
9987
|
data: formatDesc$1(rep)
|
|
@@ -9037,21 +10052,12 @@ async function convertDbprotectToHdf(input, converterVersion = "1.0.0") {
|
|
|
9037
10052
|
}
|
|
9038
10053
|
//#endregion
|
|
9039
10054
|
//#region converters/twistlock-to-hdf/typescript/converter.ts
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
*/
|
|
10055
|
+
const TWISTLOCK_ALIASES = {
|
|
10056
|
+
important: .9,
|
|
10057
|
+
moderate: .5
|
|
10058
|
+
};
|
|
9045
10059
|
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
|
-
}
|
|
10060
|
+
return severityToImpactWithAliases(severity, TWISTLOCK_ALIASES, .5);
|
|
9055
10061
|
}
|
|
9056
10062
|
/**
|
|
9057
10063
|
* Builds the baseline title from scan result data.
|
|
@@ -9247,6 +10253,7 @@ function buildRequirement$9(vuln, packageTypes, distro) {
|
|
|
9247
10253
|
const extras = { cveid: [vuln.id] };
|
|
9248
10254
|
if (typeof vuln.cvss === "number" && vuln.cvss > 0) extras["cvss_base_score"] = vuln.cvss;
|
|
9249
10255
|
const tags = buildNistCciTags(nist, cciTags, extras);
|
|
10256
|
+
markUnratedSeverity(tags, vuln.severity);
|
|
9250
10257
|
const descriptions = [{
|
|
9251
10258
|
label: "default",
|
|
9252
10259
|
data: vuln.description
|
|
@@ -9508,7 +10515,7 @@ function buildAffectedPackageFromComponent(c) {
|
|
|
9508
10515
|
let ecosystem;
|
|
9509
10516
|
if (c.purl) ecosystem = ecosystemFromPurlType(parsePurl(c.purl)?.type);
|
|
9510
10517
|
else if (c.name && c.version) ecosystem = ecosystemFromPurlType(void 0);
|
|
9511
|
-
return buildAffectedPackage$
|
|
10518
|
+
return buildAffectedPackage$2({
|
|
9512
10519
|
name: c.name,
|
|
9513
10520
|
version: c.version,
|
|
9514
10521
|
ecosystem,
|
|
@@ -9726,6 +10733,7 @@ function buildRequirement$7(entryID, entries, scanTime) {
|
|
|
9726
10733
|
cci: nistToCci(nist)
|
|
9727
10734
|
};
|
|
9728
10735
|
if (cveIDs.length > 0) tags["cve"] = cveIDs;
|
|
10736
|
+
markUnratedSeverity(tags, rep.severity);
|
|
9729
10737
|
const descriptions = [{
|
|
9730
10738
|
label: "default",
|
|
9731
10739
|
data: formatDescription(rep)
|
|
@@ -9779,7 +10787,7 @@ function buildAffectedPackageFromEntry(entry) {
|
|
|
9779
10787
|
ecosystem = ecosystemFromXraySource(parsed.scheme);
|
|
9780
10788
|
}
|
|
9781
10789
|
const fixed = entry.component_versions?.fixed_versions?.[0];
|
|
9782
|
-
return buildAffectedPackage$
|
|
10790
|
+
return buildAffectedPackage$2({
|
|
9783
10791
|
name,
|
|
9784
10792
|
version,
|
|
9785
10793
|
ecosystem: ecosystem ?? (name ? Ecosystem.Generic : void 0),
|
|
@@ -10011,7 +11019,7 @@ function buildRequirement$6(vuln, scanTime, ml) {
|
|
|
10011
11019
|
const refs = buildRefs$3(vuln);
|
|
10012
11020
|
if (refs) req.refs = refs;
|
|
10013
11021
|
const cpe23 = (vuln.cpes ?? []).find((c) => /^cpe:2\.3:[aho]:/.test(c));
|
|
10014
|
-
const pkg = buildAffectedPackage$
|
|
11022
|
+
const pkg = buildAffectedPackage$2({
|
|
10015
11023
|
name: vuln.package_name,
|
|
10016
11024
|
version: vuln.package_version,
|
|
10017
11025
|
ecosystem: vuln.package_name && vuln.package_version ? Ecosystem.Generic : void 0,
|
|
@@ -10033,12 +11041,6 @@ function imageTitle(report) {
|
|
|
10033
11041
|
function targetNameFromReport(report) {
|
|
10034
11042
|
return `${report.registry}/${report.repository}:${report.tag}`;
|
|
10035
11043
|
}
|
|
10036
|
-
const DIGEST_ALGORITHMS = {
|
|
10037
|
-
sha256: HashAlgorithm.Sha256,
|
|
10038
|
-
sha384: HashAlgorithm.Sha384,
|
|
10039
|
-
sha512: HashAlgorithm.Sha512,
|
|
10040
|
-
blake3: HashAlgorithm.Blake3
|
|
10041
|
-
};
|
|
10042
11044
|
/**
|
|
10043
11045
|
* Splits NeuVector's base_os "name:version" form (e.g. "alpine:3.12.1") into OS
|
|
10044
11046
|
* name and version. A value with no ":" is the name with no version; an empty
|
|
@@ -10060,28 +11062,6 @@ function splitBaseOS(baseOS) {
|
|
|
10060
11062
|
};
|
|
10061
11063
|
}
|
|
10062
11064
|
/**
|
|
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
11065
|
* Assembles the scan-wide containerImage component from the report's image
|
|
10086
11066
|
* identity. base_os → osName/osVersion, digest → integrity, plus
|
|
10087
11067
|
* imageId/registry/repository/tag when the report carries them.
|
|
@@ -10104,7 +11084,7 @@ function buildComponent(report) {
|
|
|
10104
11084
|
if (report.registry) component.registry = report.registry;
|
|
10105
11085
|
if (report.repository) component.repository = report.repository;
|
|
10106
11086
|
if (report.tag) component.tag = report.tag;
|
|
10107
|
-
const integrity =
|
|
11087
|
+
const integrity = digestToChecksums(report.digest);
|
|
10108
11088
|
if (integrity) component.integrity = integrity;
|
|
10109
11089
|
return component;
|
|
10110
11090
|
}
|
|
@@ -10323,8 +11303,9 @@ function buildRequirement$5(desc, vulns, snippetMap, startTimeStr) {
|
|
|
10323
11303
|
label: "tips",
|
|
10324
11304
|
data: tipsData
|
|
10325
11305
|
});
|
|
10326
|
-
|
|
10327
|
-
|
|
11306
|
+
const instanceSeverity = parseFloat(vulns[0]?.InstanceInfo?.InstanceSeverity ?? "0");
|
|
11307
|
+
const impact = instanceSeverity > 0 ? roundImpact(instanceSeverity / 5) : 0;
|
|
11308
|
+
if (!(instanceSeverity > 0)) markUnratedSeverity(tags, "");
|
|
10328
11309
|
const { items: limitedVulns, truncated } = limitArray(vulns);
|
|
10329
11310
|
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
10330
11311
|
if (truncated) console.warn(`WARNING: Input truncated at ${limitedVulns.length} vulnerability items (original: ${vulns.length})`);
|
|
@@ -10522,6 +11503,7 @@ function buildRequirement$4(rec, scanTime) {
|
|
|
10522
11503
|
const extras = {};
|
|
10523
11504
|
if (rec["CVE ID"]) extras["cve"] = [rec["CVE ID"]];
|
|
10524
11505
|
const tags = buildNistCciTags(nist, cciTags, Object.keys(extras).length > 0 ? extras : void 0);
|
|
11506
|
+
markUnratedSeverity(tags, rec.Severity);
|
|
10525
11507
|
const descriptions = [{
|
|
10526
11508
|
label: "default",
|
|
10527
11509
|
data: rec.Description
|
|
@@ -10610,7 +11592,7 @@ function buildAffectedPackageFromRecord(rec) {
|
|
|
10610
11592
|
const version = rec["Package Version"];
|
|
10611
11593
|
if (!name || !version) return void 0;
|
|
10612
11594
|
const fixMatch = rec["Fix Status"] ? FIX_VERSION_PATTERN.exec(rec["Fix Status"]) : null;
|
|
10613
|
-
return buildAffectedPackage$
|
|
11595
|
+
return buildAffectedPackage$2({
|
|
10614
11596
|
name,
|
|
10615
11597
|
version,
|
|
10616
11598
|
ecosystem: ecosystemFromDistro(rec.Distro),
|
|
@@ -10680,16 +11662,9 @@ async function convertPrismaToHdf(input, converterVersion = "1.0.0") {
|
|
|
10680
11662
|
}
|
|
10681
11663
|
//#endregion
|
|
10682
11664
|
//#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
|
-
};
|
|
11665
|
+
const NETSPARKER_ALIASES = { best_practice: 0 };
|
|
10691
11666
|
function getImpact$2(severity) {
|
|
10692
|
-
return
|
|
11667
|
+
return severityToImpactWithAliases(severity, NETSPARKER_ALIASES, .5);
|
|
10693
11668
|
}
|
|
10694
11669
|
const NETSPARKER_US_DATETIME = /^\d{2}\/\d{2}\/\d{4} \d{1,2}:\d{2} (AM|PM)$/;
|
|
10695
11670
|
function parseNetsparkerTimestamp(s) {
|
|
@@ -10824,6 +11799,7 @@ function buildRequirement$3(vuln, initiated) {
|
|
|
10824
11799
|
const pci32 = vuln.classification?.pci32;
|
|
10825
11800
|
if (pci32) extras.pci32 = pci32;
|
|
10826
11801
|
const tags = buildNistCciTags(nist, cciTags, Object.keys(extras).length > 0 ? extras : void 0);
|
|
11802
|
+
markUnratedSeverity(tags, vuln.severity);
|
|
10827
11803
|
const descriptions = [{
|
|
10828
11804
|
label: "default",
|
|
10829
11805
|
data: formatControlDesc(vuln) || vuln.name || ""
|
|
@@ -11219,14 +12195,16 @@ function buildRequirementFromResult(result, filename) {
|
|
|
11219
12195
|
const startTime = (startTimeStr ? parseTimestamp(startTimeStr) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
11220
12196
|
const runTime = computeRunTime(result.response.milestones?.service_started, result.response.milestones?.service_completed);
|
|
11221
12197
|
const score = result.result.score;
|
|
11222
|
-
const
|
|
11223
|
-
const
|
|
12198
|
+
const scoreless = score === void 0 || score === null;
|
|
12199
|
+
const status = scoreless ? ResultStatus.NotReviewed : determineStatus(score);
|
|
12200
|
+
const message = scoreless ? "Conveyor result carries no score; verdict not evaluated." : void 0;
|
|
12201
|
+
const toResult = (codeDesc) => createResult(status, message, {
|
|
11224
12202
|
codeDesc,
|
|
11225
12203
|
startTime,
|
|
11226
12204
|
runTime
|
|
11227
12205
|
});
|
|
11228
12206
|
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 });
|
|
12207
|
+
const req = createRequirement(result.sha256, filename, descriptions, scoreless ? 0 : scoreToImpact(score), results, { tags });
|
|
11230
12208
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
11231
12209
|
const controlType = deriveControlTypeFromTags([...nist]);
|
|
11232
12210
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -11301,17 +12279,17 @@ async function convertConveyorToHdf(input, converterVersion = "1.0.0") {
|
|
|
11301
12279
|
*/
|
|
11302
12280
|
/** Attribute prefix used by fast-xml-parser — all XML attributes are accessed via `@_name`. */
|
|
11303
12281
|
const A = "@_";
|
|
11304
|
-
/** Veracode severity level (0-5)
|
|
11305
|
-
const
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
12282
|
+
/** Veracode severity level (0-5) aliases; mirrors Go's veracodeAliases. */
|
|
12283
|
+
const VERACODE_ALIASES = {
|
|
12284
|
+
"5": .9,
|
|
12285
|
+
"4": .7,
|
|
12286
|
+
"3": .5,
|
|
12287
|
+
"2": .3,
|
|
12288
|
+
"1": .1,
|
|
12289
|
+
"0": 0
|
|
12290
|
+
};
|
|
11313
12291
|
function veracodeSeverityToImpact(severity) {
|
|
11314
|
-
return
|
|
12292
|
+
return severityToImpactWithAliases(severity, VERACODE_ALIASES, .1);
|
|
11315
12293
|
}
|
|
11316
12294
|
/** Get an attribute from a parsed XML node, entity-decoded. */
|
|
11317
12295
|
function attr(node, name) {
|
|
@@ -11556,12 +12534,12 @@ function buildCWERequirements(severities, firstBuildDate) {
|
|
|
11556
12534
|
for (const sev of severities) {
|
|
11557
12535
|
const impact = veracodeSeverityToImpact(attr(sev, "level"));
|
|
11558
12536
|
const categories = ensureArray(sev.category);
|
|
11559
|
-
for (const cat of categories) requirements.push(buildCWERequirement(cat, impact, firstBuildDate));
|
|
12537
|
+
for (const cat of categories) requirements.push(buildCWERequirement(cat, attr(sev, "level"), impact, firstBuildDate));
|
|
11560
12538
|
}
|
|
11561
12539
|
return requirements;
|
|
11562
12540
|
}
|
|
11563
12541
|
/** Build a single CWE-based requirement from a category. */
|
|
11564
|
-
function buildCWERequirement(cat, impact, firstBuildDate) {
|
|
12542
|
+
function buildCWERequirement(cat, sevLevel, impact, firstBuildDate) {
|
|
11565
12543
|
const cwes = ensureArray(cat.cwe);
|
|
11566
12544
|
const cweIDs = cwes.map((c) => attr(c, "cweid")).filter(Boolean);
|
|
11567
12545
|
const nist = mapCWEToNIST(cweIDs, DEFAULT_REMEDIATION_NIST_TAGS$1);
|
|
@@ -11574,6 +12552,7 @@ function buildCWERequirement(cat, impact, firstBuildDate) {
|
|
|
11574
12552
|
if (values.length > 0) extras[key] = values;
|
|
11575
12553
|
}
|
|
11576
12554
|
const tags = buildNistCciTags(nist, cciTags, extras);
|
|
12555
|
+
markUnratedSeverity(tags, sevLevel);
|
|
11577
12556
|
const cweList = cweIDs.map((id) => `CWE-${id}`);
|
|
11578
12557
|
const descriptions = [{
|
|
11579
12558
|
label: "default",
|
|
@@ -11685,6 +12664,7 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
|
|
|
11685
12664
|
else nist = DEFAULT_REMEDIATION_NIST_TAGS$1;
|
|
11686
12665
|
const cciTags = nistToCci(nist);
|
|
11687
12666
|
const tags = buildNistCciTags(nist, cciTags, {});
|
|
12667
|
+
markUnratedSeverity(tags, attr(vuln, "severity"));
|
|
11688
12668
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
11689
12669
|
const results = components.map((comp) => createResult(ResultStatus.Failed, void 0, {
|
|
11690
12670
|
codeDesc: formatSCACodeDesc(comp),
|
|
@@ -12017,14 +12997,6 @@ function repoNameFromURI(uri) {
|
|
|
12017
12997
|
//#endregion
|
|
12018
12998
|
//#region converters/msft-defender-cloud-to-hdf/typescript/converter.ts
|
|
12019
12999
|
/**
|
|
12020
|
-
* Severity to HDF impact mapping.
|
|
12021
|
-
*/
|
|
12022
|
-
const IMPACT_MAPPING$1 = {
|
|
12023
|
-
high: .7,
|
|
12024
|
-
medium: .5,
|
|
12025
|
-
low: .3
|
|
12026
|
-
};
|
|
12027
|
-
/**
|
|
12028
13000
|
* Maps Azure status code to HDF ResultStatus.
|
|
12029
13001
|
*/
|
|
12030
13002
|
function mapStatus(code) {
|
|
@@ -12052,7 +13024,7 @@ function extractSubscriptionID(resourcePath) {
|
|
|
12052
13024
|
function buildRequirement(assessmentID, assessments, scanTime) {
|
|
12053
13025
|
const rep = assessments[0];
|
|
12054
13026
|
const meta = rep.properties.metadata;
|
|
12055
|
-
const impact =
|
|
13027
|
+
const impact = severityToImpact(meta.severity);
|
|
12056
13028
|
const tags = {};
|
|
12057
13029
|
if (meta.categories.length > 0) tags["categories"] = meta.categories;
|
|
12058
13030
|
if (meta.tactics.length > 0) tags["tactics"] = meta.tactics;
|
|
@@ -12144,21 +13116,6 @@ async function convertMsftDefenderCloudToHdf(input, converterVersion = "1.0.0")
|
|
|
12144
13116
|
//#endregion
|
|
12145
13117
|
//#region converters/msft-defender-endpoint-to-hdf/typescript/converter.ts
|
|
12146
13118
|
/**
|
|
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
13119
|
* An MDE alert is a detection that fired, so every alert is a raw Failed result.
|
|
12163
13120
|
* Consumer triage (falsePositive, expected-activity) never flips the raw status —
|
|
12164
13121
|
* it rides in a structured Status_Override that carries effectiveStatus + full
|
|
@@ -12262,7 +13219,7 @@ function formatMessage(alert) {
|
|
|
12262
13219
|
const parts = [];
|
|
12263
13220
|
parts.push(`Alert: ${alert.title}`);
|
|
12264
13221
|
parts.push(`Status: ${alert.status}`);
|
|
12265
|
-
parts.push(`Severity: ${alert.severity}`);
|
|
13222
|
+
parts.push(`Severity: ${alert.severity ?? ""}`);
|
|
12266
13223
|
if (alert.threatDisplayName) parts.push(`Threat: ${alert.threatDisplayName}`);
|
|
12267
13224
|
if (alert.alertWebUrl) parts.push(`URL: ${alert.alertWebUrl}`);
|
|
12268
13225
|
return parts.join("\n");
|
|
@@ -12357,7 +13314,7 @@ function deriveScanTimestamp(alerts) {
|
|
|
12357
13314
|
* Converts a single MDE alert to an HDF EvaluatedRequirement.
|
|
12358
13315
|
*/
|
|
12359
13316
|
function alertToRequirement(alert, scanTime) {
|
|
12360
|
-
const impact = severityToImpact
|
|
13317
|
+
const impact = severityToImpact(alert.severity);
|
|
12361
13318
|
const codeDesc = formatEvidence$1(alert.evidence ?? []);
|
|
12362
13319
|
const message = formatMessage(alert);
|
|
12363
13320
|
const startTime = resolveStartTime(alert, scanTime);
|
|
@@ -12644,7 +13601,9 @@ function buildTestResultObj(hdfData, baseline) {
|
|
|
12644
13601
|
for (const req of baseline.requirements) {
|
|
12645
13602
|
const ruleIdRef = sanitizeXccdfId("xccdf_hdf_rule_" + req.id + "_rule");
|
|
12646
13603
|
const stigId = tagString(req.tags, "stig_id");
|
|
12647
|
-
const
|
|
13604
|
+
const statusInput = requirementStatusInput(req);
|
|
13605
|
+
const resolved = computeEffectiveStatus(statusInput);
|
|
13606
|
+
const effective = resolved !== worstStatus(statusInput.resultStatuses ?? []) ? hdfStatusToXccdf(resolved) : void 0;
|
|
12648
13607
|
for (const result of req.results) {
|
|
12649
13608
|
const status = effective ?? hdfStatusToXccdf(result.status);
|
|
12650
13609
|
if (status === "pass") {
|
|
@@ -12829,11 +13788,69 @@ function toKebabCase(title, fallback) {
|
|
|
12829
13788
|
if (!title) return fallback;
|
|
12830
13789
|
let name = title.toLowerCase();
|
|
12831
13790
|
name = name.replace(/[^a-z0-9]/g, "-");
|
|
12832
|
-
|
|
12833
|
-
|
|
13791
|
+
name = name.replace(/-+/g, "-");
|
|
13792
|
+
let start = 0;
|
|
13793
|
+
while (start < name.length && name[start] === "-") start++;
|
|
13794
|
+
let end = name.length;
|
|
13795
|
+
while (end > start && name[end - 1] === "-") end--;
|
|
13796
|
+
name = name.slice(start, end);
|
|
12834
13797
|
if (name.length > 80) name = name.slice(0, 80);
|
|
12835
13798
|
return name;
|
|
12836
13799
|
}
|
|
13800
|
+
/**
|
|
13801
|
+
* Encode an arbitrary identifier into OSCAL's TokenDatatype shape:
|
|
13802
|
+
* `^(\p{L}|_)(\p{L}|\p{N}|[.\-_])*$`
|
|
13803
|
+
*
|
|
13804
|
+
* HDF requirement ids come from whatever the source tool numbers its rules with,
|
|
13805
|
+
* and only some of those shapes are already tokens. Measured across this
|
|
13806
|
+
* package's converter fixtures, 46% are not (57% of the distinct ids): package-style ids
|
|
13807
|
+
* carrying '/', CIS control numbers starting with a digit, advisory ids carrying
|
|
13808
|
+
* ':'. Copying one of those into a token-typed OSCAL field produces a document
|
|
13809
|
+
* the target schema rejects while the converter resolves successfully.
|
|
13810
|
+
*
|
|
13811
|
+
* The kept set is deliberately ASCII — [A-Za-z0-9._-] — rather than the wider
|
|
13812
|
+
* \p{L}/\p{N} the pattern permits. Delegating to each platform's Unicode tables
|
|
13813
|
+
* looked equivalent and was not: V8's \p{L}/\p{N} and Go's unicode package are
|
|
13814
|
+
* built from different Unicode versions, and comparing the two implementations
|
|
13815
|
+
* across the whole code-point range turned up 4657 characters they disagree on.
|
|
13816
|
+
* An explicit ASCII set has no such dependency and is identical in both languages
|
|
13817
|
+
* by construction. It costs nothing on real data: no requirement id in this
|
|
13818
|
+
* package's converter fixtures is non-ASCII, and a non-ASCII one
|
|
13819
|
+
* is preserved in full by the caller's source-id prop regardless.
|
|
13820
|
+
*
|
|
13821
|
+
* Every character outside that set becomes '_', and a leading '_' is prepended
|
|
13822
|
+
* when the result would not start with a letter or '_'. An id built only from
|
|
13823
|
+
* the kept characters is returned unchanged; note that this is narrower than
|
|
13824
|
+
* token shape, so a token-valid non-ASCII id such as "café" is still rewritten.
|
|
13825
|
+
*
|
|
13826
|
+
* Two different ids can encode to the same token ('a/b' and 'a:b' both yield
|
|
13827
|
+
* 'a_b'), which is why callers must also record the source id in the emitted
|
|
13828
|
+
* document — for SAR that is a prop on the finding, trimmed because OSCAL's
|
|
13829
|
+
* StringDatatype forbids a padded value.
|
|
13830
|
+
*/
|
|
13831
|
+
function oscalToken(s) {
|
|
13832
|
+
if (s === "") return "";
|
|
13833
|
+
const out = [...s].map((ch) => /[A-Za-z0-9.\-_]/.test(ch) ? ch : "_").join("");
|
|
13834
|
+
const [first = ""] = [...out];
|
|
13835
|
+
return /[A-Za-z_]/.test(first) ? out : `_${out}`;
|
|
13836
|
+
}
|
|
13837
|
+
/**
|
|
13838
|
+
* Render a value for a field OSCAL types as StringDatatype, whose pattern
|
|
13839
|
+
* `^\S(.*\S)?$` forbids an empty value and any leading or trailing whitespace.
|
|
13840
|
+
* HDF constrains none of the strings that feed those fields — no minLength
|
|
13841
|
+
* anywhere in hdf-amendments — so a padded or empty value is valid HDF that
|
|
13842
|
+
* would otherwise produce a document the target schema rejects at exit 0.
|
|
13843
|
+
*
|
|
13844
|
+
* Returns '' when nothing survives trimming, which callers treat as "omit this
|
|
13845
|
+
* field" rather than substituting a placeholder: OSCAL marks most of these
|
|
13846
|
+
* fields optional, so leaving one out says exactly as much as the source did.
|
|
13847
|
+
* The companion for token-typed fields is oscalToken.
|
|
13848
|
+
*
|
|
13849
|
+
* Mirrors OSCALString in the Go peer.
|
|
13850
|
+
*/
|
|
13851
|
+
function oscalString(s) {
|
|
13852
|
+
return s.trim();
|
|
13853
|
+
}
|
|
12837
13854
|
//#endregion
|
|
12838
13855
|
//#region converters/hdf-to-oscal-sar/typescript/converter.ts
|
|
12839
13856
|
/**
|
|
@@ -12896,12 +13913,18 @@ function buildOSCALDocument(hdfResults) {
|
|
|
12896
13913
|
else importAP = { href: "#" };
|
|
12897
13914
|
const subjects = buildSubjects(hdfResults.components);
|
|
12898
13915
|
const results = [];
|
|
12899
|
-
|
|
13916
|
+
const resources = [];
|
|
13917
|
+
for (const baseline of hdfResults.baselines) {
|
|
13918
|
+
const { result, resources: baselineResources } = baselineToResult(baseline, timestamp, toolActorUuid, subjects);
|
|
13919
|
+
results.push(result);
|
|
13920
|
+
resources.push(...baselineResources);
|
|
13921
|
+
}
|
|
12900
13922
|
return { "assessment-results": {
|
|
12901
13923
|
uuid: crypto.randomUUID(),
|
|
12902
13924
|
metadata,
|
|
12903
13925
|
"import-ap": importAP,
|
|
12904
|
-
results
|
|
13926
|
+
results,
|
|
13927
|
+
...resources.length > 0 ? { "back-matter": { resources } } : {}
|
|
12905
13928
|
} };
|
|
12906
13929
|
}
|
|
12907
13930
|
/** Earliest startTime across the results, or undefined if none carry one. */
|
|
@@ -12933,7 +13956,8 @@ function assessmentStart(baseline, fallback) {
|
|
|
12933
13956
|
return formatAssessmentTime(earliest, fallback);
|
|
12934
13957
|
}
|
|
12935
13958
|
/**
|
|
12936
|
-
* Converts a single EvaluatedBaseline to an OSCAL Result
|
|
13959
|
+
* Converts a single EvaluatedBaseline to an OSCAL Result plus any back-matter
|
|
13960
|
+
* resources (embedded check source code) its findings link to.
|
|
12937
13961
|
*/
|
|
12938
13962
|
function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
12939
13963
|
let title = baseline.name;
|
|
@@ -12948,14 +13972,18 @@ function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
|
12948
13972
|
const findings = [];
|
|
12949
13973
|
const observations = [];
|
|
12950
13974
|
const risks = [];
|
|
13975
|
+
const resources = [];
|
|
12951
13976
|
const includeControls = [];
|
|
12952
13977
|
const seenControl = /* @__PURE__ */ new Set();
|
|
12953
13978
|
for (const req of baseline.requirements) {
|
|
12954
|
-
const
|
|
13979
|
+
const nistId = nistTagToControlId(req.id);
|
|
13980
|
+
if (nistId === "") continue;
|
|
13981
|
+
const { finding, observation, risk, resource } = requirementToFindingSet(req, timestamp, toolActorUuid, subjects);
|
|
12955
13982
|
findings.push(finding);
|
|
12956
13983
|
if (observation) observations.push(observation);
|
|
12957
13984
|
if (risk) risks.push(risk);
|
|
12958
|
-
|
|
13985
|
+
if (resource) resources.push(resource);
|
|
13986
|
+
const cid = oscalToken(nistId);
|
|
12959
13987
|
if (cid !== "" && !seenControl.has(cid)) {
|
|
12960
13988
|
seenControl.add(cid);
|
|
12961
13989
|
includeControls.push({ "control-id": cid });
|
|
@@ -12963,15 +13991,18 @@ function baselineToResult(baseline, timestamp, toolActorUuid, subjects) {
|
|
|
12963
13991
|
}
|
|
12964
13992
|
const controlSelection = includeControls.length > 0 ? { "include-controls": includeControls } : {};
|
|
12965
13993
|
return {
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
13994
|
+
result: {
|
|
13995
|
+
uuid: crypto.randomUUID(),
|
|
13996
|
+
title,
|
|
13997
|
+
description,
|
|
13998
|
+
start: assessmentStart(baseline, timestamp),
|
|
13999
|
+
...resultProps.length > 0 ? { props: resultProps } : {},
|
|
14000
|
+
"reviewed-controls": { "control-selections": [controlSelection] },
|
|
14001
|
+
findings,
|
|
14002
|
+
observations,
|
|
14003
|
+
risks
|
|
14004
|
+
},
|
|
14005
|
+
resources
|
|
12975
14006
|
};
|
|
12976
14007
|
}
|
|
12977
14008
|
/**
|
|
@@ -12992,18 +14023,35 @@ function buildSubjects(components) {
|
|
|
12992
14023
|
* Converts an EvaluatedRequirement into a Finding, optional Observation, and optional Risk.
|
|
12993
14024
|
*/
|
|
12994
14025
|
function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
12995
|
-
const controlID = nistTagToControlId(req.id);
|
|
14026
|
+
const controlID = oscalToken(nistTagToControlId(req.id));
|
|
12996
14027
|
const results = req.results ?? [];
|
|
12997
14028
|
const descriptions = req.descriptions ?? [];
|
|
12998
|
-
const { state, reason } = effectiveState(req
|
|
14029
|
+
const { state, reason } = effectiveState(req);
|
|
12999
14030
|
const findingDesc = extractDefaultDescription(descriptions);
|
|
13000
|
-
const props = [
|
|
14031
|
+
const props = [{
|
|
14032
|
+
name: "hdf-requirement-id",
|
|
14033
|
+
value: oscalString(req.id)
|
|
14034
|
+
}];
|
|
13001
14035
|
const addProp = (name, value) => {
|
|
13002
14036
|
if (value !== "") props.push({
|
|
13003
14037
|
name,
|
|
13004
14038
|
value
|
|
13005
14039
|
});
|
|
13006
14040
|
};
|
|
14041
|
+
const addProseProp = (name, text) => {
|
|
14042
|
+
const preview = previewLine(text);
|
|
14043
|
+
if (preview === "") return;
|
|
14044
|
+
const p = {
|
|
14045
|
+
name,
|
|
14046
|
+
value: preview
|
|
14047
|
+
};
|
|
14048
|
+
if (preview !== text) p.remarks = text;
|
|
14049
|
+
props.push(p);
|
|
14050
|
+
};
|
|
14051
|
+
const descriptionByLabel = (label) => {
|
|
14052
|
+
const d = descriptions.find((x) => x.label === label);
|
|
14053
|
+
return d ? d.data : "";
|
|
14054
|
+
};
|
|
13007
14055
|
const pushTagValues = (key) => {
|
|
13008
14056
|
const raw = req.tags?.[key];
|
|
13009
14057
|
if (Array.isArray(raw)) {
|
|
@@ -13012,15 +14060,9 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13012
14060
|
};
|
|
13013
14061
|
pushTagValues("nist");
|
|
13014
14062
|
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
|
-
}
|
|
14063
|
+
addProseProp("check", descriptionByLabel("check"));
|
|
14064
|
+
addProseProp("rationale", descriptionByLabel("rationale"));
|
|
14065
|
+
if (req.impact <= 0) addProseProp("fix", descriptionByLabel("fix"));
|
|
13024
14066
|
if (req.controlType) addProp("control-type", req.controlType);
|
|
13025
14067
|
if (req.verificationMethod) addProp("verification-method", req.verificationMethod);
|
|
13026
14068
|
if (req.applicability) addProp("applicability", req.applicability);
|
|
@@ -13048,10 +14090,7 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13048
14090
|
href: o.uri,
|
|
13049
14091
|
rel: "reference"
|
|
13050
14092
|
});
|
|
13051
|
-
else if (typeof o.ref === "string")
|
|
13052
|
-
name: "reference",
|
|
13053
|
-
value: o.ref
|
|
13054
|
-
});
|
|
14093
|
+
else if (typeof o.ref === "string") addProseProp("reference", o.ref);
|
|
13055
14094
|
}
|
|
13056
14095
|
for (const er of req.externalReferences ?? []) if (typeof er.href === "string" && er.href !== "") links.push({
|
|
13057
14096
|
href: er.href,
|
|
@@ -13059,6 +14098,22 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13059
14098
|
});
|
|
13060
14099
|
let title = req.id;
|
|
13061
14100
|
if (req.title && req.title !== "") title = req.title;
|
|
14101
|
+
let resource;
|
|
14102
|
+
if (req.code != null && req.code.trim() !== "") {
|
|
14103
|
+
const resourceUuid = crypto.randomUUID();
|
|
14104
|
+
resource = {
|
|
14105
|
+
uuid: resourceUuid,
|
|
14106
|
+
title: `Check source code for ${req.id}`,
|
|
14107
|
+
base64: {
|
|
14108
|
+
value: Buffer.from(req.code, "utf-8").toString("base64"),
|
|
14109
|
+
"media-type": "text/plain"
|
|
14110
|
+
}
|
|
14111
|
+
};
|
|
14112
|
+
links.push({
|
|
14113
|
+
href: `#${resourceUuid}`,
|
|
14114
|
+
rel: "code"
|
|
14115
|
+
});
|
|
14116
|
+
}
|
|
13062
14117
|
const targetStatus = { state };
|
|
13063
14118
|
if (reason) targetStatus.reason = reason;
|
|
13064
14119
|
const remarks = overrideRemarks(req);
|
|
@@ -13127,16 +14182,32 @@ function requirementToFindingSet(req, timestamp, toolActorUuid, subjects) {
|
|
|
13127
14182
|
return {
|
|
13128
14183
|
finding,
|
|
13129
14184
|
observation,
|
|
13130
|
-
risk
|
|
14185
|
+
risk,
|
|
14186
|
+
resource
|
|
13131
14187
|
};
|
|
13132
14188
|
}
|
|
13133
14189
|
/**
|
|
13134
|
-
*
|
|
13135
|
-
*
|
|
14190
|
+
* Reduces prose to a single line legal as an OSCAL StringDatatype prop value:
|
|
14191
|
+
* the first non-empty line, trimmed, truncated to 120 code points.
|
|
13136
14192
|
*/
|
|
13137
|
-
function
|
|
13138
|
-
|
|
13139
|
-
|
|
14193
|
+
function previewLine(text) {
|
|
14194
|
+
for (let line of text.split("\n")) {
|
|
14195
|
+
line = line.trim();
|
|
14196
|
+
if (line === "") continue;
|
|
14197
|
+
const maxRunes = 120;
|
|
14198
|
+
const runes = Array.from(line);
|
|
14199
|
+
if (runes.length > maxRunes) line = `${runes.slice(0, maxRunes - 3).join("").trim()}...`;
|
|
14200
|
+
return line;
|
|
14201
|
+
}
|
|
14202
|
+
return "";
|
|
14203
|
+
}
|
|
14204
|
+
/**
|
|
14205
|
+
* Derives the OSCAL finding target state/reason from the canonical ladder —
|
|
14206
|
+
* the stored effectiveStatus field is never read (it is an output cache; see
|
|
14207
|
+
* status-determination.md).
|
|
14208
|
+
*/
|
|
14209
|
+
function effectiveState(req) {
|
|
14210
|
+
return oscalStateFromStatus(requirementEffectiveStatus(req));
|
|
13140
14211
|
}
|
|
13141
14212
|
/** Maps a single HDF result status to an OSCAL finding target state/reason. */
|
|
13142
14213
|
function oscalStateFromStatus(status) {
|
|
@@ -13268,62 +14339,6 @@ function formatFloat(n) {
|
|
|
13268
14339
|
return String(n);
|
|
13269
14340
|
}
|
|
13270
14341
|
/**
|
|
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
14342
|
* Finds the "default" labeled description.
|
|
13328
14343
|
*/
|
|
13329
14344
|
function extractDefaultDescription(descriptions) {
|
|
@@ -14522,7 +15537,11 @@ function advisoryURI(publisher, tracking) {
|
|
|
14522
15537
|
/* c8 ignore next */
|
|
14523
15538
|
const id = tracking.id ?? "";
|
|
14524
15539
|
const ns = publisher.namespace;
|
|
14525
|
-
if (ns && id)
|
|
15540
|
+
if (ns && id) {
|
|
15541
|
+
let end = ns.length;
|
|
15542
|
+
while (end > 0 && ns[end - 1] === "/") end--;
|
|
15543
|
+
return `${ns.slice(0, end)}/${id}`;
|
|
15544
|
+
}
|
|
14526
15545
|
return id;
|
|
14527
15546
|
}
|
|
14528
15547
|
function overlap(ids, scope) {
|
|
@@ -17017,6 +18036,6 @@ function capStixExternalRefs(container, label) {
|
|
|
17017
18036
|
container.externalReferences = [...other, ...capped];
|
|
17018
18037
|
}
|
|
17019
18038
|
//#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,
|
|
18039
|
+
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
18040
|
|
|
17022
18041
|
//# sourceMappingURL=index.js.map
|