@mitre/hdf-converters 3.7.1 → 3.7.2-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 +37 -4
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +1 -1
- package/dist/index.d.ts +40 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2572 -231
- package/dist/index.js.map +1 -1
- package/dist/{register-all-BEuKQGEZ.js → register-all-BWm_HvZf.js} +72 -1
- package/dist/register-all-BWm_HvZf.js.map +1 -0
- package/package.json +11 -9
- package/dist/register-all-BEuKQGEZ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { n as detectFormat, o as isCheckovTestCaseName, r as detectConverter, t as registerAllFingerprints } from "./register-all-
|
|
1
|
+
import { n as detectFormat, o as isCheckovTestCaseName, r as detectConverter, t as registerAllFingerprints } from "./register-all-BWm_HvZf.js";
|
|
2
2
|
import { flattenOverlays } from "@mitre/hdf-parsers";
|
|
3
|
-
import { buildCsv, buildXml, canonicalJson, computeCvssScore, computeEffectiveStatus, cvssScoreToSeverity, encodeBase64Utf8, formatJsonNumber, formatTimestamp, formatTimestampSeconds, governingStatusOverride, impactToSeverity, isUnratedSeverity, normalizeHdfTimestamps, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, severityToImpact, severityToImpactWithAliases, sha256, stripHtml, stripHtml as stripHTML, trimUtcFraction, validateInputSize, worstStatus, xmlSafeText } from "@mitre/hdf-utilities";
|
|
3
|
+
import { CWE_PATTERN as CWE_PATTERN$1, absentIfGoZeroTime, buildCsv, buildXml, canonicalJson, computeCvssScore, computeEffectiveImpact, computeEffectiveStatus, cvssScoreToSeverity, encodeBase64Utf8, extractCWEIDs, formatJsonNumber, formatTimestamp, formatTimestampSeconds, governingImpactOverrideIndex, governingOverrideIndex, governingStatusOverride, governingStatusOverrideIndex, impactToSeverity, isGoZeroTime, isUnratedSeverity, normalizeHdfTimestamps, parseCsv, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, replaceHtmlTags, roundImpact, severityToImpact, severityToImpactWithAliases, sha256, stripHtml, stripHtml as stripHTML, trimUtcFraction, validateInputSize, worstStatus, xmlSafeText } from "@mitre/hdf-utilities";
|
|
4
4
|
import { Applicability, AuthorizationStatus, CVSSSeverity, CategorizationLevel, CloudProvider, ControlType, Ecosystem, EvidenceType, HashAlgorithm, IdentityType, Justification, MilestoneStatus, OverrideType, PlanType, ResultStatus, Severity, TargetType, VerificationMethodEnum, Version, affectedPackageSchema, amendmentsSchema, bomSchema, commonSchema, comparisonSchema, componentSchema, createDescription, createMinimalBaseline, createRequirement, createResult, cvssSchema, dataFlowSchema, epssSchema, eventsSchema, extensionsSchema, hdfAmendmentsSchema, hdfBaselineSchema, hdfComparisonSchema, hdfEvidencePackageSchema, hdfPlanSchema, hdfRequirementChangeEventSchema, hdfResultsSchema, hdfSystemSchema, kevSchema, parameterSchema, planSchema, platformSchema, resultSchema, runnerSchema, severityToImpact as severityToImpact$1, statisticsSchema, systemSchema, targetSchema } from "@mitre/hdf-schema";
|
|
5
|
-
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, SUPPORTED_NIST_REVISIONS, awsConfigMappedRevisions, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getAwsConfigNistControlsBySubstring, getCCINistMappings, getCheckovCciNistMapping, getCurrentNistRevision, getCweNistControl, getHipcheckNistControls, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistExists, nistToCci, normalizeNistId } from "@mitre/hdf-mappings";
|
|
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, SUPPORTED_NIST_REVISIONS, awsConfigMappedRevisions, getAwsConfigNistControlByIdentifier, getAwsConfigNistControlByName, getAwsConfigNistControlsBySubstring, getCCINistMappings, getCheckovCciNistMapping, getCurrentNistRevision, getCweNistControl, getHadolintNistMapping, getHipcheckNistControls, getNessusNistControl, getNiktoNistControl, getOwaspNistControl, getScoutsuiteNistControl, isNistStrict, nistExists, nistToCci, normalizeNistId } from "@mitre/hdf-mappings";
|
|
6
6
|
import Ajv from "ajv";
|
|
7
7
|
import addFormats from "ajv-formats";
|
|
8
|
+
import { addCounts, calculateCompliance, countControlsByStatus, deriveSeverity, severityBucket, severityTotals } from "@mitre/hdf-engine";
|
|
8
9
|
//#region ../hdf-validators/dist/index.js
|
|
9
10
|
/**
|
|
10
11
|
* Create and configure Ajv instance with all HDF schemas
|
|
@@ -145,6 +146,20 @@ async function chainOverrides(overrides) {
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
//#endregion
|
|
149
|
+
//#region shared/typescript/unrated.ts
|
|
150
|
+
/**
|
|
151
|
+
* The tag a roll-up leaves on a requirement whose severity the source never
|
|
152
|
+
* rated, and the value it carries. A leaf module: converterutil.ts re-exports
|
|
153
|
+
* these as part of its surface, and rollup.ts imports them here rather than
|
|
154
|
+
* from the barrel that re-exports rollup itself — which is the cycle a second
|
|
155
|
+
* declaration used to work around.
|
|
156
|
+
*/
|
|
157
|
+
const UNRATED_SEVERITY_TAG = "severity_rating";
|
|
158
|
+
const UNRATED_SEVERITY_VALUE = "unrated";
|
|
159
|
+
function markUnratedSeverity(tags, severity) {
|
|
160
|
+
if (isUnratedSeverity(severity)) tags[UNRATED_SEVERITY_TAG] = UNRATED_SEVERITY_VALUE;
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
148
163
|
//#region shared/typescript/converterutil.ts
|
|
149
164
|
/**
|
|
150
165
|
* Shared utilities for TypeScript HDF converters.
|
|
@@ -154,6 +169,18 @@ async function chainOverrides(overrides) {
|
|
|
154
169
|
* - Re-exports of shared constants and utilities
|
|
155
170
|
*/
|
|
156
171
|
/**
|
|
172
|
+
* The identity a finding's affected package carries as its resourceId: the purl
|
|
173
|
+
* when the source gives one, else name@version, else the bare name, else ''.
|
|
174
|
+
* One rule for every converter that stamps a package, so a downstream join on
|
|
175
|
+
* resourceId meets the same string whichever tool produced it.
|
|
176
|
+
* Parity: PackageInstanceID in shared/go/converterutil.go.
|
|
177
|
+
*/
|
|
178
|
+
function packageInstanceId$2(purl, name, version) {
|
|
179
|
+
if (purl) return purl;
|
|
180
|
+
if (!name) return "";
|
|
181
|
+
return version ? `${name}@${version}` : name;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
157
184
|
* Compute a SHA-256 checksum of raw converter input.
|
|
158
185
|
*
|
|
159
186
|
* Wraps the common 3-line pattern used in every converter into a single call.
|
|
@@ -294,22 +321,6 @@ function mapCWEToNIST(cweIDs, fallback) {
|
|
|
294
321
|
}
|
|
295
322
|
return controls.size > 0 ? [...controls].sort() : fallback;
|
|
296
323
|
}
|
|
297
|
-
/** Matches CWE identifiers like "CWE-79", "CWE 89", "cwe22"; group 1 is the number. */
|
|
298
|
-
const CWE_PATTERN$1 = /CWE[- ]?(\d+)/gi;
|
|
299
|
-
/**
|
|
300
|
-
* Extract all numeric CWE IDs from text.
|
|
301
|
-
* Returns deduplicated sorted array of numeric ID strings (e.g., ["79", "89"]).
|
|
302
|
-
*
|
|
303
|
-
* @param text - Text potentially containing CWE identifiers
|
|
304
|
-
* @returns Sorted, deduplicated numeric CWE ID strings
|
|
305
|
-
*/
|
|
306
|
-
function extractCWEIDs(text) {
|
|
307
|
-
const matches = [...text.matchAll(CWE_PATTERN$1)];
|
|
308
|
-
if (matches.length === 0) return [];
|
|
309
|
-
const ids = [...new Set(matches.map((m) => m[1]))];
|
|
310
|
-
ids.sort();
|
|
311
|
-
return ids;
|
|
312
|
-
}
|
|
313
324
|
/**
|
|
314
325
|
* Return the first candidate whose content is not empty or whitespace-only,
|
|
315
326
|
* as-is (the emptiness test trims, the returned value does not). Returns '' when
|
|
@@ -714,16 +725,6 @@ function buildNoFindingsRequirement(id, codeDesc, startTime) {
|
|
|
714
725
|
};
|
|
715
726
|
}
|
|
716
727
|
/**
|
|
717
|
-
* Shared marker converters emit when a source severity is unrated
|
|
718
|
-
* (isUnratedSeverity), so a defaulted impact stays distinguishable from a
|
|
719
|
-
* genuine rated medium. TS peer of MarkUnratedSeverity in converterutil.go.
|
|
720
|
-
*/
|
|
721
|
-
const UNRATED_SEVERITY_TAG = "severity_rating";
|
|
722
|
-
const UNRATED_SEVERITY_VALUE = "unrated";
|
|
723
|
-
function markUnratedSeverity(tags, severity) {
|
|
724
|
-
if (isUnratedSeverity(severity)) tags[UNRATED_SEVERITY_TAG] = UNRATED_SEVERITY_VALUE;
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
728
|
* The schema violations Go's typed decode would also reject.
|
|
728
729
|
*
|
|
729
730
|
* encoding/json refuses exactly two things: a JSON value whose type does not fit
|
|
@@ -1207,7 +1208,7 @@ function cvssSeverityFromScore(score) {
|
|
|
1207
1208
|
* number is supplied; baseVector and source are set only when non-empty. Consumers
|
|
1208
1209
|
* needing threat/environmental/computed enrichment set those fields on the result.
|
|
1209
1210
|
*/
|
|
1210
|
-
function buildCvss$
|
|
1211
|
+
function buildCvss$4(input) {
|
|
1211
1212
|
const cv = { version: input.version };
|
|
1212
1213
|
if (typeof input.baseScore === "number" && Number.isFinite(input.baseScore)) {
|
|
1213
1214
|
cv.baseScore = input.baseScore;
|
|
@@ -1467,7 +1468,7 @@ function vulnCvss(f) {
|
|
|
1467
1468
|
const out = [];
|
|
1468
1469
|
for (const v of f.Vulnerabilities ?? []) for (const c of v.Cvss ?? []) {
|
|
1469
1470
|
if (typeof c.BaseScore !== "number") continue;
|
|
1470
|
-
out.push(buildCvss$
|
|
1471
|
+
out.push(buildCvss$4({
|
|
1471
1472
|
version: cvssVersionFromString(c.Version),
|
|
1472
1473
|
baseScore: c.BaseScore,
|
|
1473
1474
|
source: c.Source
|
|
@@ -1526,7 +1527,7 @@ function trivyLocation(o) {
|
|
|
1526
1527
|
if (el && el !== "0" && el !== sl) loc += `-${el}`;
|
|
1527
1528
|
return loc;
|
|
1528
1529
|
}
|
|
1529
|
-
function buildRequirement$
|
|
1530
|
+
function buildRequirement$20(id, group) {
|
|
1530
1531
|
const primary = group[0];
|
|
1531
1532
|
const impact = Math.max(...group.map(findingImpact));
|
|
1532
1533
|
const descriptions = [{
|
|
@@ -1573,7 +1574,7 @@ function buildBaseline$1(name, findings, resultsChecksum) {
|
|
|
1573
1574
|
groups.set(id, [f]);
|
|
1574
1575
|
}
|
|
1575
1576
|
}
|
|
1576
|
-
const requirements = order.map((id) => buildRequirement$
|
|
1577
|
+
const requirements = order.map((id) => buildRequirement$20(id, groups.get(id)));
|
|
1577
1578
|
return createMinimalBaseline(name, requirements, { resultsChecksum });
|
|
1578
1579
|
}
|
|
1579
1580
|
/** Converts an ASFF document to HDF Results JSON. */
|
|
@@ -1621,6 +1622,112 @@ async function convertAsffToHdf(input, converterVersion = "1.0.0") {
|
|
|
1621
1622
|
return serializeHdf(hdf);
|
|
1622
1623
|
}
|
|
1623
1624
|
//#endregion
|
|
1625
|
+
//#region shared/typescript/status.ts
|
|
1626
|
+
/**
|
|
1627
|
+
* Bridge from schema-typed requirements onto the canonical effective-status
|
|
1628
|
+
* helper in @mitre/hdf-utilities, so every consumer computes status through
|
|
1629
|
+
* the single shared implementation (twin of shared/go/status.go). The stored
|
|
1630
|
+
* effectiveStatus field is never read — it is an output cache (see
|
|
1631
|
+
* status-determination.md).
|
|
1632
|
+
*/
|
|
1633
|
+
/**
|
|
1634
|
+
* RFC3339 string from a schema timestamp (quicktype Date or raw string), with
|
|
1635
|
+
* Go's zero time read as absent.
|
|
1636
|
+
*/
|
|
1637
|
+
function schemaTimestamp(v) {
|
|
1638
|
+
if (v instanceof Date) return absentIfGoZeroTime(v.toISOString());
|
|
1639
|
+
if (typeof v === "string" && v !== "") return absentIfGoZeroTime(v);
|
|
1640
|
+
}
|
|
1641
|
+
/** Maps a requirement onto the canonical effective-status input shape. */
|
|
1642
|
+
function requirementStatusInput(req) {
|
|
1643
|
+
return {
|
|
1644
|
+
impact: req.impact ?? 0,
|
|
1645
|
+
resultStatuses: (req.results ?? []).filter((r) => r != null).map((r) => String(r.status)),
|
|
1646
|
+
overrides: (req.statusOverrides ?? []).filter((o) => o != null).map((o) => ({
|
|
1647
|
+
status: o.status ? String(o.status) : void 0,
|
|
1648
|
+
appliedAt: schemaTimestamp(o.appliedAt),
|
|
1649
|
+
expiresAt: schemaTimestamp(o.expiresAt),
|
|
1650
|
+
impact: o.impact?.value
|
|
1651
|
+
}))
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
/** The requirement's canonical effective status via the shared ladder. */
|
|
1655
|
+
function requirementEffectiveStatus(req) {
|
|
1656
|
+
return computeEffectiveStatus(requirementStatusInput(req));
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* The requirement's canonical effective impact via the shared ladder: the
|
|
1660
|
+
* governing non-expired impact override's value, else the requirement's own. The
|
|
1661
|
+
* stored effectiveImpact field is an output cache and is never read, exactly as
|
|
1662
|
+
* effectiveStatus is not. Expiry is judged at now, else the clock, for a
|
|
1663
|
+
* renderer that promises assessment-time output. Parity: RequirementEffectiveImpact
|
|
1664
|
+
* and RequirementEffectiveImpactAt in shared/go.
|
|
1665
|
+
*/
|
|
1666
|
+
function requirementEffectiveImpact(req, now) {
|
|
1667
|
+
return computeEffectiveImpact(requirementStatusInput(req), now);
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* The override that governs a requirement — the most recently applied
|
|
1671
|
+
* non-expired one, whatever it carries — or undefined when none does.
|
|
1672
|
+
*
|
|
1673
|
+
* Resolution is by appliedAt, never by array position. The schema's description
|
|
1674
|
+
* says the most recent override "should be first in array", but nothing in this
|
|
1675
|
+
* repo sorts and both writers append, so on a document our own tooling amended
|
|
1676
|
+
* twice the newest override is LAST and statusOverrides[0] is the oldest.
|
|
1677
|
+
*
|
|
1678
|
+
* Parity: GoverningOverride in shared/go/status.go.
|
|
1679
|
+
*/
|
|
1680
|
+
function governingOverride(req, now) {
|
|
1681
|
+
const overrides = req.statusOverrides ?? [];
|
|
1682
|
+
const i = governingOverrideIndex(overrides.map((o) => ({
|
|
1683
|
+
status: o?.status ? String(o.status) : void 0,
|
|
1684
|
+
appliedAt: schemaTimestamp(o?.appliedAt),
|
|
1685
|
+
expiresAt: schemaTimestamp(o?.expiresAt)
|
|
1686
|
+
})), () => true, now);
|
|
1687
|
+
return i >= 0 ? overrides[i] ?? void 0 : void 0;
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* The override that governs a requirement's IMPACT — the most recently applied
|
|
1691
|
+
* non-expired one CARRYING an impact — or undefined when none does. Eligibility
|
|
1692
|
+
* is per field, so a newer override that says nothing about impact does not
|
|
1693
|
+
* displace an older re-score.
|
|
1694
|
+
*
|
|
1695
|
+
* Parity: GoverningImpactOverrideIndex usage in shared/go/checklist.
|
|
1696
|
+
*/
|
|
1697
|
+
function governingImpactOverride(req, now) {
|
|
1698
|
+
const overrides = (req.statusOverrides ?? []).filter((o) => o != null);
|
|
1699
|
+
const i = governingImpactOverrideIndex(overrides.map((o) => ({
|
|
1700
|
+
appliedAt: schemaTimestamp(o.appliedAt),
|
|
1701
|
+
expiresAt: schemaTimestamp(o.expiresAt),
|
|
1702
|
+
impact: o.impact?.value
|
|
1703
|
+
})), now);
|
|
1704
|
+
return i >= 0 ? overrides[i] : void 0;
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* The type of the override that governs a requirement, or '' when none does —
|
|
1708
|
+
* the disposition twin of requirementEffectiveStatus and
|
|
1709
|
+
* requirementEffectiveImpact.
|
|
1710
|
+
*
|
|
1711
|
+
* Whenever the requirement carries overrides, they decide: the stored
|
|
1712
|
+
* disposition field is an output cache that can disagree with them, or be stale,
|
|
1713
|
+
* and it is not read. The one exception is a requirement carrying NO overrides
|
|
1714
|
+
* at all, where the stored field is the only evidence in the document.
|
|
1715
|
+
*
|
|
1716
|
+
* This reads statusOverrides only. hdf-engine's filter and hdf-diff's checksum
|
|
1717
|
+
* fold poams[] into the same governing set and report 'poam' when a plan
|
|
1718
|
+
* governs; an export does not, because the exporters carry the plan separately
|
|
1719
|
+
* and a disposition of 'poam' would duplicate it in a column that names an
|
|
1720
|
+
* override type. Pinned by the "does not read poams" test.
|
|
1721
|
+
*
|
|
1722
|
+
* Parity: RequirementDisposition in shared/go/status.go.
|
|
1723
|
+
*/
|
|
1724
|
+
function requirementDisposition(req, now) {
|
|
1725
|
+
const governing = governingOverride(req, now);
|
|
1726
|
+
if (governing) return String(governing.type ?? "");
|
|
1727
|
+
if ((req.statusOverrides ?? []).length === 0 && req.disposition) return String(req.disposition);
|
|
1728
|
+
return "";
|
|
1729
|
+
}
|
|
1730
|
+
//#endregion
|
|
1624
1731
|
//#region converters/sarif-to-hdf/typescript/converter.ts
|
|
1625
1732
|
const SARIF_ALIASES = {
|
|
1626
1733
|
error: .7,
|
|
@@ -1703,9 +1810,9 @@ function convertResultGroup(ruleId, rule, sarifResults, timestamp) {
|
|
|
1703
1810
|
const sourceLocation = firstResult.locations && firstResult.locations.length > 0 ? extractSourceLocation$1(firstResult.locations[0]) : void 0;
|
|
1704
1811
|
const results = [];
|
|
1705
1812
|
for (const sr of sarifResults) results.push(...convertSarifResultToHDFResults(sr));
|
|
1706
|
-
const descriptions = buildDescriptions$
|
|
1813
|
+
const descriptions = buildDescriptions$3(description, rule, firstResult);
|
|
1707
1814
|
const allSuppressions = sarifResults.flatMap((sr) => sr.suppressions ?? []);
|
|
1708
|
-
const options = { tags: buildTags$
|
|
1815
|
+
const options = { tags: buildTags$5(firstResult, rule, ruleLevel, cweIds, nistControls, cciControls, allSuppressions) };
|
|
1709
1816
|
if (sourceLocation) options.sourceLocation = sourceLocation;
|
|
1710
1817
|
const req = createRequirement(ruleId, title, descriptions, impact, results, options);
|
|
1711
1818
|
req.severity = impactToSeverity(impact);
|
|
@@ -1923,7 +2030,7 @@ function rollupStatus(statuses) {
|
|
|
1923
2030
|
}
|
|
1924
2031
|
function governingDisposition(overrides, effective) {
|
|
1925
2032
|
for (const ov of overrides) if (ov.status === effective) return ov.type;
|
|
1926
|
-
return overrides[0].type;
|
|
2033
|
+
return governingOverride({ statusOverrides: overrides })?.type ?? overrides[0].type;
|
|
1927
2034
|
}
|
|
1928
2035
|
function extractBacktrace(codeFlows) {
|
|
1929
2036
|
if (!codeFlows || codeFlows.length === 0) return [];
|
|
@@ -1939,7 +2046,7 @@ function extractBacktrace(codeFlows) {
|
|
|
1939
2046
|
}
|
|
1940
2047
|
return backtrace;
|
|
1941
2048
|
}
|
|
1942
|
-
function buildDescriptions$
|
|
2049
|
+
function buildDescriptions$3(defaultDesc, rule, result) {
|
|
1943
2050
|
const descriptions = [createDescription("default", defaultDesc)];
|
|
1944
2051
|
if (rule) {
|
|
1945
2052
|
if (rule.fullDescription?.text) descriptions.push(createDescription("rationale", rule.fullDescription.text));
|
|
@@ -1949,7 +2056,7 @@ function buildDescriptions$2(defaultDesc, rule, result) {
|
|
|
1949
2056
|
if (result.fixes && result.fixes.length > 0 && result.fixes[0]?.description?.text) descriptions.push(createDescription("fix", result.fixes[0].description.text));
|
|
1950
2057
|
return descriptions;
|
|
1951
2058
|
}
|
|
1952
|
-
function buildTags$
|
|
2059
|
+
function buildTags$5(result, rule, resolvedLevel, cweIds, nistControls, cciControls, allSuppressions) {
|
|
1953
2060
|
const tags = {
|
|
1954
2061
|
severity: resolvedLevel,
|
|
1955
2062
|
cwe: cweIds,
|
|
@@ -2118,7 +2225,7 @@ function testCaseToRequirement(tc, scanTime) {
|
|
|
2118
2225
|
const { status, message } = resolveStatus(tc);
|
|
2119
2226
|
const result = {
|
|
2120
2227
|
status,
|
|
2121
|
-
codeDesc: buildCodeDesc$
|
|
2228
|
+
codeDesc: buildCodeDesc$11(tc),
|
|
2122
2229
|
startTime: scanTime
|
|
2123
2230
|
};
|
|
2124
2231
|
if (message !== void 0) result.message = message;
|
|
@@ -2206,7 +2313,7 @@ function buildFailureMessage(message, typeName, body) {
|
|
|
2206
2313
|
if (body) result += "\n" + decodeXmlEntities$6(body);
|
|
2207
2314
|
return result;
|
|
2208
2315
|
}
|
|
2209
|
-
function buildCodeDesc$
|
|
2316
|
+
function buildCodeDesc$11(tc) {
|
|
2210
2317
|
if (tc["@_classname"]) return `${decodeXmlEntities$6(tc["@_classname"])} :: ${decodeXmlEntities$6(tc["@_name"])}`;
|
|
2211
2318
|
return decodeXmlEntities$6(tc["@_name"]);
|
|
2212
2319
|
}
|
|
@@ -2552,7 +2659,7 @@ function ruleResultToRequirement(rr, ruleIndex, groupIndex, scanTime) {
|
|
|
2552
2659
|
const req = createRequirement(id, title, descriptions, impact, [result], { tags });
|
|
2553
2660
|
const code = buildCheckCode(check);
|
|
2554
2661
|
if (code) req.code = code;
|
|
2555
|
-
const refs = buildRefs$
|
|
2662
|
+
const refs = buildRefs$11(ruleDef?.reference);
|
|
2556
2663
|
if (refs.length > 0) req.refs = refs;
|
|
2557
2664
|
const hdfSeverity = xccdfSeverityToHdf(severity);
|
|
2558
2665
|
if (hdfSeverity) req.severity = hdfSeverity;
|
|
@@ -2709,7 +2816,7 @@ function checkDescriptionData(check) {
|
|
|
2709
2816
|
* href, when present, populates url. References that carry nothing are skipped.
|
|
2710
2817
|
* Inner keys are inserted alphabetically to match Go's map JSON ordering.
|
|
2711
2818
|
*/
|
|
2712
|
-
function buildRefs$
|
|
2819
|
+
function buildRefs$11(refs) {
|
|
2713
2820
|
if (!refs) return [];
|
|
2714
2821
|
const out = [];
|
|
2715
2822
|
for (const r of refs) {
|
|
@@ -3367,36 +3474,6 @@ function setIf$1(m, key, val) {
|
|
|
3367
3474
|
if (val) m[key] = val;
|
|
3368
3475
|
}
|
|
3369
3476
|
//#endregion
|
|
3370
|
-
//#region shared/typescript/status.ts
|
|
3371
|
-
/**
|
|
3372
|
-
* Bridge from schema-typed requirements onto the canonical effective-status
|
|
3373
|
-
* helper in @mitre/hdf-utilities, so every consumer computes status through
|
|
3374
|
-
* the single shared implementation (twin of shared/go/status.go). The stored
|
|
3375
|
-
* effectiveStatus field is never read — it is an output cache (see
|
|
3376
|
-
* status-determination.md).
|
|
3377
|
-
*/
|
|
3378
|
-
/** RFC3339 string from a schema timestamp (quicktype Date or raw string). */
|
|
3379
|
-
function stamp(v) {
|
|
3380
|
-
if (v instanceof Date) return v.toISOString();
|
|
3381
|
-
if (typeof v === "string" && v !== "") return v;
|
|
3382
|
-
}
|
|
3383
|
-
/** Maps a requirement onto the canonical effective-status input shape. */
|
|
3384
|
-
function requirementStatusInput(req) {
|
|
3385
|
-
return {
|
|
3386
|
-
impact: req.impact ?? 0,
|
|
3387
|
-
resultStatuses: (req.results ?? []).filter((r) => r != null).map((r) => String(r.status)),
|
|
3388
|
-
overrides: (req.statusOverrides ?? []).filter((o) => o != null).map((o) => ({
|
|
3389
|
-
status: o.status ? String(o.status) : void 0,
|
|
3390
|
-
appliedAt: stamp(o.appliedAt),
|
|
3391
|
-
expiresAt: stamp(o.expiresAt)
|
|
3392
|
-
}))
|
|
3393
|
-
};
|
|
3394
|
-
}
|
|
3395
|
-
/** The requirement's canonical effective status via the shared ladder. */
|
|
3396
|
-
function requirementEffectiveStatus(req) {
|
|
3397
|
-
return computeEffectiveStatus(requirementStatusInput(req));
|
|
3398
|
-
}
|
|
3399
|
-
//#endregion
|
|
3400
3477
|
//#region shared/typescript/checklist/from-hdf.ts
|
|
3401
3478
|
/**
|
|
3402
3479
|
* Map HDF Results back to the format-neutral Checklist model. When the HDF
|
|
@@ -3519,7 +3596,8 @@ function composeComments(tags, req) {
|
|
|
3519
3596
|
function overrideProvenance(req) {
|
|
3520
3597
|
const overrides = req.statusOverrides ?? [];
|
|
3521
3598
|
if (overrides.length > 0) return overrides.map(formatOverride).join("\n");
|
|
3522
|
-
|
|
3599
|
+
const disposition = requirementDisposition(req);
|
|
3600
|
+
if (disposition) return `Disposition: ${disposition}`;
|
|
3523
3601
|
return "";
|
|
3524
3602
|
}
|
|
3525
3603
|
function formatOverride(o) {
|
|
@@ -3527,22 +3605,23 @@ function formatOverride(o) {
|
|
|
3527
3605
|
if (o.reason) s += `: ${o.reason}`;
|
|
3528
3606
|
const meta = [];
|
|
3529
3607
|
if (o.appliedBy?.identifier) meta.push(`by ${o.appliedBy.identifier}`);
|
|
3530
|
-
const applied = hdfTime(o.appliedAt);
|
|
3608
|
+
const applied = hdfTime(schemaTimestamp(o.appliedAt));
|
|
3531
3609
|
if (applied) meta.push(`applied ${formatTimestamp(applied)}`);
|
|
3532
|
-
const expires = hdfTime(o.expiresAt);
|
|
3610
|
+
const expires = hdfTime(schemaTimestamp(o.expiresAt));
|
|
3533
3611
|
if (expires) meta.push(`expires ${formatTimestamp(expires)}`);
|
|
3534
3612
|
if (meta.length > 0) s += ` (${meta.join(", ")})`;
|
|
3535
3613
|
return s;
|
|
3536
3614
|
}
|
|
3537
3615
|
function overrideSeverity(req) {
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
justification: o.reason ?? ""
|
|
3541
|
-
};
|
|
3542
|
-
return {
|
|
3616
|
+
const o = governingImpactOverride(req);
|
|
3617
|
+
if (!o) return {
|
|
3543
3618
|
severity: "",
|
|
3544
3619
|
justification: ""
|
|
3545
3620
|
};
|
|
3621
|
+
return {
|
|
3622
|
+
severity: cklSeverityOrFloor(o.impact.value),
|
|
3623
|
+
justification: o.reason ?? ""
|
|
3624
|
+
};
|
|
3546
3625
|
}
|
|
3547
3626
|
function cklSeverityFromImpact(impact) {
|
|
3548
3627
|
switch (impactToSeverity(impact)) {
|
|
@@ -3703,7 +3782,7 @@ const NIST_MAPPING_TAG = "nistMapping";
|
|
|
3703
3782
|
const NIST_MAPPING_TABLE = "mapped";
|
|
3704
3783
|
const NIST_MAPPING_CWE = "cwe-derived";
|
|
3705
3784
|
const NIST_MAPPING_FALLBACK = "static-fallback";
|
|
3706
|
-
function impactFor$
|
|
3785
|
+
function impactFor$2(severity) {
|
|
3707
3786
|
return severityToImpactWithAliases(severity, KICS_SEVERITY_ALIASES, DEFAULT_IMPACT$1);
|
|
3708
3787
|
}
|
|
3709
3788
|
/** KICS emits the bare number, e.g. "778". Normalize before the lookup. */
|
|
@@ -3826,7 +3905,7 @@ function buildCoverageRequirement$1(report, startTime) {
|
|
|
3826
3905
|
}
|
|
3827
3906
|
};
|
|
3828
3907
|
}
|
|
3829
|
-
function buildRequirement$
|
|
3908
|
+
function buildRequirement$19(query, startTime) {
|
|
3830
3909
|
const tags = tagsFor$1(query);
|
|
3831
3910
|
const descriptions = [{
|
|
3832
3911
|
label: "default",
|
|
@@ -3838,7 +3917,7 @@ function buildRequirement$18(query, startTime) {
|
|
|
3838
3917
|
message: evidenceFor(file),
|
|
3839
3918
|
startTime
|
|
3840
3919
|
}));
|
|
3841
|
-
const requirement = createRequirement(query.query_id || query.query_name || "unknown", query.query_name || query.query_id || "Unnamed KICS query", descriptions, impactFor$
|
|
3920
|
+
const requirement = createRequirement(query.query_id || query.query_name || "unknown", query.query_name || query.query_id || "Unnamed KICS query", descriptions, impactFor$2(query.severity), results, { tags });
|
|
3842
3921
|
requirement.verificationMethod = VerificationMethodEnum.Automated;
|
|
3843
3922
|
const controlType = deriveControlTypeFromTags(tags.nist);
|
|
3844
3923
|
if (controlType !== void 0) requirement.controlType = controlType;
|
|
@@ -3861,7 +3940,7 @@ async function convertKicsToHdf(input, converterVersion = "1.0.0") {
|
|
|
3861
3940
|
const report = parseJSON(input);
|
|
3862
3941
|
if (!Array.isArray(report.queries) || typeof report.kics_version !== "string") throw new Error("kics: input does not look like a KICS report");
|
|
3863
3942
|
const startTime = /* @__PURE__ */ new Date();
|
|
3864
|
-
const requirements = report.queries.filter((q) => Array.isArray(q.files) && q.files.length > 0).map((q) => buildRequirement$
|
|
3943
|
+
const requirements = report.queries.filter((q) => Array.isArray(q.files) && q.files.length > 0).map((q) => buildRequirement$19(q, startTime));
|
|
3865
3944
|
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("kics-no-findings", `KICS scanned ${report.files_scanned ?? 0} file(s) and reported no findings.`, startTime));
|
|
3866
3945
|
requirements.push(buildCoverageRequirement$1(report, startTime));
|
|
3867
3946
|
const baseline = createMinimalBaseline("KICS Scan", requirements, {
|
|
@@ -3937,7 +4016,7 @@ function nistControlsFor(metadata) {
|
|
|
3937
4016
|
const deduped = [...new Set(controls)];
|
|
3938
4017
|
return deduped.length > 0 ? deduped : [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS$1];
|
|
3939
4018
|
}
|
|
3940
|
-
function impactFor(result) {
|
|
4019
|
+
function impactFor$1(result) {
|
|
3941
4020
|
const severity = result.extra?.severity;
|
|
3942
4021
|
if (!isPresent(severity)) return DEFAULT_IMPACT;
|
|
3943
4022
|
return severityToImpactWithAliases(severity, SEMGREP_SEVERITY_ALIASES, DEFAULT_IMPACT);
|
|
@@ -3949,7 +4028,7 @@ function impactFor(result) {
|
|
|
3949
4028
|
*/
|
|
3950
4029
|
function severityFor(result) {
|
|
3951
4030
|
if (!isPresent(result.extra?.severity)) return;
|
|
3952
|
-
return impactToSeverity(impactFor(result));
|
|
4031
|
+
return impactToSeverity(impactFor$1(result));
|
|
3953
4032
|
}
|
|
3954
4033
|
/**
|
|
3955
4034
|
* Semgrep rule ids are dotted paths whose final segment is the rule name. The
|
|
@@ -3962,7 +4041,7 @@ function titleFor(checkId) {
|
|
|
3962
4041
|
const segments = checkId.split(".").filter((segment) => segment.length > 0);
|
|
3963
4042
|
return (segments[segments.length - 1] ?? checkId).split(/[-_]+/).filter((word) => word.length > 0).map((word) => word[0].toUpperCase() + word.slice(1)).join(" ");
|
|
3964
4043
|
}
|
|
3965
|
-
function codeDescFor(result) {
|
|
4044
|
+
function codeDescFor$1(result) {
|
|
3966
4045
|
const path = isPresent(result.path) ? result.path : "unknown";
|
|
3967
4046
|
const startLine = lineOf(result.start);
|
|
3968
4047
|
const endLine = lineOf(result.end);
|
|
@@ -4063,7 +4142,7 @@ function codeFor(result) {
|
|
|
4063
4142
|
return JSON.stringify(envelope, null, 2);
|
|
4064
4143
|
}
|
|
4065
4144
|
/** Every occurrence of one rule becomes a result under a single requirement. */
|
|
4066
|
-
function buildRequirement$
|
|
4145
|
+
function buildRequirement$18(checkId, results, startTime) {
|
|
4067
4146
|
const representative = results[0];
|
|
4068
4147
|
const metadata = representative.extra?.metadata ?? {};
|
|
4069
4148
|
const message = representative.extra?.message;
|
|
@@ -4073,7 +4152,7 @@ function buildRequirement$17(checkId, results, startTime) {
|
|
|
4073
4152
|
}];
|
|
4074
4153
|
const requirementResults = results.map((result) => ({
|
|
4075
4154
|
status: ResultStatus.Failed,
|
|
4076
|
-
codeDesc: codeDescFor(result),
|
|
4155
|
+
codeDesc: codeDescFor$1(result),
|
|
4077
4156
|
message: messageFor(result),
|
|
4078
4157
|
startTime
|
|
4079
4158
|
}));
|
|
@@ -4081,7 +4160,7 @@ function buildRequirement$17(checkId, results, startTime) {
|
|
|
4081
4160
|
const cwe = cweCatalogFor(metadata);
|
|
4082
4161
|
const refs = refsFor(metadata);
|
|
4083
4162
|
const sourceLocation = sourceLocationFor(representative);
|
|
4084
|
-
const requirement = createRequirement(checkId, titleFor(checkId), descriptions, impactFor(representative), requirementResults, {
|
|
4163
|
+
const requirement = createRequirement(checkId, titleFor(checkId), descriptions, impactFor$1(representative), requirementResults, {
|
|
4085
4164
|
tags,
|
|
4086
4165
|
code: codeFor(representative),
|
|
4087
4166
|
...cwe.length > 0 ? { cwe } : {},
|
|
@@ -4182,7 +4261,7 @@ async function convertSemgrepToHdf(input, converterVersion = "1.0.0") {
|
|
|
4182
4261
|
else groups.set(result.check_id, [result]);
|
|
4183
4262
|
}
|
|
4184
4263
|
const requirements = [];
|
|
4185
|
-
for (const [checkId, results] of groups) requirements.push(buildRequirement$
|
|
4264
|
+
for (const [checkId, results] of groups) requirements.push(buildRequirement$18(checkId, results, startTime));
|
|
4186
4265
|
if (groups.size === 0) {
|
|
4187
4266
|
const scanned = scannedCount(report);
|
|
4188
4267
|
requirements.push(buildNoFindingsRequirement("semgrep-no-findings", `Semgrep scanned ${scanned} file(s) and reported no findings.`, startTime));
|
|
@@ -4255,7 +4334,7 @@ function buildReferenceCode(r) {
|
|
|
4255
4334
|
if (r.url) o.url = r.url;
|
|
4256
4335
|
return o;
|
|
4257
4336
|
}
|
|
4258
|
-
function buildVulnCode$
|
|
4337
|
+
function buildVulnCode$3(v) {
|
|
4259
4338
|
const o = {};
|
|
4260
4339
|
if (v.id) o.id = v.id;
|
|
4261
4340
|
if (v.title) o.title = v.title;
|
|
@@ -4331,7 +4410,7 @@ function synthesizePurl(ecosystem, name, version) {
|
|
|
4331
4410
|
*/
|
|
4332
4411
|
function buildSnykCvss(vuln) {
|
|
4333
4412
|
if (!vuln.cvssScore && !vuln.CVSSv3) return [];
|
|
4334
|
-
return [buildCvss$
|
|
4413
|
+
return [buildCvss$4({
|
|
4335
4414
|
version: cvssVersionFromVector(vuln.CVSSv3),
|
|
4336
4415
|
baseScore: vuln.cvssScore,
|
|
4337
4416
|
baseVector: vuln.CVSSv3
|
|
@@ -4360,7 +4439,7 @@ function formatUpgradePath(path) {
|
|
|
4360
4439
|
const steps = path.filter((e) => typeof e === "string" && e.length > 0);
|
|
4361
4440
|
return steps.length > 0 ? steps.join(" > ") : void 0;
|
|
4362
4441
|
}
|
|
4363
|
-
function buildRequirement$
|
|
4442
|
+
function buildRequirement$17(vulnID, vulns, scanTime, packageManager) {
|
|
4364
4443
|
const rep = vulns[0];
|
|
4365
4444
|
const cweIDs = rep.identifiers.CWE ?? [];
|
|
4366
4445
|
const nist = mapCWEToNIST(cweIDs, DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
@@ -4392,7 +4471,7 @@ function buildRequirement$16(vulnID, vulns, scanTime, packageManager) {
|
|
|
4392
4471
|
const controlType = deriveControlTypeFromTags(nist);
|
|
4393
4472
|
if (controlType !== void 0) req.controlType = controlType;
|
|
4394
4473
|
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
4395
|
-
const code = buildVulnCode$
|
|
4474
|
+
const code = buildVulnCode$3(rep);
|
|
4396
4475
|
if (code) req.code = code;
|
|
4397
4476
|
const cvss = buildSnykCvss(rep);
|
|
4398
4477
|
if (cvss.length > 0) req.cvss = cvss;
|
|
@@ -4428,7 +4507,7 @@ function convertSingleProject(report, resultsChecksum, scanTime) {
|
|
|
4428
4507
|
else groups.set(vuln.id, [vuln]);
|
|
4429
4508
|
}
|
|
4430
4509
|
const requirements = [];
|
|
4431
|
-
for (const [vulnID, vulns] of groups) requirements.push(buildRequirement$
|
|
4510
|
+
for (const [vulnID, vulns] of groups) requirements.push(buildRequirement$17(vulnID, vulns, scanTime, report.packageManager));
|
|
4432
4511
|
if (requirements.length === 0) {
|
|
4433
4512
|
const target = report.projectName ?? report.path ?? "project";
|
|
4434
4513
|
requirements.push(buildNoFindingsRequirement("snyk-no-findings", `Snyk scanned ${target} and reported zero vulnerable components.`, scanTime));
|
|
@@ -4528,7 +4607,18 @@ function getReferences(vuln, relatedVulns) {
|
|
|
4528
4607
|
}
|
|
4529
4608
|
return Array.from(refs);
|
|
4530
4609
|
}
|
|
4531
|
-
|
|
4610
|
+
/**
|
|
4611
|
+
* The per-result instance identity for a grype match: the matched package's
|
|
4612
|
+
* purl, which is what distinguishes two entries reporting the same CVE. Falls
|
|
4613
|
+
* back to name@version when a source omits purl (optional in grype's schema,
|
|
4614
|
+
* though every committed fixture carries it) — the same identity twistlock
|
|
4615
|
+
* uses, whose source has no purl at all. Returns '' when the artifact names
|
|
4616
|
+
* nothing at all, so the caller leaves both fields unset.
|
|
4617
|
+
*/
|
|
4618
|
+
function packageInstanceId$1(a) {
|
|
4619
|
+
return packageInstanceId$2(a.purl ?? "", a.name, a.version ?? "");
|
|
4620
|
+
}
|
|
4621
|
+
function buildCodeDesc$10(match) {
|
|
4532
4622
|
const parts = [];
|
|
4533
4623
|
parts.push(`Package: ${match.artifact.name}@${match.artifact.version}`);
|
|
4534
4624
|
if (match.artifact.type) parts.push(`Type: ${match.artifact.type}`);
|
|
@@ -4546,7 +4636,7 @@ function buildCvssEntries$7(vuln) {
|
|
|
4546
4636
|
if (!vuln.cvss || vuln.cvss.length === 0) return;
|
|
4547
4637
|
const entries = [];
|
|
4548
4638
|
for (const c of vuln.cvss) {
|
|
4549
|
-
const entry = buildCvss$
|
|
4639
|
+
const entry = buildCvss$4({
|
|
4550
4640
|
version: cvssVersionFromString(c.version),
|
|
4551
4641
|
baseScore: c.metrics?.baseScore,
|
|
4552
4642
|
baseVector: c.vector,
|
|
@@ -4568,7 +4658,7 @@ function mapGrypeTypeToEcosystem(grypeType) {
|
|
|
4568
4658
|
default: return ecosystemFromPurlType(grypeType);
|
|
4569
4659
|
}
|
|
4570
4660
|
}
|
|
4571
|
-
function buildAffectedPackages(match) {
|
|
4661
|
+
function buildAffectedPackages$1(match) {
|
|
4572
4662
|
const artifact = match.artifact;
|
|
4573
4663
|
const fix = match.vulnerability.fix;
|
|
4574
4664
|
const pkg = buildAffectedPackage$1({
|
|
@@ -4625,10 +4715,15 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
4625
4715
|
const message = messageParts.join(" ");
|
|
4626
4716
|
const result = {
|
|
4627
4717
|
status,
|
|
4628
|
-
codeDesc: buildCodeDesc$
|
|
4718
|
+
codeDesc: buildCodeDesc$10(match),
|
|
4629
4719
|
message,
|
|
4630
4720
|
startTime
|
|
4631
4721
|
};
|
|
4722
|
+
const instanceId = packageInstanceId$1(match.artifact);
|
|
4723
|
+
if (instanceId) {
|
|
4724
|
+
result.resource = "package";
|
|
4725
|
+
result.resourceId = instanceId;
|
|
4726
|
+
}
|
|
4632
4727
|
const cciTags = nistToCci(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
4633
4728
|
const tags = buildNistCciTags(DEFAULT_STATIC_ANALYSIS_NIST_TAGS, cciTags);
|
|
4634
4729
|
markUnratedSeverity(tags, severity);
|
|
@@ -4660,7 +4755,7 @@ function convertMatchToRequirement(match, isIgnored, targetName, startTime) {
|
|
|
4660
4755
|
if (controlType !== void 0) requirement.controlType = controlType;
|
|
4661
4756
|
const cvss = buildCvssEntries$7(vuln);
|
|
4662
4757
|
if (cvss) requirement.cvss = cvss;
|
|
4663
|
-
requirement.affectedPackages = buildAffectedPackages(match);
|
|
4758
|
+
requirement.affectedPackages = buildAffectedPackages$1(match);
|
|
4664
4759
|
const cwe = extractCwe(vuln.cwe);
|
|
4665
4760
|
if (cwe) requirement.cwe = cwe;
|
|
4666
4761
|
const epss = buildEpss$3(vuln.epss);
|
|
@@ -4673,7 +4768,7 @@ function grypeTargetName(target) {
|
|
|
4673
4768
|
if (typeof target === "string") return target || "Grype Scan";
|
|
4674
4769
|
return target?.userInput || "Grype Scan";
|
|
4675
4770
|
}
|
|
4676
|
-
function buildComponent$
|
|
4771
|
+
function buildComponent$3(report, targetName) {
|
|
4677
4772
|
const rawTarget = report.source?.target;
|
|
4678
4773
|
const t = typeof rawTarget === "object" ? rawTarget : void 0;
|
|
4679
4774
|
const firstRepoDigest = t?.repoDigests?.find((d) => d);
|
|
@@ -4725,11 +4820,212 @@ async function convertGrypeToHdf(input, converterVersion = "1.0.0") {
|
|
|
4725
4820
|
toolName: "Grype",
|
|
4726
4821
|
toolVersion: grypeData.descriptor?.version,
|
|
4727
4822
|
baselines: [baseline],
|
|
4728
|
-
components: [buildComponent$
|
|
4823
|
+
components: [buildComponent$3(grypeData, targetName)],
|
|
4729
4824
|
timestamp: scanTime ?? void 0
|
|
4730
4825
|
});
|
|
4731
4826
|
}
|
|
4732
4827
|
//#endregion
|
|
4828
|
+
//#region converters/hadolint-to-hdf/typescript/rules.ts
|
|
4829
|
+
/**
|
|
4830
|
+
* Rule documentation links for the hadolint converter.
|
|
4831
|
+
*
|
|
4832
|
+
* hadolint's report carries a one-line message per finding and nothing else.
|
|
4833
|
+
* Each rule's rationale and fix live on a wiki page, and the two rule families
|
|
4834
|
+
* are documented in different projects, so every requirement links its own.
|
|
4835
|
+
*
|
|
4836
|
+
* Twin of go/rules.go.
|
|
4837
|
+
*/
|
|
4838
|
+
const HADOLINT_RULE_BASE = "https://github.com/hadolint/hadolint/wiki";
|
|
4839
|
+
const SHELLCHECK_RULE_BASE = "https://github.com/koalaman/shellcheck/wiki";
|
|
4840
|
+
/** Matches hadolint's own rule codes and the shellcheck codes it surfaces. */
|
|
4841
|
+
const RULE_CODE = /^(DL|SC)\d+$/;
|
|
4842
|
+
/** hadolint surfaces shellcheck's findings in its own report. */
|
|
4843
|
+
function isShellcheck(code) {
|
|
4844
|
+
return code.startsWith("SC");
|
|
4845
|
+
}
|
|
4846
|
+
/**
|
|
4847
|
+
* The published documentation for a rule, or an empty string when the code is
|
|
4848
|
+
* not one. The code is checked first, so a report carrying a crafted code
|
|
4849
|
+
* cannot steer the link somewhere of its choosing.
|
|
4850
|
+
*/
|
|
4851
|
+
function ruleReferenceUrl(code) {
|
|
4852
|
+
if (!RULE_CODE.test(code)) return "";
|
|
4853
|
+
return `${isShellcheck(code) ? SHELLCHECK_RULE_BASE : HADOLINT_RULE_BASE}/${code}`;
|
|
4854
|
+
}
|
|
4855
|
+
//#endregion
|
|
4856
|
+
//#region converters/hadolint-to-hdf/typescript/converter.ts
|
|
4857
|
+
/**
|
|
4858
|
+
* hadolint Dockerfile linter output to HDF.
|
|
4859
|
+
*
|
|
4860
|
+
* hadolint's JSON formatter emits a bare array of flat findings, each carrying
|
|
4861
|
+
* only code, column, file, level, line and message. One requirement is produced
|
|
4862
|
+
* per distinct rule code and one result per finding, so a rule that fires on
|
|
4863
|
+
* several lines keeps every occurrence.
|
|
4864
|
+
*
|
|
4865
|
+
* Twin of go/converter.go.
|
|
4866
|
+
*/
|
|
4867
|
+
const CONVERTER_NAME$3 = "hadolint";
|
|
4868
|
+
/**
|
|
4869
|
+
* hadolint's severity vocabulary as its JSON writer spells it. An ignored rule
|
|
4870
|
+
* serializes as the empty string, and anything unrecognized scores zero rather
|
|
4871
|
+
* than guessing.
|
|
4872
|
+
*/
|
|
4873
|
+
const LEVEL_IMPACT = {
|
|
4874
|
+
error: .7,
|
|
4875
|
+
warning: .5,
|
|
4876
|
+
info: .3,
|
|
4877
|
+
style: .1,
|
|
4878
|
+
"": 0
|
|
4879
|
+
};
|
|
4880
|
+
function impactFor(level) {
|
|
4881
|
+
return severityToImpactWithAliases(level, LEVEL_IMPACT, 0);
|
|
4882
|
+
}
|
|
4883
|
+
/**
|
|
4884
|
+
* Resolves a rule's NIST controls and CCIs, falling back to the static-analysis
|
|
4885
|
+
* controls when the rule is unmapped. A mapped rule can still translate to
|
|
4886
|
+
* nothing — the table is authored at Rev 5 and SR-4 has no Rev 4 equivalent —
|
|
4887
|
+
* so an empty result takes the fallback too rather than leaving the finding
|
|
4888
|
+
* with an empty nist tag. Mirrors Go's `controlsFor`.
|
|
4889
|
+
*/
|
|
4890
|
+
function controlsFor$1(ruleId) {
|
|
4891
|
+
const mapping = getHadolintNistMapping(ruleId);
|
|
4892
|
+
if (mapping && mapping.nist.length > 0) return {
|
|
4893
|
+
nist: mapping.nist,
|
|
4894
|
+
cci: nistToCci(mapping.nist)
|
|
4895
|
+
};
|
|
4896
|
+
const fallback = [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS];
|
|
4897
|
+
return {
|
|
4898
|
+
nist: fallback,
|
|
4899
|
+
cci: nistToCci(fallback)
|
|
4900
|
+
};
|
|
4901
|
+
}
|
|
4902
|
+
function codeDescFor(f) {
|
|
4903
|
+
return `File: ${f.file ?? ""} | Line: ${f.line ?? 0} | Column: ${f.column ?? 0}`;
|
|
4904
|
+
}
|
|
4905
|
+
function findingToResult(f, scanTime) {
|
|
4906
|
+
return createResult(ResultStatus.Failed, f.message ?? "", {
|
|
4907
|
+
codeDesc: codeDescFor(f),
|
|
4908
|
+
startTime: scanTime
|
|
4909
|
+
});
|
|
4910
|
+
}
|
|
4911
|
+
function buildRequirement$16(ruleId, group, scanTime) {
|
|
4912
|
+
const first = group[0];
|
|
4913
|
+
const { nist, cci } = controlsFor$1(ruleId);
|
|
4914
|
+
const tags = buildNistCciTags(nist, cci);
|
|
4915
|
+
const message = first.message ?? "";
|
|
4916
|
+
const req = createRequirement(ruleId, message, [{
|
|
4917
|
+
label: "default",
|
|
4918
|
+
data: message
|
|
4919
|
+
}], impactFor(first.level ?? ""), group.map((f) => findingToResult(f, scanTime)), { tags });
|
|
4920
|
+
req.verificationMethod = VerificationMethodEnum.Automated;
|
|
4921
|
+
const reference = ruleReferenceUrl(ruleId);
|
|
4922
|
+
if (reference !== "") req.refs = [{ url: reference }];
|
|
4923
|
+
req.code = JSON.stringify({
|
|
4924
|
+
code: first.code ?? "",
|
|
4925
|
+
column: first.column ?? 0,
|
|
4926
|
+
file: first.file ?? "",
|
|
4927
|
+
level: first.level ?? "",
|
|
4928
|
+
line: first.line ?? 0,
|
|
4929
|
+
message: first.message ?? ""
|
|
4930
|
+
});
|
|
4931
|
+
if (first.file) {
|
|
4932
|
+
const location = { ref: first.file };
|
|
4933
|
+
if (first.line !== void 0 && first.line > 0) location.line = first.line;
|
|
4934
|
+
req.sourceLocation = location;
|
|
4935
|
+
}
|
|
4936
|
+
const controlType = deriveControlTypeFromTags(nist);
|
|
4937
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
4938
|
+
return req;
|
|
4939
|
+
}
|
|
4940
|
+
/**
|
|
4941
|
+
* Names each distinct scanned file. hadolint accepts several Dockerfiles in one
|
|
4942
|
+
* run and tags every finding with the file it came from.
|
|
4943
|
+
*/
|
|
4944
|
+
function componentsFor(findings) {
|
|
4945
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4946
|
+
const components = [];
|
|
4947
|
+
for (const f of findings) {
|
|
4948
|
+
if (!f.file || seen.has(f.file)) continue;
|
|
4949
|
+
seen.add(f.file);
|
|
4950
|
+
components.push({
|
|
4951
|
+
name: f.file,
|
|
4952
|
+
type: TargetType.Repository
|
|
4953
|
+
});
|
|
4954
|
+
}
|
|
4955
|
+
return components;
|
|
4956
|
+
}
|
|
4957
|
+
function baselineTitle(components) {
|
|
4958
|
+
if (components.length === 0) return "Hadolint Scan";
|
|
4959
|
+
if (components.length === 1) return `Hadolint Scan of ${components[0].name}`;
|
|
4960
|
+
return `Hadolint Scan of ${components.length} files`;
|
|
4961
|
+
}
|
|
4962
|
+
const STRING_FIELDS = [
|
|
4963
|
+
"code",
|
|
4964
|
+
"file",
|
|
4965
|
+
"level",
|
|
4966
|
+
"message"
|
|
4967
|
+
];
|
|
4968
|
+
const INTEGER_FIELDS = ["column", "line"];
|
|
4969
|
+
/**
|
|
4970
|
+
* Parses the findings array, rejecting what the Go decoder rejects so both
|
|
4971
|
+
* public implementations accept the same inputs. Go decodes an absent or null
|
|
4972
|
+
* field to its zero value, so those stay valid; it refuses a field present
|
|
4973
|
+
* with the wrong type.
|
|
4974
|
+
*/
|
|
4975
|
+
function parseInput$2(input) {
|
|
4976
|
+
const findings = parseJSON(input);
|
|
4977
|
+
if (!Array.isArray(findings)) throw new Error(`${CONVERTER_NAME$3}: input is not a hadolint findings array`);
|
|
4978
|
+
for (const finding of findings) {
|
|
4979
|
+
if (typeof finding !== "object" || finding === null || Array.isArray(finding)) throw new Error(`${CONVERTER_NAME$3}: input is not a hadolint findings array`);
|
|
4980
|
+
const fields = finding;
|
|
4981
|
+
for (const key of STRING_FIELDS) {
|
|
4982
|
+
const value = fields[key];
|
|
4983
|
+
if (value !== void 0 && value !== null && typeof value !== "string") throw new Error(`${CONVERTER_NAME$3}: finding field ${key} is not a string`);
|
|
4984
|
+
}
|
|
4985
|
+
for (const key of INTEGER_FIELDS) {
|
|
4986
|
+
const value = fields[key];
|
|
4987
|
+
if (value !== void 0 && value !== null && !Number.isInteger(value)) throw new Error(`${CONVERTER_NAME$3}: finding field ${key} is not an integer`);
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
return findings;
|
|
4991
|
+
}
|
|
4992
|
+
/** Converts hadolint JSON output to HDF results. */
|
|
4993
|
+
async function convertHadolintToHdf(input, converterVersion = "1.0.0") {
|
|
4994
|
+
validateInputSize$1(input, CONVERTER_NAME$3);
|
|
4995
|
+
registerAllFingerprints();
|
|
4996
|
+
const detected = detectConverter(input);
|
|
4997
|
+
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
4998
|
+
const resultsChecksum = await inputChecksum(input);
|
|
4999
|
+
const scanTime = /* @__PURE__ */ new Date();
|
|
5000
|
+
const parsed = parseInput$2(input);
|
|
5001
|
+
const { items: findings, truncated } = limitArray(parsed);
|
|
5002
|
+
/* v8 ignore next -- truncation only triggers with >100K items */
|
|
5003
|
+
if (truncated) console.warn(`WARNING: Input truncated at ${findings.length} finding items (original: ${parsed.length})`);
|
|
5004
|
+
const components = componentsFor(findings);
|
|
5005
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5006
|
+
for (const f of findings) {
|
|
5007
|
+
const code = f.code ?? "";
|
|
5008
|
+
const existing = groups.get(code);
|
|
5009
|
+
if (existing) existing.push(f);
|
|
5010
|
+
else groups.set(code, [f]);
|
|
5011
|
+
}
|
|
5012
|
+
const requirements = [];
|
|
5013
|
+
for (const [ruleId, group] of groups) requirements.push(buildRequirement$16(ruleId, group, scanTime));
|
|
5014
|
+
if (requirements.length === 0) requirements.push(buildNoFindingsRequirement("hadolint-no-findings", "hadolint scanned the Dockerfile and reported zero findings.", scanTime));
|
|
5015
|
+
const baseline = createMinimalBaseline("Hadolint Scan", requirements, {
|
|
5016
|
+
title: baselineTitle(components),
|
|
5017
|
+
resultsChecksum
|
|
5018
|
+
});
|
|
5019
|
+
return buildHdfResults({
|
|
5020
|
+
generatorName: "hadolint-to-hdf",
|
|
5021
|
+
converterVersion,
|
|
5022
|
+
toolName: CONVERTER_NAME$3,
|
|
5023
|
+
baselines: [baseline],
|
|
5024
|
+
components: components.length > 0 ? components : void 0,
|
|
5025
|
+
timestamp: scanTime
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
//#endregion
|
|
4733
5029
|
//#region converters/defectdojo-to-hdf/typescript/converter.ts
|
|
4734
5030
|
function deriveStatus(f) {
|
|
4735
5031
|
if (f.out_of_scope) return ResultStatus.NotApplicable;
|
|
@@ -4833,11 +5129,11 @@ function findingId(f) {
|
|
|
4833
5129
|
function cveList(f) {
|
|
4834
5130
|
return (f.vulnerability_ids ?? []).map((v) => v.vulnerability_id).filter(Boolean);
|
|
4835
5131
|
}
|
|
4836
|
-
function buildCvss$
|
|
5132
|
+
function buildCvss$3(f) {
|
|
4837
5133
|
const out = [];
|
|
4838
5134
|
const add = (version, vector, score) => {
|
|
4839
5135
|
if ((score === void 0 || score === null) && !vector) return;
|
|
4840
|
-
out.push(buildCvss$
|
|
5136
|
+
out.push(buildCvss$4({
|
|
4841
5137
|
version,
|
|
4842
5138
|
baseScore: score,
|
|
4843
5139
|
baseVector: vector
|
|
@@ -4858,7 +5154,7 @@ function buildEpss$2(f) {
|
|
|
4858
5154
|
function nistTags(f) {
|
|
4859
5155
|
return f.cwe && f.cwe > 0 ? mapCWEToNIST([`CWE-${f.cwe}`], DEFAULT_STATIC_ANALYSIS_NIST_TAGS) : DEFAULT_STATIC_ANALYSIS_NIST_TAGS;
|
|
4860
5156
|
}
|
|
4861
|
-
function buildDescriptions$
|
|
5157
|
+
function buildDescriptions$2(f) {
|
|
4862
5158
|
const descs = [{
|
|
4863
5159
|
label: "default",
|
|
4864
5160
|
data: f.description || f.title || "No description provided."
|
|
@@ -4886,7 +5182,7 @@ function sourceLocus(f) {
|
|
|
4886
5182
|
if (f.sast_source_file_path) return [f.sast_source_file_path, f.sast_source_line];
|
|
4887
5183
|
return [void 0, void 0];
|
|
4888
5184
|
}
|
|
4889
|
-
function buildSourceLocation$
|
|
5185
|
+
function buildSourceLocation$6(f) {
|
|
4890
5186
|
const [ref, line] = sourceLocus(f);
|
|
4891
5187
|
if (!ref) return void 0;
|
|
4892
5188
|
const loc = { ref };
|
|
@@ -4908,17 +5204,17 @@ function convertFinding(f) {
|
|
|
4908
5204
|
impact: severityToImpact(f.severity),
|
|
4909
5205
|
results: [result],
|
|
4910
5206
|
tags,
|
|
4911
|
-
descriptions: buildDescriptions$
|
|
5207
|
+
descriptions: buildDescriptions$2(f)
|
|
4912
5208
|
};
|
|
4913
5209
|
const controlType = deriveControlTypeFromTags(nist);
|
|
4914
5210
|
if (controlType !== void 0) req.controlType = controlType;
|
|
4915
|
-
const cvss = buildCvss$
|
|
5211
|
+
const cvss = buildCvss$3(f);
|
|
4916
5212
|
if (cvss.length > 0) req.cvss = cvss;
|
|
4917
5213
|
const epss = buildEpss$2(f);
|
|
4918
5214
|
if (epss) req.epss = epss;
|
|
4919
5215
|
if (f.cwe && f.cwe > 0) req.cwe = [`CWE-${f.cwe}`];
|
|
4920
5216
|
req.code = JSON.stringify(f, null, 2);
|
|
4921
|
-
const sourceLocation = buildSourceLocation$
|
|
5217
|
+
const sourceLocation = buildSourceLocation$6(f);
|
|
4922
5218
|
if (sourceLocation) req.sourceLocation = sourceLocation;
|
|
4923
5219
|
const firstRisk = f.accepted_risks?.[0];
|
|
4924
5220
|
if (f.risk_accepted && firstRisk) {
|
|
@@ -5111,10 +5407,10 @@ function convertReportItemToRequirement(item, host) {
|
|
|
5111
5407
|
const isCompliance = !!item["compliance-reference"];
|
|
5112
5408
|
const id = isCompliance ? parseComplianceRef(item["compliance-reference"], "Vuln-ID")[0] || item["pluginID"] : item["pluginID"];
|
|
5113
5409
|
const title = isCompliance ? item["compliance-check-name"] || item["pluginName"] : item["pluginName"];
|
|
5114
|
-
const descriptions = buildDescriptions(item, isCompliance);
|
|
5410
|
+
const descriptions = buildDescriptions$1(item, isCompliance);
|
|
5115
5411
|
const impact = calculateImpact(item, isCompliance);
|
|
5116
|
-
const tags = buildTags$
|
|
5117
|
-
const refs = buildRefs$
|
|
5412
|
+
const tags = buildTags$4(item, isCompliance);
|
|
5413
|
+
const refs = buildRefs$10(item);
|
|
5118
5414
|
const results = [buildResult$1(item, host, isCompliance)];
|
|
5119
5415
|
const code = JSON.stringify(item, null, 2);
|
|
5120
5416
|
const nistTags = tags["nist"];
|
|
@@ -5253,7 +5549,7 @@ function epssDate$1(host) {
|
|
|
5253
5549
|
if (d) return d.toISOString().slice(0, 10);
|
|
5254
5550
|
}
|
|
5255
5551
|
}
|
|
5256
|
-
function buildDescriptions(item, isCompliance) {
|
|
5552
|
+
function buildDescriptions$1(item, isCompliance) {
|
|
5257
5553
|
const descriptions = [];
|
|
5258
5554
|
if (isCompliance && item["compliance-info"]) descriptions.push({
|
|
5259
5555
|
label: "default",
|
|
@@ -5301,7 +5597,7 @@ function calculateImpact(item, isCompliance) {
|
|
|
5301
5597
|
function orDefaultNist(controls) {
|
|
5302
5598
|
return controls.length > 0 ? controls : [...DEFAULT_REMEDIATION_NIST_TAGS$1];
|
|
5303
5599
|
}
|
|
5304
|
-
function buildTags$
|
|
5600
|
+
function buildTags$4(item, isCompliance) {
|
|
5305
5601
|
const tags = { rid: isCompliance ? parseComplianceRef(item["compliance-reference"], "Rule-ID").join(",") : item["pluginID"] };
|
|
5306
5602
|
if (isCompliance && item["compliance-reference"]) {
|
|
5307
5603
|
const cciTags = parseComplianceRef(item["compliance-reference"], "CCI");
|
|
@@ -5324,7 +5620,7 @@ function buildTags$3(item, isCompliance) {
|
|
|
5324
5620
|
if (item.cvss_base_score) tags.cvss_base_score = item.cvss_base_score;
|
|
5325
5621
|
return tags;
|
|
5326
5622
|
}
|
|
5327
|
-
function buildRefs$
|
|
5623
|
+
function buildRefs$10(item) {
|
|
5328
5624
|
const refs = [];
|
|
5329
5625
|
if (item.see_also) for (const url of item.see_also.split(/\s+/).filter(Boolean)) refs.push({ url });
|
|
5330
5626
|
return refs.length > 0 ? refs : void 0;
|
|
@@ -5699,7 +5995,7 @@ function mapComplianceStatus(complianceType) {
|
|
|
5699
5995
|
function splitControls(nistId) {
|
|
5700
5996
|
return nistId.split("|").map((c) => c.trim()).filter(Boolean);
|
|
5701
5997
|
}
|
|
5702
|
-
function buildNistTags$
|
|
5998
|
+
function buildNistTags$4(sourceIdentifier, ruleName) {
|
|
5703
5999
|
const byIdentifier = sourceIdentifier ? getAwsConfigNistControlByIdentifier(sourceIdentifier) : void 0;
|
|
5704
6000
|
if (byIdentifier) return splitControls(byIdentifier);
|
|
5705
6001
|
const byName = getAwsConfigNistControlByName(ruleName);
|
|
@@ -5748,7 +6044,7 @@ function buildCheckText(rule) {
|
|
|
5748
6044
|
}
|
|
5749
6045
|
return parts.join("<br/>");
|
|
5750
6046
|
}
|
|
5751
|
-
function buildCodeDesc$
|
|
6047
|
+
function buildCodeDesc$9(q) {
|
|
5752
6048
|
return `config_rule_name: ${q.ConfigRuleName}, resource_type: ${q.ResourceType}, resource_id: ${q.ResourceId}`;
|
|
5753
6049
|
}
|
|
5754
6050
|
function buildResultMessage(codeDesc, annotation, status) {
|
|
@@ -5765,7 +6061,7 @@ function computeRunTime$1(invoked, recorded) {
|
|
|
5765
6061
|
function buildResult(r) {
|
|
5766
6062
|
const q = r.EvaluationResultIdentifier.EvaluationResultQualifier;
|
|
5767
6063
|
const status = mapComplianceStatus(r.ComplianceType);
|
|
5768
|
-
const codeDesc = buildCodeDesc$
|
|
6064
|
+
const codeDesc = buildCodeDesc$9(q);
|
|
5769
6065
|
const message = buildResultMessage(codeDesc, r.Annotation, status);
|
|
5770
6066
|
const startTime = (r.ConfigRuleInvokedTime ? parseTimestamp(r.ConfigRuleInvokedTime) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
5771
6067
|
const runTime = computeRunTime$1(r.ConfigRuleInvokedTime, r.ResultRecordedTime);
|
|
@@ -5790,7 +6086,7 @@ function buildNotApplicableResult(rule) {
|
|
|
5790
6086
|
});
|
|
5791
6087
|
}
|
|
5792
6088
|
function buildRequirement$15(rule) {
|
|
5793
|
-
const rawNist = buildNistTags$
|
|
6089
|
+
const rawNist = buildNistTags$4(rule.Source.SourceIdentifier, rule.ConfigRuleName);
|
|
5794
6090
|
const nist = rawNist.length > 0 ? rawNist : DEFAULT_CONFIG_MANAGEMENT_NIST_TAGS;
|
|
5795
6091
|
const tags = { nist };
|
|
5796
6092
|
const descriptions = [{
|
|
@@ -5964,7 +6260,7 @@ function buildRequirement$14(checkId, group, scanTime) {
|
|
|
5964
6260
|
/**
|
|
5965
6261
|
* Parses checkov input which can be a single object or array.
|
|
5966
6262
|
*/
|
|
5967
|
-
function parseInput(input) {
|
|
6263
|
+
function parseInput$1(input) {
|
|
5968
6264
|
const parsed = parseJSON(input);
|
|
5969
6265
|
if (Array.isArray(parsed)) return parsed;
|
|
5970
6266
|
if (!parsed || typeof parsed !== "object") throw new Error("Invalid checkov structure: not a valid JSON object");
|
|
@@ -5986,7 +6282,7 @@ async function convertCheckovToHdf(input, converterVersion = "1.0.0") {
|
|
|
5986
6282
|
if (detected && detected.fingerprint.id === "sarif-to-hdf") return convertSarifToHdf(input, converterVersion);
|
|
5987
6283
|
const resultsChecksum = await inputChecksum(input);
|
|
5988
6284
|
const scanTime = /* @__PURE__ */ new Date();
|
|
5989
|
-
const reports = parseInput(input);
|
|
6285
|
+
const reports = parseInput$1(input);
|
|
5990
6286
|
const allChecks = [];
|
|
5991
6287
|
const checkTypes = [];
|
|
5992
6288
|
let version;
|
|
@@ -6052,7 +6348,7 @@ function parseSourceLine(line) {
|
|
|
6052
6348
|
* Promotes an issue's file/line locus into the structured HDF sourceLocation
|
|
6053
6349
|
* field. Returns undefined when the issue carries no file path.
|
|
6054
6350
|
*/
|
|
6055
|
-
function buildSourceLocation$
|
|
6351
|
+
function buildSourceLocation$5(issue) {
|
|
6056
6352
|
if (!issue.file) return;
|
|
6057
6353
|
const loc = { ref: issue.file };
|
|
6058
6354
|
const line = parseSourceLine(issue.line);
|
|
@@ -6108,7 +6404,7 @@ function buildRequirement$13(ruleId, issues, scanTime) {
|
|
|
6108
6404
|
data: `CWE-${rep.cwe.id}: ${rep.cwe.url}`
|
|
6109
6405
|
}];
|
|
6110
6406
|
const options = { tags };
|
|
6111
|
-
const sourceLocation = buildSourceLocation$
|
|
6407
|
+
const sourceLocation = buildSourceLocation$5(rep);
|
|
6112
6408
|
if (sourceLocation) options.sourceLocation = sourceLocation;
|
|
6113
6409
|
const req = createRequirement(ruleId, rep.details, descriptions, impact, results, options);
|
|
6114
6410
|
const cwe = cweIds(rep);
|
|
@@ -6200,23 +6496,23 @@ async function convertGosecToHdf(input, converterVersion = "1.0.0") {
|
|
|
6200
6496
|
}
|
|
6201
6497
|
//#endregion
|
|
6202
6498
|
//#region converters/nikto-to-hdf/typescript/converter.ts
|
|
6203
|
-
function buildNistTags$
|
|
6499
|
+
function buildNistTags$3(niktoId) {
|
|
6204
6500
|
const control = getNiktoNistControl(niktoId);
|
|
6205
6501
|
if (control) return [control];
|
|
6206
6502
|
return [...DEFAULT_STATIC_ANALYSIS_NIST_TAGS];
|
|
6207
6503
|
}
|
|
6208
|
-
function buildCodeDesc$
|
|
6504
|
+
function buildCodeDesc$8(vuln) {
|
|
6209
6505
|
const parts = [];
|
|
6210
6506
|
if (vuln.url) parts.push(`URL: ${vuln.url}`);
|
|
6211
6507
|
if (vuln.method) parts.push(`Method: ${vuln.method}`);
|
|
6212
6508
|
return parts.join(" ");
|
|
6213
6509
|
}
|
|
6214
6510
|
function convertVulnToRequirement(vuln) {
|
|
6215
|
-
const nistTags = buildNistTags$
|
|
6511
|
+
const nistTags = buildNistTags$3(vuln.id);
|
|
6216
6512
|
const cciTags = nistToCci(nistTags);
|
|
6217
6513
|
const result = {
|
|
6218
6514
|
status: ResultStatus.Failed,
|
|
6219
|
-
codeDesc: buildCodeDesc$
|
|
6515
|
+
codeDesc: buildCodeDesc$8(vuln),
|
|
6220
6516
|
startTime: /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
6221
6517
|
};
|
|
6222
6518
|
const extras = {};
|
|
@@ -6259,7 +6555,7 @@ async function convertNiktoToHdf(input, converterVersion = "unknown") {
|
|
|
6259
6555
|
const req = convertVulnToRequirement(primary);
|
|
6260
6556
|
if (vulns.length > 1) for (let i = 1; i < vulns.length; i++) req.results.push({
|
|
6261
6557
|
status: ResultStatus.Failed,
|
|
6262
|
-
codeDesc: buildCodeDesc$
|
|
6558
|
+
codeDesc: buildCodeDesc$8(vulns[i]),
|
|
6263
6559
|
startTime: /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
6264
6560
|
});
|
|
6265
6561
|
requirements.push(req);
|
|
@@ -6303,7 +6599,7 @@ function riskCodeToImpact(riskCode) {
|
|
|
6303
6599
|
default: return .5;
|
|
6304
6600
|
}
|
|
6305
6601
|
}
|
|
6306
|
-
function buildNistTags$
|
|
6602
|
+
function buildNistTags$2(cweid) {
|
|
6307
6603
|
if (cweid && cweid !== "0") {
|
|
6308
6604
|
const cweNum = parseInt(cweid, 10);
|
|
6309
6605
|
if (!isNaN(cweNum)) {
|
|
@@ -6319,7 +6615,7 @@ function buildCwe(cweid) {
|
|
|
6319
6615
|
if (isNaN(cweNum)) return;
|
|
6320
6616
|
return [`CWE-${cweNum}`];
|
|
6321
6617
|
}
|
|
6322
|
-
function buildCodeDesc$
|
|
6618
|
+
function buildCodeDesc$7(instance) {
|
|
6323
6619
|
const parts = [];
|
|
6324
6620
|
if (instance.uri) parts.push(`URI: ${instance.uri}`);
|
|
6325
6621
|
if (instance.method) parts.push(`Method: ${instance.method}`);
|
|
@@ -6342,13 +6638,13 @@ function buildRequirementCode$1(alert) {
|
|
|
6342
6638
|
if (parts.length === 0) return;
|
|
6343
6639
|
return parts.join("\n");
|
|
6344
6640
|
}
|
|
6345
|
-
function buildSourceLocation$
|
|
6641
|
+
function buildSourceLocation$4(alert) {
|
|
6346
6642
|
const inst = (alert.instances ?? []).find((i) => i.uri);
|
|
6347
6643
|
if (!inst || !inst.uri) return;
|
|
6348
6644
|
return { ref: inst.uri };
|
|
6349
6645
|
}
|
|
6350
6646
|
const REF_URL_RE = /https?:\/\/[^\s<>"']+/g;
|
|
6351
|
-
function buildRefs$
|
|
6647
|
+
function buildRefs$9(reference) {
|
|
6352
6648
|
if (!reference) return;
|
|
6353
6649
|
const urls = reference.match(REF_URL_RE);
|
|
6354
6650
|
if (!urls || urls.length === 0) return;
|
|
@@ -6368,7 +6664,7 @@ function buildSiteRequirements(site, startTime) {
|
|
|
6368
6664
|
const count = pluginIdCount.get(alert.pluginid) ?? 0;
|
|
6369
6665
|
pluginIdCount.set(alert.pluginid, count + 1);
|
|
6370
6666
|
const reqId = count === 0 ? alert.pluginid : `${alert.pluginid}.${count}`;
|
|
6371
|
-
const nistTags = buildNistTags$
|
|
6667
|
+
const nistTags = buildNistTags$2(alert.cweid ?? "");
|
|
6372
6668
|
const cciTags = nistToCci(nistTags);
|
|
6373
6669
|
const extras = {};
|
|
6374
6670
|
if (alert.wascid) extras.wascid = alert.wascid;
|
|
@@ -6383,9 +6679,13 @@ function buildSiteRequirements(site, startTime) {
|
|
|
6383
6679
|
for (const instance of limitedInstances) {
|
|
6384
6680
|
const result = {
|
|
6385
6681
|
status: ResultStatus.Failed,
|
|
6386
|
-
codeDesc: buildCodeDesc$
|
|
6682
|
+
codeDesc: buildCodeDesc$7(instance),
|
|
6387
6683
|
startTime
|
|
6388
6684
|
};
|
|
6685
|
+
if (instance.uri) {
|
|
6686
|
+
result.resource = "url";
|
|
6687
|
+
result.resourceId = instance.uri;
|
|
6688
|
+
}
|
|
6389
6689
|
if (instance.attack) result.message = instance.attack;
|
|
6390
6690
|
results.push(result);
|
|
6391
6691
|
}
|
|
@@ -6421,9 +6721,9 @@ function buildSiteRequirements(site, startTime) {
|
|
|
6421
6721
|
if (controlType !== void 0) req.controlType = controlType;
|
|
6422
6722
|
const code = buildRequirementCode$1(alert);
|
|
6423
6723
|
if (code !== void 0) req.code = code;
|
|
6424
|
-
const sourceLocation = buildSourceLocation$
|
|
6724
|
+
const sourceLocation = buildSourceLocation$4(alert);
|
|
6425
6725
|
if (sourceLocation !== void 0) req.sourceLocation = sourceLocation;
|
|
6426
|
-
const refs = buildRefs$
|
|
6726
|
+
const refs = buildRefs$9(alert.reference);
|
|
6427
6727
|
if (refs !== void 0) req.refs = refs;
|
|
6428
6728
|
requirements.push(req);
|
|
6429
6729
|
}
|
|
@@ -7068,8 +7368,8 @@ function statusOf(req) {
|
|
|
7068
7368
|
if (!o) return [];
|
|
7069
7369
|
return [{
|
|
7070
7370
|
status: getStr(o, "status") || void 0,
|
|
7071
|
-
appliedAt: getStr(o, "appliedAt") || void 0,
|
|
7072
|
-
expiresAt: getStr(o, "expiresAt") || void 0
|
|
7371
|
+
appliedAt: absentIfGoZeroTime(getStr(o, "appliedAt") || void 0),
|
|
7372
|
+
expiresAt: absentIfGoZeroTime(getStr(o, "expiresAt") || void 0)
|
|
7073
7373
|
}];
|
|
7074
7374
|
});
|
|
7075
7375
|
const impact = typeof req.impact === "number" ? req.impact : 1;
|
|
@@ -7306,6 +7606,48 @@ function epochMillis(s) {
|
|
|
7306
7606
|
if (d === null) return void 0;
|
|
7307
7607
|
return d.getTime();
|
|
7308
7608
|
}
|
|
7609
|
+
/**
|
|
7610
|
+
* The index of the override that governs a requirement — the most recently
|
|
7611
|
+
* applied non-expired one — or -1 when none does. The map-shaped twin of
|
|
7612
|
+
* shared/go/exportmap GoverningOverrideIndex.
|
|
7613
|
+
*
|
|
7614
|
+
* Resolution is by appliedAt, never by array position. The schema's description
|
|
7615
|
+
* says the most recent override "should be first in array", but nothing sorts
|
|
7616
|
+
* and this repo's own writers append, so on a document amended twice the newest
|
|
7617
|
+
* override is LAST and overrides[0] is the oldest.
|
|
7618
|
+
*
|
|
7619
|
+
* An entry that is not an object sorts earliest and never governs, so a
|
|
7620
|
+
* malformed override cannot take a whole document's conversion down.
|
|
7621
|
+
*/
|
|
7622
|
+
function governingOverrideIndexOf(overrides, now) {
|
|
7623
|
+
const inputs = overrides.map((raw) => {
|
|
7624
|
+
const m = asMap(raw);
|
|
7625
|
+
if (!m) return {};
|
|
7626
|
+
return {
|
|
7627
|
+
status: getStr(m, "status") || void 0,
|
|
7628
|
+
appliedAt: getStr(m, "appliedAt") || void 0,
|
|
7629
|
+
expiresAt: getStr(m, "expiresAt") || void 0
|
|
7630
|
+
};
|
|
7631
|
+
});
|
|
7632
|
+
return governingOverrideIndex(inputs, (i) => asMap(overrides[i]) !== void 0, now);
|
|
7633
|
+
}
|
|
7634
|
+
/** The override object that governs a requirement, or undefined when none does. */
|
|
7635
|
+
function governingOverrideOf(overrides, now) {
|
|
7636
|
+
const i = governingOverrideIndexOf(overrides, now);
|
|
7637
|
+
return i >= 0 ? asMap(overrides[i]) : void 0;
|
|
7638
|
+
}
|
|
7639
|
+
/**
|
|
7640
|
+
* The type of the override that governs a requirement, or '' when none does —
|
|
7641
|
+
* the map-shaped twin of requirementDisposition, including its no-overrides
|
|
7642
|
+
* fallback to the stored field.
|
|
7643
|
+
*/
|
|
7644
|
+
function disposition(req, now) {
|
|
7645
|
+
const overrides = asArr(req.statusOverrides) ?? [];
|
|
7646
|
+
const gov = governingOverrideOf(overrides, now);
|
|
7647
|
+
if (gov) return getStr(gov, "type");
|
|
7648
|
+
if (overrides.length === 0) return getStr(req, "disposition");
|
|
7649
|
+
return "";
|
|
7650
|
+
}
|
|
7309
7651
|
//#endregion
|
|
7310
7652
|
//#region converters/cyclonedx-to-hdf/typescript/converter.ts
|
|
7311
7653
|
const CVSS_METHODS = /* @__PURE__ */ new Set([
|
|
@@ -7363,7 +7705,7 @@ function buildCvssEntries$5(ratings) {
|
|
|
7363
7705
|
const hasCvssMethod = r.method !== void 0 && CVSS_METHODS.has(r.method);
|
|
7364
7706
|
const hasMetric = r.score !== void 0 && r.score !== null || r.vector !== void 0 && r.vector !== "";
|
|
7365
7707
|
if (!hasCvssMethod || !hasMetric) continue;
|
|
7366
|
-
entries.push(buildCvss$
|
|
7708
|
+
entries.push(buildCvss$4({
|
|
7367
7709
|
version: cvssVersionFromMethod(r.method, r.vector),
|
|
7368
7710
|
baseScore: r.score,
|
|
7369
7711
|
baseVector: r.vector,
|
|
@@ -7378,7 +7720,7 @@ function buildCvssEntries$5(ratings) {
|
|
|
7378
7720
|
* de-duplicated across all three in first-seen order. Returns undefined when the
|
|
7379
7721
|
* vulnerability carries no links.
|
|
7380
7722
|
*/
|
|
7381
|
-
function buildRefs$
|
|
7723
|
+
function buildRefs$8(vuln) {
|
|
7382
7724
|
const seen = /* @__PURE__ */ new Set();
|
|
7383
7725
|
const refs = [];
|
|
7384
7726
|
const add = (url) => {
|
|
@@ -7392,6 +7734,49 @@ function buildRefs$7(vuln) {
|
|
|
7392
7734
|
return refs.length > 0 ? refs : void 0;
|
|
7393
7735
|
}
|
|
7394
7736
|
/**
|
|
7737
|
+
* The per-result instance identity for a CycloneDX finding: the affected
|
|
7738
|
+
* component's purl, which is what distinguishes the several vulnerabilities that
|
|
7739
|
+
* land on different components. purl is optional in CycloneDX, so this falls
|
|
7740
|
+
* back to the bom-ref — also document-unique, and the only identifier a VEX
|
|
7741
|
+
* document (which carries no components[] to resolve the ref against) publishes
|
|
7742
|
+
* at all.
|
|
7743
|
+
*/
|
|
7744
|
+
function componentInstanceId(componentLookup, ref) {
|
|
7745
|
+
return componentLookup.get(ref)?.purl ?? ref;
|
|
7746
|
+
}
|
|
7747
|
+
/**
|
|
7748
|
+
* Extracts the type segment of a purl ('pkg:maven/...' -> 'maven'). Returns
|
|
7749
|
+
* undefined for anything that is not a purl.
|
|
7750
|
+
*/
|
|
7751
|
+
function purlType(purl) {
|
|
7752
|
+
return purl ? parsePurl(purl)?.type : void 0;
|
|
7753
|
+
}
|
|
7754
|
+
/**
|
|
7755
|
+
* Assembles the requirement's affected-package inventory from the components its
|
|
7756
|
+
* affects[] entries resolve to, de-duplicated by ref in first-seen order.
|
|
7757
|
+
* Components with neither a purl nor an ecosystem yield no schema-valid entry
|
|
7758
|
+
* and are skipped rather than emitted half-built.
|
|
7759
|
+
*/
|
|
7760
|
+
function buildAffectedPackages(componentLookup, affects) {
|
|
7761
|
+
const out = [];
|
|
7762
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7763
|
+
for (const affect of affects) {
|
|
7764
|
+
const comp = componentLookup.get(affect.ref);
|
|
7765
|
+
if (!comp || seen.has(affect.ref)) continue;
|
|
7766
|
+
seen.add(affect.ref);
|
|
7767
|
+
const type = purlType(comp.purl);
|
|
7768
|
+
const ecosystem = type ? ecosystemFromPurlType(type) : void 0;
|
|
7769
|
+
const pkg = buildAffectedPackage$1({
|
|
7770
|
+
name: comp.name,
|
|
7771
|
+
version: comp.version,
|
|
7772
|
+
ecosystem,
|
|
7773
|
+
purl: comp.purl
|
|
7774
|
+
});
|
|
7775
|
+
if (pkg) out.push(pkg);
|
|
7776
|
+
}
|
|
7777
|
+
return out;
|
|
7778
|
+
}
|
|
7779
|
+
/**
|
|
7395
7780
|
* Formats a component reference as a code_desc string.
|
|
7396
7781
|
*/
|
|
7397
7782
|
function formatCodeDesc$4(componentLookup, ref) {
|
|
@@ -7462,7 +7847,7 @@ function projectLicenseEntry(l) {
|
|
|
7462
7847
|
* both languages emit byte-identical code strings. JSON.stringify never escapes
|
|
7463
7848
|
* `&`/`<`/`>`, matching Go's SetEscapeHTML(false).
|
|
7464
7849
|
*/
|
|
7465
|
-
function buildVulnCode$
|
|
7850
|
+
function buildVulnCode$2(vuln) {
|
|
7466
7851
|
const proj = {
|
|
7467
7852
|
id: vuln.id,
|
|
7468
7853
|
source: projectSource(vuln.source),
|
|
@@ -7683,19 +8068,28 @@ async function convertCyclonedxToHdf(input, converterVersion = "1.0.0") {
|
|
|
7683
8068
|
data: fixParts.join("\n\n")
|
|
7684
8069
|
});
|
|
7685
8070
|
const affects = vuln.affects ?? [];
|
|
7686
|
-
const results = affects.length > 0 ? affects.map((affect) =>
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
8071
|
+
const results = affects.length > 0 ? affects.map((affect) => {
|
|
8072
|
+
const instanceId = componentInstanceId(componentLookup, affect.ref);
|
|
8073
|
+
return createResult(ResultStatus.Failed, componentSummary(componentLookup, affect.ref), {
|
|
8074
|
+
codeDesc: formatCodeDesc$4(componentLookup, affect.ref),
|
|
8075
|
+
startTime: scanTime,
|
|
8076
|
+
...instanceId ? {
|
|
8077
|
+
resource: "component",
|
|
8078
|
+
resourceId: instanceId
|
|
8079
|
+
} : {}
|
|
8080
|
+
});
|
|
8081
|
+
}) : [createResult(ResultStatus.Failed, void 0, {
|
|
7690
8082
|
codeDesc: `Vulnerability ${vuln.id}`,
|
|
7691
8083
|
startTime: scanTime
|
|
7692
8084
|
})];
|
|
7693
8085
|
const title = vuln.source?.name ? `${vuln.id} (${vuln.source.name})` : vuln.id;
|
|
7694
8086
|
const req = createRequirement(vuln.id, title, descriptions, impact, results, { tags });
|
|
7695
|
-
req.code = buildVulnCode$
|
|
8087
|
+
req.code = buildVulnCode$2(vuln);
|
|
7696
8088
|
if (cvssEntries.length > 0) req.cvss = cvssEntries;
|
|
7697
8089
|
if (cweIds.length > 0) req.cwe = cweIds;
|
|
7698
|
-
const
|
|
8090
|
+
const affectedPackages = buildAffectedPackages(componentLookup, affects);
|
|
8091
|
+
if (affectedPackages.length > 0) req.affectedPackages = affectedPackages;
|
|
8092
|
+
const refs = buildRefs$8(vuln);
|
|
7699
8093
|
if (refs !== void 0) req.refs = refs;
|
|
7700
8094
|
const controlType = deriveControlTypeFromTags(nist);
|
|
7701
8095
|
if (controlType !== void 0) req.controlType = controlType;
|
|
@@ -7758,7 +8152,7 @@ function convertHdfToCsv(input) {
|
|
|
7758
8152
|
ipAddress: ""
|
|
7759
8153
|
}];
|
|
7760
8154
|
for (const baseline of hdf.baselines) {
|
|
7761
|
-
if (!Array.isArray(baseline.requirements) || baseline.requirements.length === 0) throw new Error(`hdf-to-csv: baseline "${text(baseline.name)}" has no requirements`);
|
|
8155
|
+
if (!Array.isArray(baseline.requirements) || baseline.requirements.length === 0) throw new Error(`hdf-to-csv: baseline "${text$1(baseline.name)}" has no requirements`);
|
|
7762
8156
|
for (const requirement of baseline.requirements) for (const target of targetList) rows.push(createRow(baseline, requirement, target));
|
|
7763
8157
|
}
|
|
7764
8158
|
return buildCsv(rows, { sanitize: true });
|
|
@@ -7770,7 +8164,7 @@ function convertHdfToCsv(input) {
|
|
|
7770
8164
|
* the converters deliberately do not do. Rendering 0 here matches what Go emits,
|
|
7771
8165
|
* where reading .toFixed off an absent value threw a raw TypeError instead.
|
|
7772
8166
|
*/
|
|
7773
|
-
function numeric(value) {
|
|
8167
|
+
function numeric$1(value) {
|
|
7774
8168
|
return typeof value === "number" ? value : 0;
|
|
7775
8169
|
}
|
|
7776
8170
|
/**
|
|
@@ -7780,12 +8174,12 @@ function numeric(value) {
|
|
|
7780
8174
|
* nothing. Every schema-required STRING cell goes through here; the numeric
|
|
7781
8175
|
* ones go through numeric(), which serves the same purpose for a different type.
|
|
7782
8176
|
*/
|
|
7783
|
-
function text(value) {
|
|
8177
|
+
function text$1(value) {
|
|
7784
8178
|
return typeof value === "string" ? value : "";
|
|
7785
8179
|
}
|
|
7786
8180
|
/** The requirement id as text; also names the requirement in the no-results error. */
|
|
7787
8181
|
function requirementIdText$1(id) {
|
|
7788
|
-
return text(id);
|
|
8182
|
+
return text$1(id);
|
|
7789
8183
|
}
|
|
7790
8184
|
/**
|
|
7791
8185
|
* Create a CSV row from baseline, requirement, and target data
|
|
@@ -7806,14 +8200,14 @@ function createRow(baseline, requirement, target) {
|
|
|
7806
8200
|
const nistControls = extractArrayFromTags(requirement.tags, "nist");
|
|
7807
8201
|
const cciControls = extractArrayFromTags(requirement.tags, "cci");
|
|
7808
8202
|
const effectiveStatus = requirementEffectiveStatus(requirement);
|
|
7809
|
-
const effectiveImpact = numeric(requirement
|
|
7810
|
-
const disposition =
|
|
8203
|
+
const effectiveImpact = numeric$1(requirementEffectiveImpact(requirement)).toFixed(2);
|
|
8204
|
+
const disposition = requirementDisposition(requirement);
|
|
7811
8205
|
return {
|
|
7812
|
-
"Baseline ID": text(baseline.name),
|
|
8206
|
+
"Baseline ID": text$1(baseline.name),
|
|
7813
8207
|
"Baseline Version": baseline.version || "",
|
|
7814
8208
|
"Baseline Title": baseline.title || "",
|
|
7815
|
-
"Target ID": text(target.name),
|
|
7816
|
-
"Target Type": text(target.type),
|
|
8209
|
+
"Target ID": text$1(target.name),
|
|
8210
|
+
"Target Type": text$1(target.type),
|
|
7817
8211
|
"Requirement ID": requirementIdText$1(requirement.id),
|
|
7818
8212
|
"Requirement Title": requirement.title || "",
|
|
7819
8213
|
"Description": description,
|
|
@@ -7823,8 +8217,8 @@ function createRow(baseline, requirement, target) {
|
|
|
7823
8217
|
"Code": code,
|
|
7824
8218
|
"References": references,
|
|
7825
8219
|
"Severity": severity,
|
|
7826
|
-
"Impact": numeric(requirement.impact).toFixed(2),
|
|
7827
|
-
"Status": text(status),
|
|
8220
|
+
"Impact": numeric$1(requirement.impact).toFixed(2),
|
|
8221
|
+
"Status": text$1(status),
|
|
7828
8222
|
"NIST Controls": nistControls,
|
|
7829
8223
|
"CCI Controls": cciControls,
|
|
7830
8224
|
"Control Type": requirement.controlType ?? "",
|
|
@@ -7839,7 +8233,7 @@ function createRow(baseline, requirement, target) {
|
|
|
7839
8233
|
"Expires At": joinOverrides(requirement.statusOverrides, (o) => formatExpires(o.expiresAt)),
|
|
7840
8234
|
"CVSS": cvssScores(requirement.cvss),
|
|
7841
8235
|
"CWE": (requirement.cwe ?? []).join("; "),
|
|
7842
|
-
"EPSS": requirement.epss ? numeric(requirement.epss.score).toFixed(5) : "",
|
|
8236
|
+
"EPSS": requirement.epss ? numeric$1(requirement.epss.score).toFixed(5) : "",
|
|
7843
8237
|
"KEV": requirement.kev ? requirement.kev.inKev ? "true" : "false" : "",
|
|
7844
8238
|
"Target FQDN": target.fqdn,
|
|
7845
8239
|
"Target IP": target.ipAddress
|
|
@@ -7918,17 +8312,1211 @@ function descriptionByLabel(requirement, label) {
|
|
|
7918
8312
|
* it outright — a reject-versus-degrade split tracked on its own card. Joined with '; ' to
|
|
7919
8313
|
* match the NIST/CCI column convention.
|
|
7920
8314
|
*/
|
|
7921
|
-
function flattenRefs(refs) {
|
|
7922
|
-
if (!Array.isArray(refs)) return "";
|
|
7923
|
-
const out = [];
|
|
7924
|
-
for (const r of refs) {
|
|
7925
|
-
if (!r || typeof r !== "object") continue;
|
|
7926
|
-
const o = r;
|
|
7927
|
-
if (typeof o.url === "string") out.push(o.url);
|
|
7928
|
-
else if (typeof o.uri === "string") out.push(o.uri);
|
|
7929
|
-
else if (typeof o.ref === "string") out.push(o.ref);
|
|
8315
|
+
function flattenRefs(refs) {
|
|
8316
|
+
if (!Array.isArray(refs)) return "";
|
|
8317
|
+
const out = [];
|
|
8318
|
+
for (const r of refs) {
|
|
8319
|
+
if (!r || typeof r !== "object") continue;
|
|
8320
|
+
const o = r;
|
|
8321
|
+
if (typeof o.url === "string") out.push(o.url);
|
|
8322
|
+
else if (typeof o.uri === "string") out.push(o.uri);
|
|
8323
|
+
else if (typeof o.ref === "string") out.push(o.ref);
|
|
8324
|
+
}
|
|
8325
|
+
return out.join("; ");
|
|
8326
|
+
}
|
|
8327
|
+
//#endregion
|
|
8328
|
+
//#region converters/hdf-to-html/typescript/assets.ts
|
|
8329
|
+
/** The vendored file carries no trailing newline, so one separates it from the report layer. */
|
|
8330
|
+
const STYLESHEET = "/*!\n * Blades CSS 🥷 v2.4\n * Copyright 2026 (https://blades.ninja/)\n * Licensed under MIT\n *//*!\n * Pico CSS ✨ v2.5\n * Copyright 2019-2025 (https://picocss.com)\n * Copyright 2026 (https://blades.ninja/css/pico/)\n * Licensed under MIT\n */:host,:scope{--pico-font-family-emoji:\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";--pico-font-family-sans-serif:system-ui,\"Segoe UI\",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,\"Helvetica Neue\",sans-serif,var(--pico-font-family-emoji);--pico-font-family-monospace:ui-monospace,SFMono-Regular,\"SF Mono\",Menlo,Consolas,\"Liberation Mono\",monospace,var(--pico-font-family-emoji);--pico-font-family:var(--pico-font-family-sans-serif);--pico-line-height:1.5;--pico-font-weight:400;--pico-font-size:100%;--pico-text-underline-offset:0.1rem;--pico-border-radius:0.25rem;--pico-border-width:0.0625rem;--pico-outline-width:0.125rem;--pico-transition:0.2s ease-in-out;--pico-spacing:1rem;--pico-typography-spacing-vertical:1rem;--pico-block-spacing-vertical:var(--pico-spacing);--pico-block-spacing-horizontal:var(--pico-spacing);--pico-grid-column-gap:var(--pico-spacing);--pico-grid-row-gap:var(--pico-spacing);--pico-form-element-spacing-vertical:0.75rem;--pico-form-element-spacing-horizontal:1rem;--pico-group-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-primary-focus);--pico-group-box-shadow-focus-with-input:0 0 0 0.0625rem var(--pico-form-element-border-color);--pico-modal-overlay-backdrop-filter:blur(0.375rem);--pico-nav-element-spacing-vertical:1rem;--pico-nav-element-spacing-horizontal:0.5rem;--pico-nav-link-spacing-vertical:0.5rem;--pico-nav-link-spacing-horizontal:0.5rem;--pico-nav-breadcrumb-divider:\">\";--pico-icon-checkbox:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");--pico-icon-minus:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E\");--pico-icon-chevron:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");--pico-icon-date:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E\");--pico-icon-time:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E\");--pico-icon-search:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E\");--pico-icon-close:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E\");--pico-icon-loading:url(\"data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E\")}@media (min-width:576px){:host,:scope{--pico-font-size:106.25%}}@media (min-width:768px){:host,:scope{--pico-font-size:112.5%}}@media (min-width:1024px){:host,:scope{--pico-font-size:118.75%}}@media (min-width:1280px){:host,:scope{--pico-font-size:125%}}@media (min-width:1536px){:host,:scope{--pico-font-size:131.25%}}a{--pico-text-decoration:underline}a.contrast,a.secondary{--pico-text-decoration:underline}small{--pico-font-size:0.875em}h1,h2,h3,h4,h5,h6{--pico-font-weight:700}h1{--pico-font-size:2rem;--pico-line-height:1.125;--pico-typography-spacing-top:3rem}h2{--pico-font-size:1.75rem;--pico-line-height:1.15;--pico-typography-spacing-top:2.625rem}h3{--pico-font-size:1.5rem;--pico-line-height:1.175;--pico-typography-spacing-top:2.25rem}h4{--pico-font-size:1.25rem;--pico-line-height:1.2;--pico-typography-spacing-top:1.874rem}h5{--pico-font-size:1.125rem;--pico-line-height:1.225;--pico-typography-spacing-top:1.6875rem}h6{--pico-font-size:1rem;--pico-line-height:1.25;--pico-typography-spacing-top:1.5rem}tfoot td,tfoot th,thead td,thead th{--pico-font-weight:600;--pico-border-width:0.1875rem}code,kbd,pre,samp{--pico-font-family:var(--pico-font-family-monospace)}kbd{--pico-font-weight:bolder}:where(select,textarea),input:not(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]\n){--pico-outline-width:0.0625rem}[type=search]{--pico-border-radius:5rem}[type=checkbox],[type=radio]{--pico-border-width:0.125rem}[type=checkbox][role=switch]{--pico-border-width:0.1875rem}details.dropdown summary:not([role=button]){--pico-outline-width:0.0625rem}nav details.dropdown summary:focus-visible{--pico-outline-width:0.125rem}[role=search]{--pico-border-radius:5rem}[role=group]:has(\nbutton.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus\n),[role=search]:has(\nbutton.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus\n){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[role=group]:has(\nbutton.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus\n),[role=search]:has(\nbutton.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus\n){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=submit],[role=search] button{--pico-form-element-spacing-horizontal:2rem}details summary[role=button]:not(.outline)::after{filter:brightness(0) invert(1)}[aria-busy=true]:not(input,select,textarea):is(\nbutton,[type=submit],[type=button],[type=reset],[role=button]\n):not(.outline)::before{filter:brightness(0) invert(1)}:host(:not([data-theme=dark])),:scope:not([data-theme=dark]),[data-theme=light]{color-scheme:light;--pico-background-color:#fff;--pico-color:#373c44;--pico-text-selection-color:rgba(2, 154, 232, 0.25);--pico-muted-color:#646b79;--pico-muted-border-color:rgb(231, 234, 239.5);--pico-primary:#0172ad;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 114, 173, 0.5);--pico-primary-hover:#015887;--pico-primary-hover-background:#02659a;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(2, 154, 232, 0.5);--pico-primary-inverse:#fff;--pico-secondary:#5d6b89;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(93, 107, 137, 0.5);--pico-secondary-hover:#48536b;--pico-secondary-hover-background:#48536b;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(93, 107, 137, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#181c25;--pico-contrast-background:#181c25;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(24, 28, 37, 0.5);--pico-contrast-hover:#000;--pico-contrast-hover-background:#000;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-secondary-hover);--pico-contrast-focus:rgba(93, 107, 137, 0.25);--pico-contrast-inverse:#fff;--pico-box-shadow:0 0.5rem 2.5rem rgba(129, 145, 181, 0.2);--pico-h1-color:#2d3138;--pico-h2-color:#373c44;--pico-h3-color:#424751;--pico-h4-color:#4d535e;--pico-h5-color:#5c6370;--pico-h6-color:#646b79;--pico-mark-background-color:rgb(252.5, 230.5, 191.5);--pico-mark-color:#0f1114;--pico-ins-color:rgb(28.5, 105.5, 84);--pico-del-color:rgb(136, 56.5, 53);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(243, 244.5, 246.75);--pico-code-color:#646b79;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(251, 251.5, 252.25);--pico-form-element-selected-background-color:#dfe3eb;--pico-form-element-border-color:#cfd5e2;--pico-form-element-color:#23262c;--pico-form-element-placeholder-color:var(--pico-muted-color);--pico-form-element-active-background-color:#fff;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(183.5, 105.5, 106.5);--pico-form-element-invalid-active-border-color:rgb(200.25, 79.25, 72.25);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:rgb(76, 154.5, 137.5);--pico-form-element-valid-active-border-color:rgb(39, 152.75, 118.75);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#bfc7d9;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#dfe3eb;--pico-range-active-border-color:#bfc7d9;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:var(--pico-background-color);--pico-card-border-color:var(--pico-muted-border-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(251, 251.5, 252.25);--pico-dropdown-background-color:#fff;--pico-dropdown-border-color:#eff1f4;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#eff1f4;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(232, 234, 237, 0.75);--pico-progress-background-color:#dfe3eb;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 154.5, 137.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");--pico-icon-invalid:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.25, 79.25, 72.25)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\")}:host(:not([data-theme=dark])) input:is(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]\n),:scope:not([data-theme=dark]) input:is(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]\n),[data-theme=light] input:is(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]\n){--pico-form-element-focus-color:var(--pico-primary-focus)}@media only screen and (prefers-color-scheme:dark){:host(:not([data-theme])),:scope:not([data-theme]){color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0 0.5rem 2.5rem rgba(7, 8.5, 12, 0.2);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");--pico-icon-invalid:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\")}:host(:not([data-theme])) input:is(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]\n),:scope:not([data-theme]) input:is(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]\n){--pico-form-element-focus-color:var(--pico-primary-focus)}:host(:not([data-theme])) details summary[role=button].contrast:not(.outline)::after,:scope:not([data-theme]) details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}:host(:not([data-theme])) [aria-busy=true]:not(input,select,textarea).contrast:is(\nbutton,[type=submit],[type=button],[type=reset],[role=button]\n):not(.outline)::before,:scope:not([data-theme]) [aria-busy=true]:not(input,select,textarea).contrast:is(\nbutton,[type=submit],[type=button],[type=reset],[role=button]\n):not(.outline)::before{filter:brightness(0)}}[data-theme=dark]{color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0 0.5rem 2.5rem rgba(7, 8.5, 12, 0.2);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");--pico-icon-invalid:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\")}[data-theme=dark] input:is(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]\n){--pico-form-element-focus-color:var(--pico-primary-focus)}[data-theme=dark] details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}[data-theme=dark] [aria-busy=true]:not(input,select,textarea).contrast:is(\nbutton,[type=submit],[type=button],[type=reset],[role=button]\n):not(.outline)::before{filter:brightness(0)}[type=checkbox],[type=radio],[type=range],progress{accent-color:var(--pico-primary)}*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:host),:where(:scope){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family);text-underline-offset:var(--pico-text-underline-offset);text-rendering:optimizeLegibility;overflow-wrap:break-word;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{width:100%;margin:0}main{display:block}body>footer,body>header,body>main{padding-block:var(--pico-block-spacing-vertical)}section{margin-bottom:var(--pico-block-spacing-vertical)}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--pico-spacing);padding-left:var(--pico-spacing)}@media (min-width:576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width:768px){.container{max-width:700px}}@media (min-width:1024px){.container{max-width:950px}}@media (min-width:1280px){.container{max-width:1200px}}@media (min-width:1536px){.container{max-width:1450px}}.grid{grid-column-gap:var(--pico-grid-column-gap);grid-row-gap:var(--pico-grid-row-gap);display:grid;grid-template-columns:1fr}@media (min-width:768px){.grid{grid-template-columns:repeat(auto-fit,minmax(0%,1fr))}}.grid>:not(.does-not-exist){min-width:0;margin-bottom:0}.overflow-auto{overflow:auto}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}address,blockquote,dl,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-style:normal;font-weight:var(--pico-font-weight)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family)}h1{--pico-color:var(--pico-h1-color)}h2{--pico-color:var(--pico-h2-color)}h3{--pico-color:var(--pico-h3-color)}h4{--pico-color:var(--pico-h4-color)}h5{--pico-color:var(--pico-h5-color)}h6{--pico-color:var(--pico-h6-color)}:where(article,address,blockquote,dl,figure,form,ol,p,pre,table,ul)~:is(h1,h2,h3,h4,h5,h6){margin-top:var(--pico-typography-spacing-top)}p{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup>*{margin-top:0;margin-bottom:0}hgroup>:not(:first-child):last-child{--pico-color:var(--pico-muted-color);--pico-font-weight:unset;font-size:1rem}:where(ol,ul) li{margin-bottom:calc(var(--pico-typography-spacing-vertical) * .25)}:where(dl,ol,ul) :where(dl,ol,ul){margin:0;margin-top:calc(var(--pico-typography-spacing-vertical) * .25)}ul li{list-style:square}mark{padding:.125rem .25rem;background-color:var(--pico-mark-background-color);color:var(--pico-mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--pico-typography-spacing-vertical) 0;padding:var(--pico-spacing);border-right:none;border-left:.25rem solid var(--pico-blockquote-border-color);border-inline-start:0.25rem solid var(--pico-blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--pico-typography-spacing-vertical) * .5);color:var(--pico-blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--pico-ins-color);text-decoration:none}del{color:var(--pico-del-color)}::-moz-selection{background-color:var(--pico-text-selection-color)}::selection{background-color:var(--pico-text-selection-color)}:where(a:not([role=button])),[role=link]{--pico-color:var(--pico-primary);--pico-background-color:transparent;--pico-underline:var(--pico-primary-underline);outline:0;background-color:var(--pico-background-color);color:var(--pico-color);-webkit-text-decoration:var(--pico-text-decoration);text-decoration:var(--pico-text-decoration);text-decoration-color:var(--pico-underline);text-underline-offset:0.125em;transition:background-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition)}:where(a:not([role=button])):is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n),[role=link]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-primary-hover);--pico-underline:var(--pico-primary-hover-underline);--pico-text-decoration:underline}:where(a:not([role=button])):focus-visible,[role=link]:focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}:where(a:not([role=button])).secondary,[role=link].secondary{--pico-color:var(--pico-secondary);--pico-underline:var(--pico-secondary-underline)}:where(a:not([role=button])).secondary:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n),[role=link].secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-underline:var(--pico-secondary-hover-underline)}:where(a:not([role=button])).contrast,[role=link].contrast{--pico-color:var(--pico-contrast);--pico-underline:var(--pico-contrast-underline)}:where(a:not([role=button])).contrast:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n),[role=link].contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-contrast-hover);--pico-underline:var(--pico-contrast-hover-underline)}a[role=button]{display:inline-block}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[role=button],[type=button],[type=file]::file-selector-button,[type=reset],[type=submit],button{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);--pico-color:var(--pico-primary-inverse);--pico-box-shadow:var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:1rem;line-height:var(--pico-line-height);text-align:center;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition);user-select:none}[role=button]:is(:hover,:active,:focus),[role=button]:is([aria-current]:not([aria-current=false])),[type=button]:is(:hover,:active,:focus),[type=button]:is([aria-current]:not([aria-current=false])),[type=file]::file-selector-button:is(:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])),[type=reset]:is(:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false])),[type=submit]:is(:hover,:active,:focus),[type=submit]:is([aria-current]:not([aria-current=false])),button:is(:hover,:active,:focus),button:is([aria-current]:not([aria-current=false])){--pico-background-color:var(--pico-primary-hover-background);--pico-border-color:var(--pico-primary-hover-border);--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--pico-color:var(--pico-primary-inverse)}[role=button]:focus,[role=button]:is([aria-current]:not([aria-current=false])):focus,[type=button]:focus,[type=button]:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus,[type=submit]:focus,[type=submit]:is([aria-current]:not([aria-current=false])):focus,button:focus,button:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}[type=button],[type=reset],[type=submit]{margin-bottom:var(--pico-spacing)}:is(button,[type=submit],[type=button],[role=button]).secondary,[type=file]::file-selector-button,[type=reset]{--pico-background-color:var(--pico-secondary-background);--pico-border-color:var(--pico-secondary-border);--pico-color:var(--pico-secondary-inverse);cursor:pointer}:is(button,[type=submit],[type=button],[role=button]).secondary:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n),[type=file]::file-selector-button:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n),[type=reset]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border);--pico-color:var(--pico-secondary-inverse)}:is(button,[type=submit],[type=button],[role=button]).secondary:focus,:is(button,[type=submit],[type=button],[role=button]).secondary:is(\n[aria-current]:not([aria-current=false])\n):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}:is(button,[type=submit],[type=button],[role=button]).contrast{--pico-background-color:var(--pico-contrast-background);--pico-border-color:var(--pico-contrast-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n){--pico-background-color:var(--pico-contrast-hover-background);--pico-border-color:var(--pico-contrast-hover-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:focus,:is(button,[type=submit],[type=button],[role=button]).contrast:is(\n[aria-current]:not([aria-current=false])\n):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}:is(button,[type=submit],[type=button],[role=button]).outline,[type=reset].outline{--pico-background-color:transparent;--pico-color:var(--pico-primary);--pico-border-color:var(--pico-primary)}:is(button,[type=submit],[type=button],[role=button]).outline:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:transparent;--pico-color:var(--pico-primary-hover);--pico-border-color:var(--pico-primary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary,[type=reset].outline{--pico-color:var(--pico-secondary);--pico-border-color:var(--pico-secondary)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-border-color:var(--pico-secondary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast{--pico-color:var(--pico-contrast);--pico-border-color:var(--pico-contrast)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n){--pico-color:var(--pico-contrast-hover);--pico-border-color:var(--pico-contrast-hover)}:where(button,[type=submit],[type=reset],[type=button],[role=button])[disabled],:where(fieldset[disabled]) :is(button,[type=submit],[type=button],[type=reset],[role=button]){opacity:.5;pointer-events:none}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}td,th{padding:calc(var(--pico-spacing)/ 2) var(--pico-spacing);border-bottom:var(--pico-border-width) solid var(--pico-table-border-color);background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);text-align:start}tfoot td,tfoot th{border-top:var(--pico-border-width) solid var(--pico-table-border-color);border-bottom:0}table.striped tbody tr:nth-child(odd) td,table.striped tbody tr:nth-child(odd) th{background-color:var(--pico-table-row-stripped-background-color)}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img:not([height]){max-width:100%;height:auto}img{border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:host),svg:not(:scope){overflow:hidden}code,kbd,pre,samp,var{font-size:.875em;font-family:var(--pico-font-family)}pre code,pre samp{font-size:inherit;font-family:inherit}pre{-ms-overflow-style:scrollbar;overflow:auto}code,kbd,pre,samp,var{border-radius:var(--pico-border-radius);background:var(--pico-code-background-color);color:var(--pico-code-color);font-weight:var(--pico-font-weight);line-height:initial}code,kbd,samp,var{display:inline-block;padding:.375rem}pre{display:block;margin-bottom:var(--pico-spacing);overflow-x:auto}pre>code,pre>samp{display:block;padding:var(--pico-spacing);background:0 0;line-height:var(--pico-line-height)}kbd{background-color:var(--pico-code-kbd-background-color);color:var(--pico-code-kbd-color);vertical-align:baseline}figure{display:block;margin:0;padding:0}figure figcaption{padding:calc(var(--pico-spacing) * .5) 0;color:var(--pico-muted-color)}hr{height:0;margin:var(--pico-typography-spacing-vertical) 0;border:0;border-top:1px solid var(--pico-muted-border-color);color:inherit}[hidden],template{display:none!important}canvas{display:inline-block}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--pico-line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:0}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type=file],[type=range]{padding:0;border-width:0}input:not([type=checkbox],[type=radio],[type=range]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2)}fieldset{width:100%;min-width:0;margin:0;margin-bottom:var(--pico-spacing);padding:0;border:0}fieldset legend,label{display:block;margin-bottom:calc(var(--pico-spacing) * .375);color:var(--pico-color);font-weight:var(--pico-form-label-font-weight,var(--pico-font-weight))}fieldset legend{margin-bottom:calc(var(--pico-spacing) * .5)}button[type=submit],input:not([type=checkbox],[type=radio]),select,textarea{width:100%}input:not([type=checkbox],[type=radio],[type=range],[type=file]),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal)}input,select,textarea{--pico-background-color:var(--pico-form-element-background-color);--pico-border-color:var(--pico-form-element-border-color);--pico-color:var(--pico-form-element-color);--pico-box-shadow:none;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not(\n[type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[readonly]\n):is(:active,:focus){--pico-background-color:var(--pico-form-element-active-background-color)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[role=switch],[readonly]):is(\n:active,:focus\n){--pico-border-color:var(--pico-form-element-active-border-color)}:where(select,textarea):not([readonly]):focus,input:not(\n[type=submit],[type=button],[type=reset],[type=range],[type=file],[readonly]\n):focus{--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}:where(fieldset[disabled]) :is(input:not([type=submit],[type=button],[type=reset]),select,textarea),input:not([type=submit],[type=button],[type=reset])[disabled],label[aria-disabled=true],select[disabled],textarea[disabled]{opacity:var(--pico-form-element-disabled-opacity);pointer-events:none}label[aria-disabled=true] input[disabled]{opacity:1}:where(input,select,textarea):not(\n[type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range]\n)[aria-invalid]{padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal)!important;padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input,select,textarea):not(\n[type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range]\n)[aria-invalid=false]:not(select){background-image:var(--pico-icon-valid)}:where(input,select,textarea):not(\n[type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range]\n)[aria-invalid=true]:not(select){background-image:var(--pico-icon-invalid)}:where(input,select,textarea)[aria-invalid=false]{--pico-border-color:var(--pico-form-element-valid-border-color)}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus){--pico-border-color:var(--pico-form-element-valid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus):not(\n[type=checkbox],[type=radio]\n){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-valid-focus-color)!important}:where(input,select,textarea)[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus){--pico-border-color:var(--pico-form-element-invalid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus):not(\n[type=checkbox],[type=radio]\n){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-invalid-focus-color)!important}[dir=rtl] :where(input,select,textarea):not([type=checkbox],[type=radio]):is(\n[aria-invalid],[aria-invalid=true],[aria-invalid=false]\n){background-position:center left .75rem}input::-webkit-input-placeholder,input::placeholder,select:invalid,textarea::-webkit-input-placeholder,textarea::placeholder{color:var(--pico-form-element-placeholder-color);opacity:1}input:not([type=checkbox],[type=radio]),select,textarea{margin-bottom:var(--pico-spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal);padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);background-image:var(--pico-icon-chevron);background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}select[multiple] option:checked{background:var(--pico-form-element-selected-background-color);color:var(--pico-form-element-color)}[dir=rtl] select:not([multiple],[size]){background-position:center left .75rem}textarea{display:block;resize:vertical}textarea[aria-invalid]{--pico-icon-height:calc(\n 1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 +\n var(--pico-border-width) * 2\n );background-position:top right .75rem!important;background-size:1rem var(--pico-icon-height)!important}:where(input,select,textarea,fieldset,.grid)+small{display:block;width:100%;margin-top:calc(var(--pico-spacing) * -.75);margin-bottom:var(--pico-spacing);color:var(--pico-muted-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=false]+small{color:var(--pico-ins-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=true]+small{color:var(--pico-del-color)}label>:where(input,select,textarea){margin-top:calc(var(--pico-spacing) * .25)}label:has([type=checkbox],[type=radio]){width:-moz-fit-content;width:fit-content;cursor:pointer}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-.125em;margin-inline-end:.5em;border-width:var(--pico-border-width);vertical-align:middle;cursor:pointer}[type=checkbox]::-ms-check,[type=radio]::-ms-check{display:none}[type=checkbox]:checked,[type=checkbox]:checked:active,[type=checkbox]:checked:focus,[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-checkbox);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=checkbox]~label,[type=radio]~label{display:inline-block;margin-bottom:0;cursor:pointer}[type=checkbox]~label:not(:last-of-type),[type=radio]~label:not(:last-of-type){margin-inline-end:1em}[type=checkbox]:indeterminate{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-minus);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=radio]{border-radius:50%}[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-inverse);border-width:.35em;background-image:none}[type=checkbox][role=switch]{--pico-background-color:var(--pico-switch-background-color);--pico-color:var(--pico-switch-color);width:2.25em;height:1.25em;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:1.25em;background-color:var(--pico-background-color);line-height:1.25em}[type=checkbox][role=switch]:not([aria-invalid]){--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:before{display:block;aspect-ratio:1;height:100%;border-radius:50%;background-color:var(--pico-color);box-shadow:var(--pico-switch-thumb-box-shadow);content:\"\";transition:margin .1s ease-in-out}[type=checkbox][role=switch]:focus{--pico-background-color:var(--pico-switch-background-color);--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:checked{--pico-background-color:var(--pico-switch-checked-background-color);--pico-border-color:var(--pico-switch-checked-background-color);background-image:none}[type=checkbox][role=switch]:checked::before{margin-inline-start:calc(2.25em - 1.25em)}[type=checkbox][role=switch][disabled]{--pico-background-color:var(--pico-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus{--pico-background-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true]{--pico-background-color:var(--pico-form-element-invalid-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus,[type=radio][aria-invalid=false]:checked,[type=radio][aria-invalid=false]:checked:active,[type=radio][aria-invalid=false]:checked:focus{--pico-border-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true],[type=radio]:checked:active[aria-invalid=true],[type=radio]:checked:focus[aria-invalid=true],[type=radio]:checked[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}[type=color]::-webkit-color-swatch-wrapper{padding:0}[type=color]::-moz-focus-inner{padding:0}[type=color]::-webkit-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}[type=color]::-moz-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}input:not([type=checkbox],[type=radio],[type=range],[type=file]):is(\n[type=date],[type=datetime-local],[type=month],[type=time],[type=week]\n){--pico-icon-position:0.75rem;--pico-icon-width:1rem;min-width:0;padding-right:calc(var(--pico-icon-width) + var(--pico-icon-position));background-image:var(--pico-icon-date);background-position:center right var(--pico-icon-position);background-size:var(--pico-icon-width) auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=time]{background-image:var(--pico-icon-time)}[type=date]::-webkit-calendar-picker-indicator,[type=datetime-local]::-webkit-calendar-picker-indicator,[type=month]::-webkit-calendar-picker-indicator,[type=time]::-webkit-calendar-picker-indicator,[type=week]::-webkit-calendar-picker-indicator{width:var(--pico-icon-width);margin-right:calc(var(--pico-icon-width) * -1);margin-left:var(--pico-icon-position);opacity:0}@-moz-document url-prefix(){[type=date],[type=datetime-local],[type=month],[type=time],[type=week]{padding-right:var(--pico-form-element-spacing-horizontal)!important;background-image:none!important}}[dir=rtl] :is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){text-align:end}[type=file]{--pico-color:var(--pico-muted-color);margin-left:calc(var(--pico-outline-width) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) 0;padding-left:var(--pico-outline-width);border:0;border-radius:0;background:0 0}[type=file]::file-selector-button{margin-right:calc(var(--pico-spacing)/ 2);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal)}[type=file]:is(:hover,:active,:focus)::file-selector-button{--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border)}[type=file]:focus::file-selector-button{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:0 0}[type=range]::-webkit-slider-runnable-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-webkit-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-moz-range-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-moz-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-ms-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-ms-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-webkit-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-moz-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-ms-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]:active,[type=range]:focus-within{--pico-range-border-color:var(--pico-range-active-border-color);--pico-range-thumb-color:var(--pico-range-thumb-active-color)}[type=range]:active::-webkit-slider-thumb{transform:scale(1.25)}[type=range]:active::-moz-range-thumb{transform:scale(1.25)}[type=range]:active::-ms-thumb{transform:scale(1.25)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem);background-image:var(--pico-icon-search);background-position:center left calc(var(--pico-form-element-spacing-horizontal) + .125rem);background-size:1rem auto;background-repeat:no-repeat}input:not(\n[type=checkbox],[type=radio],[type=range],[type=file]\n)[type=search][aria-invalid]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem)!important;background-position:center left 1.125rem,center right .75rem}input:not(\n[type=checkbox],[type=radio],[type=range],[type=file]\n)[type=search][aria-invalid=false]{background-image:var(--pico-icon-search),var(--pico-icon-valid)}input:not(\n[type=checkbox],[type=radio],[type=range],[type=file]\n)[type=search][aria-invalid=true]{background-image:var(--pico-icon-search),var(--pico-icon-invalid)}[dir=rtl] :where(input):not(\n[type=checkbox],[type=radio],[type=range],[type=file]\n)[type=search]{background-position:center right 1.125rem}[dir=rtl] :where(input):not(\n[type=checkbox],[type=radio],[type=range],[type=file]\n)[type=search][aria-invalid]{background-position:center right 1.125rem,center left .75rem}details{display:block;margin-bottom:var(--pico-spacing)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--pico-transition)}details summary:not([role]){color:var(--pico-accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--pico-spacing,1rem) * .5);float:right;transform:rotate(-90deg);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:\"\";transition:transform var(--pico-transition)}details summary:focus{outline:0}details summary:focus:not([role]){color:var(--pico-accordion-active-summary-color)}details summary:focus-visible:not([role]){outline:var(--pico-outline-width) solid var(--pico-primary-focus);outline-offset:calc(var(--pico-spacing,1rem) * 0.5);color:var(--pico-primary)}details summary[role=button]{width:100%;text-align:start}details summary[role=button]::after{height:calc(1rem * var(--pico-line-height,1.5))}details[open]>summary{margin-bottom:var(--pico-spacing)}details[open]>summary:not([role]):not(:focus){color:var(--pico-accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir=rtl] details summary{text-align:end}[dir=rtl] details summary::after{float:left;background-position:left center}article{margin-bottom:var(--pico-block-spacing-vertical);padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);border-radius:var(--pico-border-radius);background:var(--pico-card-background-color);box-shadow:var(--pico-card-box-shadow)}article>footer,article>header{margin-right:calc(var(--pico-block-spacing-horizontal) * -1);margin-left:calc(var(--pico-block-spacing-horizontal) * -1);padding:calc(var(--pico-block-spacing-vertical) * .66) var(--pico-block-spacing-horizontal);background-color:var(--pico-card-sectioning-background-color)}article>header{margin-top:calc(var(--pico-block-spacing-vertical) * -1);margin-bottom:var(--pico-block-spacing-vertical);border-bottom:var(--pico-border-width) solid var(--pico-card-border-color);border-top-right-radius:var(--pico-border-radius);border-top-left-radius:var(--pico-border-radius)}article>footer{margin-top:var(--pico-block-spacing-vertical);margin-bottom:calc(var(--pico-block-spacing-vertical) * -1);border-top:var(--pico-border-width) solid var(--pico-card-border-color);border-bottom-right-radius:var(--pico-border-radius);border-bottom-left-radius:var(--pico-border-radius)}details.dropdown{position:relative;border-bottom:none}details.dropdown>a::after,details.dropdown>button::after,details.dropdown>summary::after{display:block;width:1rem;height:calc(1rem * var(--pico-line-height,1.5));margin-inline-start:.25rem;float:right;transform:rotate(0) translateX(.2rem);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:\"\"}nav details.dropdown{margin-bottom:0}details.dropdown>summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-form-element-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-form-element-background-color);color:var(--pico-form-element-placeholder-color);line-height:inherit;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition);user-select:none}details.dropdown>summary:not([role]):active,details.dropdown>summary:not([role]):focus{border-color:var(--pico-form-element-active-border-color);background-color:var(--pico-form-element-active-background-color)}details.dropdown>summary:not([role]):focus{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}details.dropdown>summary:not([role]):focus-visible{outline:0}details.dropdown>summary:not([role])[aria-invalid=false]{--pico-form-element-border-color:var(--pico-form-element-valid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-valid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-valid-focus-color)}details.dropdown>summary:not([role])[aria-invalid=true]{--pico-form-element-border-color:var(--pico-form-element-invalid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-invalid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-invalid-focus-color)}nav details.dropdown{display:inline-block;margin:calc(var(--pico-nav-element-spacing-vertical) * -1) 0}nav details.dropdown>summary::after{transform:rotate(0) translateX(0)}nav details.dropdown>summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 2);padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav details.dropdown>summary:not([role]):focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}details.dropdown>summary+ul{display:flex;z-index:99;position:absolute;left:0;flex-direction:column;width:100%;min-width:-moz-fit-content;min-width:fit-content;margin:0;margin-top:var(--pico-outline-width);padding:0;border:var(--pico-border-width) solid var(--pico-dropdown-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-dropdown-background-color);box-shadow:var(--pico-dropdown-box-shadow);color:var(--pico-dropdown-color);white-space:nowrap;opacity:0;transition:opacity var(--pico-transition),transform 0s ease-in-out 1s}details.dropdown>summary+ul[dir=rtl]{right:0;left:auto}details.dropdown>summary+ul li{width:100%;margin-bottom:0;padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);list-style:none}details.dropdown>summary+ul li:first-of-type{margin-top:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown>summary+ul li:last-of-type{margin-bottom:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown>summary+ul li a{display:block;margin:calc(var(--pico-form-element-spacing-vertical) * -.5) calc(var(--pico-form-element-spacing-horizontal) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);overflow:hidden;border-radius:0;color:var(--pico-dropdown-color);text-decoration:none;text-overflow:ellipsis}details.dropdown>summary+ul li a:active,details.dropdown>summary+ul li a:focus,details.dropdown>summary+ul li a:focus-visible,details.dropdown>summary+ul li a:hover,details.dropdown>summary+ul li a[aria-current]:not([aria-current=false]){background-color:var(--pico-dropdown-hover-background-color)}details.dropdown>summary+ul li label{width:100%}details.dropdown>summary+ul li:has(label):hover{background-color:var(--pico-dropdown-hover-background-color)}details.dropdown[open]>summary{margin-bottom:0}details.dropdown[open]>summary+ul{transform:scaleY(1);opacity:1;transition:opacity var(--pico-transition),transform 0s ease-in-out 0s}details.dropdown[open]>summary::before{display:block;z-index:1;position:fixed;width:100vw;height:100vh;inset:0;background:0 0;content:\"\";cursor:default}label>details.dropdown{margin-top:calc(var(--pico-spacing) * .25)}[role=group],[role=search]{display:inline-flex;position:relative;width:100%;margin-bottom:var(--pico-spacing);border-radius:var(--pico-border-radius);box-shadow:var(--pico-group-box-shadow,0 0 0 transparent);vertical-align:middle;transition:box-shadow var(--pico-transition)}[role=group] input:not([type=checkbox],[type=radio]),[role=group] select,[role=group]>*,[role=search] input:not([type=checkbox],[type=radio]),[role=search] select,[role=search]>*{position:relative;flex:1 1 auto;margin-bottom:0}[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=group]>:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child),[role=search]>:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}[role=group] input:not([type=checkbox],[type=radio]):not(:last-child),[role=group] select:not(:last-child),[role=group]>:not(:last-child),[role=search] input:not([type=checkbox],[type=radio]):not(:last-child),[role=search] select:not(:last-child),[role=search]>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}[role=group] input:not([type=checkbox],[type=radio]):focus,[role=group] select:focus,[role=group]>:focus,[role=search] input:not([type=checkbox],[type=radio]):focus,[role=search] select:focus,[role=search]>:focus{z-index:2}[role=group] [role=button]:not(:first-child),[role=group] [type=button]:not(:first-child),[role=group] [type=reset]:not(:first-child),[role=group] [type=submit]:not(:first-child),[role=group] button:not(:first-child),[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=search] [role=button]:not(:first-child),[role=search] [type=button]:not(:first-child),[role=search] [type=reset]:not(:first-child),[role=search] [type=submit]:not(:first-child),[role=search] button:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child){margin-left:calc(var(--pico-border-width) * -1)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=reset],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=reset],[role=search] [type=submit],[role=search] button{width:auto}@supports selector(:has(*)){[role=group]:has(\nbutton:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus\n),[role=search]:has(\nbutton:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus\n){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-button)}[role=group]:has(\nbutton:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus\n) input:not([type=checkbox],[type=radio]),[role=group]:has(\nbutton:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus\n) select,[role=search]:has(\nbutton:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus\n) input:not([type=checkbox],[type=radio]),[role=search]:has(\nbutton:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus\n) select{border-color:transparent}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus),[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-input)}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) button,[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) button{--pico-button-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-border);--pico-button-hover-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-hover-border)}[role=group] [role=button]:focus,[role=group] [type=button]:focus,[role=group] [type=reset]:focus,[role=group] [type=submit]:focus,[role=group] button:focus,[role=search] [role=button]:focus,[role=search] [type=button]:focus,[role=search] [type=reset]:focus,[role=search] [type=submit]:focus,[role=search] button:focus{box-shadow:none}}[role=search]>:first-child{border-top-left-radius:5rem;border-bottom-left-radius:5rem}[role=search]>:last-child{border-top-right-radius:5rem;border-bottom-right-radius:5rem}[aria-busy=true]:not(input,select,textarea,html,form){white-space:nowrap}[aria-busy=true]:not(input,select,textarea,html,form)::before{display:inline-block;width:1em;height:1em;background-image:var(--pico-icon-loading);background-size:1em auto;background-repeat:no-repeat;content:\"\";vertical-align:-.125em}[aria-busy=true]:not(input,select,textarea,html,form):not(:empty)::before{margin-inline-end:calc(var(--pico-spacing) * .5)}[aria-busy=true]:not(input,select,textarea,html,form):empty{text-align:center}[role=button][aria-busy=true],[type=button][aria-busy=true],[type=reset][aria-busy=true],[type=submit][aria-busy=true],a[aria-busy=true],button[aria-busy=true]{pointer-events:none}:host,:scope{--pico-scrollbar-width:0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:0;border:0;backdrop-filter:var(--pico-modal-overlay-backdrop-filter);background-color:var(--pico-modal-overlay-background-color);color:var(--pico-color)}dialog>article{width:100%;max-height:calc(100vh - var(--pico-spacing) * 2);margin:var(--pico-spacing);overflow:auto}@media (min-width:576px){dialog>article{max-width:510px}}@media (min-width:768px){dialog>article{max-width:700px}}dialog>article>header>*{margin-bottom:0}dialog>article>header .close,dialog>article>header :is(a,button)[rel=prev]{margin:0;margin-left:var(--pico-spacing);padding:0;float:right}dialog>article>footer{text-align:end}dialog>article>footer [role=button],dialog>article>footer button{margin-bottom:0}dialog>article>footer [role=button]:not(:first-of-type),dialog>article>footer button:not(:first-of-type){margin-left:calc(var(--pico-spacing) * .5)}dialog>article .close,dialog>article :is(a,button)[rel=prev]{display:block;width:1rem;height:1rem;margin-top:calc(var(--pico-spacing) * -1);margin-bottom:var(--pico-spacing);margin-left:auto;border:none;background-image:var(--pico-icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;background-color:transparent;opacity:.5;transition:opacity var(--pico-transition)}dialog>article .close:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),dialog>article :is(a,button)[rel=prev]:is(\n[aria-current]:not([aria-current=false]),:hover,:active,:focus\n){opacity:1}dialog:not([open]),dialog[open=false]{display:none}.modal-is-open{padding-right:var(--pico-scrollbar-width,0);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto;touch-action:auto}:where(.modal-is-opening,.modal-is-closing) dialog,:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening,.modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:\"\"}nav,nav ul{display:flex}nav{justify-content:space-between;overflow:visible}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--pico-nav-element-spacing-vertical) var(--pico-nav-element-spacing-horizontal)}nav li :where(a,[role=link]){display:inline-block;margin:calc(var(--pico-nav-link-spacing-vertical) * -1) calc(var(--pico-nav-link-spacing-horizontal) * -1);padding:var(--pico-nav-link-spacing-vertical) var(--pico-nav-link-spacing-horizontal);border-radius:var(--pico-border-radius)}nav li :where(a,[role=link]):not(:hover){text-decoration:none}nav li [role=button],nav li [type=button],nav li button,nav li input:not([type=checkbox],[type=radio],[type=range],[type=file]),nav li select{height:auto;margin-right:inherit;margin-bottom:0;margin-left:inherit;padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb]{align-items:center;justify-content:start}nav[aria-label=breadcrumb] ul li:not(:first-child){margin-inline-start:var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb] ul li a{margin:calc(var(--pico-nav-link-spacing-vertical) * -1) 0;margin-inline-start:calc(var(--pico-nav-link-spacing-horizontal) * -1)}nav[aria-label=breadcrumb] ul li:not(:last-child)::after{display:inline-block;position:absolute;width:calc(var(--pico-nav-link-spacing-horizontal) * 4);margin:0 calc(var(--pico-nav-link-spacing-horizontal) * -1);content:var(--pico-nav-breadcrumb-divider);color:var(--pico-muted-color);text-align:center;text-decoration:none;white-space:nowrap}nav[aria-label=breadcrumb] a[aria-current]:not([aria-current=false]){background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}aside li,aside nav,aside ol,aside ul{display:block}aside li{padding:calc(var(--pico-nav-element-spacing-vertical) * .5) var(--pico-nav-element-spacing-horizontal)}aside li a{display:block}aside li [role=button]{margin:inherit}[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child)::after{content:\"\\\\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:.5rem;margin-bottom:calc(var(--pico-spacing) * .5);overflow:hidden;border:0;border-radius:var(--pico-border-radius);background-color:var(--pico-progress-background-color);color:var(--pico-progress-color)}progress::-webkit-progress-bar{border-radius:var(--pico-border-radius);background:0 0}progress[value]::-webkit-progress-value{background-color:var(--pico-progress-color);-webkit-transition:inline-size var(--pico-transition);transition:inline-size var(--pico-transition)}progress::-moz-progress-bar{background-color:var(--pico-progress-color)}@media (prefers-reduced-motion:no-preference){progress:indeterminate{background:var(--pico-progress-background-color) linear-gradient(to right,var(--pico-progress-color) 30%,var(--pico-progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion:no-preference){[dir=rtl] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input,[role=button]){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip]::after,[data-tooltip]::before,[data-tooltip][data-placement=top]::after,[data-tooltip][data-placement=top]::before{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%,-.25rem);border-radius:var(--pico-border-radius);background:var(--pico-tooltip-background-color);content:attr(data-tooltip);color:var(--pico-tooltip-color);font-style:normal;font-weight:var(--pico-font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip]::after,[data-tooltip][data-placement=top]::after{padding:0;transform:translate(-50%,0);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:\"\";color:var(--pico-tooltip-background-color)}[data-tooltip][data-placement=bottom]::after,[data-tooltip][data-placement=bottom]::before{top:100%;bottom:auto;transform:translate(-50%,.25rem)}[data-tooltip][data-placement=bottom]:after{transform:translate(-50%,-.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement=left]::after,[data-tooltip][data-placement=left]::before{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-.25rem,-50%)}[data-tooltip][data-placement=left]:after{transform:translate(.3rem,-50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement=right]::after,[data-tooltip][data-placement=right]::before{top:50%;right:auto;bottom:auto;left:100%;transform:translate(.25rem,-50%)}[data-tooltip][data-placement=right]:after{transform:translate(-.3rem,-50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{--pico-tooltip-slide-to:translate(-50%, -0.25rem);transform:translate(-50%,.75rem);animation-duration:.2s;animation-fill-mode:forwards;animation-name:tooltip-slide;opacity:0}[data-tooltip]:focus::after,[data-tooltip]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, 0rem);transform:translate(-50%,-.25rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover::after,[data-tooltip][data-placement=bottom]:hover::before{--pico-tooltip-slide-to:translate(-50%, 0.25rem);transform:translate(-50%,-.75rem);animation-name:tooltip-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, -0.3rem);transform:translate(-50%,-.5rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:focus::before,[data-tooltip][data-placement=left]:hover::after,[data-tooltip][data-placement=left]:hover::before{--pico-tooltip-slide-to:translate(-0.25rem, -50%);transform:translate(.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:hover::after{--pico-tooltip-caret-slide-to:translate(0.3rem, -50%);transform:translate(.05rem,-50%);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:focus::before,[data-tooltip][data-placement=right]:hover::after,[data-tooltip][data-placement=right]:hover::before{--pico-tooltip-slide-to:translate(0.25rem, -50%);transform:translate(-.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:hover::after{--pico-tooltip-caret-slide-to:translate(-0.3rem, -50%);transform:translate(-.05rem,-50%);animation-name:tooltip-caret-slide}}@keyframes tooltip-slide{to{transform:var(--pico-tooltip-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{transform:var(--pico-tooltip-caret-slide-to);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation}[dir=rtl]{direction:rtl}@media (prefers-reduced-motion:reduce){:not([aria-busy=true])::after,:not([aria-busy=true])::before,:not([aria-busy=true]):not(.unreduce-motion){background-attachment:initial!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important}}.columns{-moz-columns:25ch auto;columns:25ch auto}.columns>*{-moz-column-break-inside:avoid;break-inside:avoid}[data-jump-to=top]{position:fixed;right:0;bottom:0;padding-top:50vh}.breakout,.breakout-all{max-width:calc(10% + 65ch + 10%);padding-inline:10%}@media (width < 768px){.breakout,.breakout-all{max-width:calc(1rem + 65ch + 1rem);padding-inline:1rem}}.breakout-all>:is(\ntable,pre,figure,video,iframe,canvas,img,picture,.breakout-item,.breakout-item-max\n),.breakout>:is(\ntable,pre,figure,video,iframe,canvas,img,picture,.breakout-item,.breakout-item-max\n){width:-moz-fit-content;width:fit-content;min-width:100%;max-width:min(125%,100dvw);margin-left:50%;transform:translateX(-50%)}.breakout-all>:is(img,picture):not(.does-not-exist),.breakout>:is(img,picture):not(.does-not-exist){min-width:auto}.breakout-all>.breakout-item-max,.breakout>.breakout-item-max{width:min(125%,100dvw)!important}.breakout-all>:is(h2,h3,h4,h5,h6,hr):not([class]){position:relative}.breakout-all>:is(h2,h3,h4,h5,h6,hr):not([class])::before{display:block;position:absolute;background:gray;content:\"\";opacity:12.5%}.breakout-all>:is(h2,h3,h4,h5,h6):not([class])::before{top:50%;right:100%;width:10em;height:.25em;margin-right:.8ch;transform:translateY(-50%);background:linear-gradient(to left,gray,transparent)}.breakout-all>:is(h2,h3,h4,h5,h6):not([class]):where(hr + *)::before{display:none!important}.breakout-all>:is(hr){height:.5rem;overflow:visible;border:none}.breakout-all>:is(hr)::before{left:50%;width:100dvw;height:100%;transform:translateX(-50%)}h1,h2,h3,h4,h5,h6{position:relative}h1 a[aria-hidden=true],h2 a[aria-hidden=true],h3 a[aria-hidden=true],h4 a[aria-hidden=true],h5 a[aria-hidden=true],h6 a[aria-hidden=true]{visibility:hidden;position:absolute;top:50%;right:100%;padding-right:.2ch;transform:translateY(-50%);color:silver;text-decoration:none}@media (hover:hover){h1:hover a[aria-hidden=true],h2:hover a[aria-hidden=true],h3:hover a[aria-hidden=true],h4:hover a[aria-hidden=true],h5:hover a[aria-hidden=true],h6:hover a[aria-hidden=true]{visibility:visible}}ol[style*=\"--list-marker:\"],ul[style*=\"--list-marker:\"]{list-style-type:var(--list-marker)}ol[style*=\"--list-marker:\"]>li,ul[style*=\"--list-marker:\"]>li{list-style-type:inherit}ol li[style*=\"--list-marker:\"],ul li[style*=\"--list-marker:\"]{list-style-type:var(--list-marker)}ol li[data-marker]::marker,ul li[data-marker]::marker{content:attr(data-marker) \" \"}ol.markerless,ul.markerless{padding-inline-start:1.25rem}ol.markerless li,ul.markerless li{list-style:none}.unlist-all ol,.unlist-all ul,ol.unlist,ol.unlist-all,ul.unlist,ul.unlist-all{padding-inline-start:0}.unlist-all ol>li,.unlist-all ul>li,ol.unlist-all>li,ol.unlist>li,ul.unlist-all>li,ul.unlist>li{list-style:none}a:not(.does-not-exist):has(> i){display:inline-flex;overflow-y:clip;gap:.375ch;text-wrap:balance}a:not(.does-not-exist)>i{float:left;font-style:normal;text-underline-offset:-2em}a:not(.does-not-exist)>i>img{display:inline-block;max-width:none;height:1.25em;margin-block-start:calc(-0.25em / 2)}a:not(.does-not-exist)>i[class*=\" fa-\"],a:not(.does-not-exist)>i[class^=fa-]{line-height:inherit;--fa-width:auto}a:not(.does-not-exist)>i.fa-lg{line-height:normal}th hr{visibility:hidden;width:12ch;height:0;margin:0}th hr.lg{width:18ch}th hr.x2{width:24ch}table.borderless td,table.borderless th{border:none}.breakout-all>table:not(.does-not-exist),.breakout>table:not(.does-not-exist),table.responsive{display:block;width:-moz-max-content;width:max-content;min-width:auto;max-width:100dvw;margin-left:50%;padding-inline:7.5%;overflow-x:auto;transform:translateX(-50%);-webkit-overflow-scrolling:touch}.breakout-all>table:not(.does-not-exist) td,.breakout-all>table:not(.does-not-exist) th,.breakout>table:not(.does-not-exist) td,.breakout>table:not(.does-not-exist) th,table.responsive td,table.responsive th{padding-inline-start:0}@media (width < 768px){pre{border-radius:0}}code[data-caption]::before{display:block;margin-bottom:1rem;content:attr(data-caption);font-style:italic;opacity:50%}.has-float-label,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select){display:block;position:relative}.has-float-label label,.has-float-label>span,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) label,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select)>span{position:absolute;top:0;left:0;font-size:75%;cursor:text}.has-float-label :placeholder-shown:not(:focus)::-moz-placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) :placeholder-shown:not(:focus)::-moz-placeholder{opacity:0}.has-float-label :-moz-placeholder:not(:focus)::placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) :-moz-placeholder:not(:focus)::placeholder{opacity:0}.has-float-label :placeholder-shown:not(:focus)::placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) :placeholder-shown:not(:focus)::placeholder{opacity:0}.has-float-label:has(:-moz-placeholder:not(:focus)) label,.has-float-label:has(:-moz-placeholder:not(:focus))>span,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:-moz-placeholder:not(:focus)) label,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:-moz-placeholder:not(:focus))>span{font-size:inherit;opacity:50%}.has-float-label:has(:placeholder-shown:not(:focus)) label,.has-float-label:has(:placeholder-shown:not(:focus))>span,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:placeholder-shown:not(:focus)) label,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:placeholder-shown:not(:focus))>span{font-size:inherit;opacity:50%}.has-timeline>dl dd,dl.timeline dd{margin-block:0;padding:.25rem 0 .75rem 1rem;border-inline-start:0.25rem solid gray}:root{--blades-dark-filter:invert(100%) hue-rotate(180deg)}@media (prefers-color-scheme:dark){.dark-auto{filter:var(--blades-dark-filter)}}.dark-auto:where([data-theme=dark] *),.dark-auto[data-theme=dark]{filter:var(--blades-dark-filter)}.faded{opacity:50%}.faded:hover{opacity:87.5%}.invert ::-webkit-scrollbar{filter:invert(1)!important}.has-float-label label,.has-float-label>span,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) label,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select)>span{padding-inline-start:calc(1rem + 1px);padding-block-start:0.25rem;opacity:75%;transition:all .25s}.has-float-label input,.has-float-label select,.has-float-label textarea,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) input,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) select,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) textarea{margin-block-start:0;padding-inline-start:1rem;padding-block:1.125rem 0.375rem}.has-float-label input::-moz-placeholder,.has-float-label select::-moz-placeholder,.has-float-label textarea::-moz-placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) input::-moz-placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) select::-moz-placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) textarea::-moz-placeholder{opacity:100%;-moz-transition:all .25s;transition:all .25s}.has-float-label input::placeholder,.has-float-label select::placeholder,.has-float-label textarea::placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) input::placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) select::placeholder,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select) textarea::placeholder{opacity:100%;transition:all .25s}.has-float-label:has(:-moz-placeholder:not(:focus)) label,.has-float-label:has(:-moz-placeholder:not(:focus))>span,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:-moz-placeholder:not(:focus)) label,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:-moz-placeholder:not(:focus))>span{padding-block:0.75rem}.has-float-label:has(:placeholder-shown:not(:focus)) label,.has-float-label:has(:placeholder-shown:not(:focus))>span,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:placeholder-shown:not(:focus)) label,label:has(> span):has(input:not([type=checkbox],[type=radio],[type=range]),textarea,select):has(:placeholder-shown:not(:focus))>span{padding-block:0.75rem}html{overflow-x:clip;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{display:flex;flex-direction:column;min-height:100dvh}body>main{flex-grow:1}body{hyphens:auto;text-wrap:pretty}body a,body table{hyphens:none}a:not([href^=\"#\"]){text-decoration-thickness:1px}a:not([href^=\"#\"]):hover{text-decoration-thickness:2px}h1,h2,h3,h4,h5,h6{text-wrap:balance}h1{margin-bottom:1rem;font-size:2.5em}hr{margin-block:2em}ul ul{font-size:87.5%}dl dt{margin-block:0.25rem;font-weight:500}dl dd{margin-block-end:1rem}pre small{font-weight:lighter;opacity:75%}pre:has(code){background:#181c25!important}table th{font-weight:700;vertical-align:bottom}table td{vertical-align:top}table pre{margin-bottom:.25rem}[data-jump-to=top]{opacity:25%}[data-jump-to=top]:hover{opacity:75%}[data-jump-to=top]>i{display:inline-block;margin:.5rem;padding:.25rem .375rem;border-color:#000;color:#000;font-size:.75rem}.breakout-all>figure,.breakout-all>img,.breakout>figure,.breakout>img{margin-bottom:1rem}.faded a{text-decoration-style:dotted}\n/* The report layer, inlined after the vendored Pico+Blades stylesheet. It holds\n only what no framework ships: the status and severity palette, the dashboard,\n and the print rules. Everything else is driven through --pico-* variables so\n the vendored file is never edited. */\n\n:root {\n /* Stated here rather than inherited so the report's typography is system\n fonts by declaration, with nothing to download. */\n --pico-font-family-sans-serif: system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n --pico-font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace;\n --pico-font-family: var(--pico-font-family-sans-serif);\n /* Pico scales the root to 131.25% on a wide screen. A compliance report is a\n dense document read at length, so it stays at the browser's own size. */\n --pico-font-size: 100%;\n /* The page's own text colour, captured before the headings below redefine it. */\n --pico-report-ink: var(--pico-color);\n}\n/* Pico's heading scale is for prose; these are the report's section, panel and\n block labels, so they are driven back through its own per-heading token. */\nh2 { --pico-font-size: 1.3rem; letter-spacing: -0.01em; }\nh3 { --pico-font-size: 1.02rem; }\nh4, h5 { --pico-font-size: 0.78rem; margin: 1.2rem 0 0.45rem; color: var(--pico-muted-color); text-transform: uppercase; letter-spacing: 0.07em; }\n/* The three scopes Pico itself uses: light by default or when light is chosen,\n dark when the system asks and nothing was chosen, dark when dark was chosen. */\n:root:not([data-theme=\"dark\"]), [data-theme=\"light\"] {\n /* Pico's own input border is 1.47:1 against the page, under the 3:1 WCAG 1.4.11\n asks of a control boundary; the report raises it in both schemes. */\n --pico-form-element-border-color: #767f91;\n --pico-s-passed: #2e7d32; --pico-s-passed-inverse: #ffffff; --pico-t-passed: #e8f5e9; --pico-t-passed-inverse: #1b5e20;\n --pico-s-failed: #c62828; --pico-s-failed-inverse: #ffffff; --pico-t-failed: #ffebee; --pico-t-failed-inverse: #8e1616;\n --pico-s-not-applicable: #0277bd; --pico-s-not-applicable-inverse: #ffffff; --pico-t-not-applicable: #e1f5fe; --pico-t-not-applicable-inverse: #01497c;\n --pico-s-not-reviewed: #b45300; --pico-s-not-reviewed-inverse: #ffffff; --pico-t-not-reviewed: #fff3e0; --pico-t-not-reviewed-inverse: #7a3600;\n --pico-s-error: #3f51b5; --pico-s-error-inverse: #ffffff; --pico-t-error: #e8eaf6; --pico-t-error-inverse: #1a237e;\n --pico-s-unknown: #5c6670; --pico-s-unknown-inverse: #ffffff; --pico-t-unknown: #e9edf1; --pico-t-unknown-inverse: #16212d;\n --pico-s-critical: #c62828; --pico-s-critical-inverse: #ffffff; --pico-t-critical: #ffebee; --pico-t-critical-inverse: #8e1616;\n --pico-s-high: #bf360c; --pico-s-high-inverse: #ffffff; --pico-t-high: #fbe9e7; --pico-t-high-inverse: #8a2607;\n --pico-s-medium: #b45300; --pico-s-medium-inverse: #ffffff; --pico-t-medium: #fff3e0; --pico-t-medium-inverse: #7a3600;\n --pico-s-low: #806600; --pico-s-low-inverse: #ffffff; --pico-t-low: #fffde7; --pico-t-low-inverse: #5c4a00;\n --pico-s-none: #0277bd; --pico-s-none-inverse: #ffffff; --pico-t-none: #e1f5fe; --pico-t-none-inverse: #01497c;\n}\n@media only screen and (prefers-color-scheme: dark) {\n :root:not([data-theme]) {\n --pico-form-element-border-color: #6b7488;\n /* Pico's dark muted text is 4.41:1 on its own sectioning background. */\n --pico-muted-color: #868fa0;\n --pico-s-passed: #4cb04f; --pico-s-passed-inverse: #06200a; --pico-t-passed: #12301a; --pico-t-passed-inverse: #b9e4bb;\n --pico-s-failed: #ef7a70; --pico-s-failed-inverse: #2b0603; --pico-t-failed: #3d1614; --pico-t-failed-inverse: #ffc9c4;\n --pico-s-not-applicable: #03a9f4; --pico-s-not-applicable-inverse: #02202e; --pico-t-not-applicable: #0c3048; --pico-t-not-applicable-inverse: #b3e5fc;\n --pico-s-not-reviewed: #fe9900; --pico-s-not-reviewed-inverse: #2e1b00; --pico-t-not-reviewed: #3d2a0a; --pico-t-not-reviewed-inverse: #ffe0b2;\n --pico-s-error: #9fa8da; --pico-s-error-inverse: #10153d; --pico-t-error: #262c58; --pico-t-error-inverse: #dfe2f5;\n --pico-s-unknown: #aab6c2; --pico-s-unknown-inverse: #101820; --pico-t-unknown: #26374a; --pico-t-unknown-inverse: #e8edf2;\n --pico-s-critical: #ef7a70; --pico-s-critical-inverse: #2b0603; --pico-t-critical: #3d1614; --pico-t-critical-inverse: #ffc9c4;\n --pico-s-high: #ff8a65; --pico-s-high-inverse: #2e0d02; --pico-t-high: #3f1d12; --pico-t-high-inverse: #ffccbc;\n --pico-s-medium: #ff9800; --pico-s-medium-inverse: #2e1b00; --pico-t-medium: #3d2a0a; --pico-t-medium-inverse: #ffe0b2;\n --pico-s-low: #ffeb3b; --pico-s-low-inverse: #2b2600; --pico-t-low: #3a3508; --pico-t-low-inverse: #fff59d;\n --pico-s-none: #03a9f4; --pico-s-none-inverse: #02202e; --pico-t-none: #0c3048; --pico-t-none-inverse: #b3e5fc;\n }\n}\n[data-theme=\"dark\"] {\n --pico-form-element-border-color: #6b7488;\n --pico-muted-color: #868fa0;\n --pico-s-passed: #4cb04f; --pico-s-passed-inverse: #06200a; --pico-t-passed: #12301a; --pico-t-passed-inverse: #b9e4bb;\n --pico-s-failed: #ef7a70; --pico-s-failed-inverse: #2b0603; --pico-t-failed: #3d1614; --pico-t-failed-inverse: #ffc9c4;\n --pico-s-not-applicable: #03a9f4; --pico-s-not-applicable-inverse: #02202e; --pico-t-not-applicable: #0c3048; --pico-t-not-applicable-inverse: #b3e5fc;\n --pico-s-not-reviewed: #fe9900; --pico-s-not-reviewed-inverse: #2e1b00; --pico-t-not-reviewed: #3d2a0a; --pico-t-not-reviewed-inverse: #ffe0b2;\n --pico-s-error: #9fa8da; --pico-s-error-inverse: #10153d; --pico-t-error: #262c58; --pico-t-error-inverse: #dfe2f5;\n --pico-s-unknown: #aab6c2; --pico-s-unknown-inverse: #101820; --pico-t-unknown: #26374a; --pico-t-unknown-inverse: #e8edf2;\n --pico-s-critical: #ef7a70; --pico-s-critical-inverse: #2b0603; --pico-t-critical: #3d1614; --pico-t-critical-inverse: #ffc9c4;\n --pico-s-high: #ff8a65; --pico-s-high-inverse: #2e0d02; --pico-t-high: #3f1d12; --pico-t-high-inverse: #ffccbc;\n --pico-s-medium: #ff9800; --pico-s-medium-inverse: #2e1b00; --pico-t-medium: #3d2a0a; --pico-t-medium-inverse: #ffe0b2;\n --pico-s-low: #ffeb3b; --pico-s-low-inverse: #2b2600; --pico-t-low: #3a3508; --pico-t-low-inverse: #fff59d;\n --pico-s-none: #03a9f4; --pico-s-none-inverse: #02202e; --pico-t-none: #0c3048; --pico-t-none-inverse: #b3e5fc;\n}\n.c-passed { --c: var(--pico-s-passed); --c-inverse: var(--pico-s-passed-inverse); --t: var(--pico-t-passed); --t-inverse: var(--pico-t-passed-inverse); }\n.c-failed { --c: var(--pico-s-failed); --c-inverse: var(--pico-s-failed-inverse); --t: var(--pico-t-failed); --t-inverse: var(--pico-t-failed-inverse); }\n.c-not-applicable { --c: var(--pico-s-not-applicable); --c-inverse: var(--pico-s-not-applicable-inverse); --t: var(--pico-t-not-applicable); --t-inverse: var(--pico-t-not-applicable-inverse); }\n.c-not-reviewed { --c: var(--pico-s-not-reviewed); --c-inverse: var(--pico-s-not-reviewed-inverse); --t: var(--pico-t-not-reviewed); --t-inverse: var(--pico-t-not-reviewed-inverse); }\n.c-error { --c: var(--pico-s-error); --c-inverse: var(--pico-s-error-inverse); --t: var(--pico-t-error); --t-inverse: var(--pico-t-error-inverse); }\n.c-unknown { --c: var(--pico-s-unknown); --c-inverse: var(--pico-s-unknown-inverse); --t: var(--pico-t-unknown); --t-inverse: var(--pico-t-unknown-inverse); }\n.c-critical { --c: var(--pico-s-critical); --c-inverse: var(--pico-s-critical-inverse); --t: var(--pico-t-critical); --t-inverse: var(--pico-t-critical-inverse); }\n.c-high { --c: var(--pico-s-high); --c-inverse: var(--pico-s-high-inverse); --t: var(--pico-t-high); --t-inverse: var(--pico-t-high-inverse); }\n.c-medium { --c: var(--pico-s-medium); --c-inverse: var(--pico-s-medium-inverse); --t: var(--pico-t-medium); --t-inverse: var(--pico-t-medium-inverse); }\n.c-low { --c: var(--pico-s-low); --c-inverse: var(--pico-s-low-inverse); --t: var(--pico-t-low); --t-inverse: var(--pico-t-low-inverse); }\n.c-none { --c: var(--pico-s-none); --c-inverse: var(--pico-s-none-inverse); --t: var(--pico-t-none); --t-inverse: var(--pico-t-none-inverse); }\n\nhtml { scroll-padding-top: 5rem; }\n:focus-visible { outline: 3px solid var(--pico-primary); outline-offset: 2px; }\n.vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.skip { position: absolute; left: 0.75rem; top: -4rem; z-index: 20; background: var(--pico-card-background-color); color: var(--pico-color); padding: 0.5rem 0.9rem; border-radius: var(--pico-border-radius); border: 2px solid var(--pico-primary); font-weight: 600; text-decoration: none; }\n.skip:focus { top: 0.6rem; }\n\n.topbar { position: sticky; top: 0; z-index: 10; display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 1.25rem; background: var(--pico-primary-background); color: var(--pico-primary-inverse); padding: 0.85rem 1.75rem; }\n.topbar .brand { font-size: 1.2rem; font-weight: 700; margin: 0; color: inherit; }\n.topbar .report-type { font-size: 0.8rem; font-weight: 600; border: 1px solid currentcolor; border-radius: 5rem; padding: 0.1rem 0.7rem; }\n.topbar nav { margin-left: auto; padding: 0; }\n.topbar nav ul { margin: 0; color: inherit; }\n.topbar nav li { padding: 0; }\n.topbar a { color: inherit; text-decoration: none; font-size: 0.9rem; font-weight: 600; padding: 0.3rem 0.75rem; border-radius: 5rem; }\n.topbar a:hover { text-decoration: underline; }\n.topbar :focus-visible { outline-color: var(--pico-primary-inverse); }\n.theme-toggle { display: none; width: auto; font-size: 0.85rem; font-weight: 600; margin: 0; padding: 0.2rem 0.85rem; border: 1px solid currentcolor; border-radius: 5rem; background: transparent; color: inherit; }\n.js .theme-toggle { display: inline-flex; align-items: center; }\n\nsection.card { padding: var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal); border: var(--pico-border-width) solid var(--pico-card-border-color); border-radius: var(--pico-border-radius); background: var(--pico-card-background-color); box-shadow: var(--pico-card-box-shadow); }\nsection.card > h2 { margin-bottom: 0.85rem; }\nsection.card > h3 { margin-top: 1.25rem; }\n.as-of, .empty, .formula { color: var(--pico-muted-color); font-size: 0.88rem; }\n\ndetails.fold { margin: 0.6rem 0; border: var(--pico-border-width) solid var(--pico-muted-border-color); border-radius: var(--pico-border-radius); background: var(--pico-card-background-color); }\ndetails.fold > summary { display: flex; align-items: center; gap: 0.6rem; line-height: inherit; padding: 0.55rem 2.2rem 0.55rem 0.9rem; position: relative; border-radius: var(--pico-border-radius); }\ndetails.fold > summary::after { position: absolute; right: 0.9rem; top: calc(50% - 0.5rem); margin-inline-start: 0; }\ndetails.fold > summary:hover { background: var(--pico-card-sectioning-background-color); }\ndetails.fold[open] > summary { border-bottom: var(--pico-border-width) solid var(--pico-muted-border-color); background: var(--pico-card-sectioning-background-color); }\ndetails.fold > summary > :where(h3, h4, h5) { display: inline; margin: 0; min-width: 0; overflow-wrap: anywhere; color: var(--pico-report-ink); }\ndetails.fold.source > summary > h3, details.fold.baseline > summary > :where(h3, h4) { font-size: 1.02rem; text-transform: none; letter-spacing: 0; }\ndetails.fold > summary > .count { flex: none; margin-left: auto; }\n.fold-body { padding: 0.75rem 0.9rem 0.9rem; }\n.fold-body > :first-child { margin-top: 0; }\n.fold-body > :last-child { margin-bottom: 0; }\n.count { display: inline-block; min-width: 1.6rem; text-align: center; font-size: 0.75rem; font-weight: 700; border-radius: 5rem; padding: 0.05rem 0.5rem; background: var(--pico-t-unknown); color: var(--pico-t-unknown-inverse); }\n.to-top { margin: 0.9rem 0 0; font-size: 0.85rem; text-align: right; }\n.page-top { position: fixed; right: 1rem; bottom: 1rem; z-index: 9; padding: 0.4rem 0.9rem; border-radius: 5rem; background: var(--pico-primary-background); color: var(--pico-primary-inverse); text-decoration: none; font-weight: 600; }\n\n.dashboard { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1.25fr) minmax(0, 1fr); gap: 1rem; margin: 1rem 0 1.25rem; align-items: stretch; }\n.facts, .components { display: grid; grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr)); gap: 0.75rem; align-items: start; }\n.facts > details.fold, .components > details.fold { margin: 0; background: var(--pico-card-sectioning-background-color); }\n.panel { border: var(--pico-border-width) solid var(--pico-muted-border-color); border-radius: var(--pico-border-radius); padding: 1rem 1.1rem 1.15rem; background: var(--pico-card-sectioning-background-color); display: flex; flex-direction: column; }\n.panel > h3 { font-size: 1.02rem; margin-bottom: 0.7rem; }\n.stats { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); gap: 0.6rem; }\n.stat { display: flex; flex-direction: column; gap: 0.1rem; border-radius: var(--pico-border-radius); padding: 0.65rem 0.8rem 0.7rem; background: var(--t); color: var(--t-inverse); border-left: 6px solid var(--c); }\n.stat .num { font-size: 1.9rem; font-weight: 800; line-height: 1.1; font-variant-numeric: tabular-nums; }\n.stat .lbl { font-weight: 700; font-size: 0.92rem; }\n.stat .sub { font-size: 0.78rem; }\n.stat-total { background: none; color: var(--pico-color); border: var(--pico-border-width) dashed var(--pico-form-element-border-color); border-left: 6px solid var(--pico-s-unknown); }\n.bar { display: flex; gap: 2px; height: 0.8rem; border-radius: 5rem; overflow: hidden; background: var(--pico-progress-background-color); margin-top: auto; }\n.stats + .bar { margin-top: 1rem; }\n.seg { flex: var(--n) 0 0; background: var(--c); }\n\n.compliance { text-align: center; align-items: center; }\n/* The ring's auto margins can only centre it if nothing else around it claims\n vertical space, so the heading and the lines below it carry none of their own. */\n.compliance > h3 { align-self: flex-start; margin-bottom: 0; }\n.compliance > p { margin: 0; }\n.compliance > .level { margin-bottom: 0.45rem; }\n/* The ring: the percentage swept out of the track, with the same number printed\n inside it, and the engine's 60/90 bands as its colour. The auto block margins\n centre it in whatever height the row settles on. */\n.gauge { --ring: var(--pico-s-failed); width: 10.5rem; height: 10.5rem; margin: auto; border-radius: 50%; display: grid; place-items: center; background: conic-gradient(var(--ring) calc(var(--pct) * 1%), var(--pico-progress-background-color) 0); }\n.gauge .pct { width: 8.1rem; height: 8.1rem; border-radius: 50%; background: var(--pico-card-sectioning-background-color); display: grid; place-items: center; font-size: 1.75rem; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--pico-report-ink); }\n.compliance-high .gauge { --ring: var(--pico-s-passed); }\n.compliance-medium .gauge { --ring: var(--pico-s-not-reviewed); }\n.level { font-weight: 700; display: inline-block; border-radius: 5rem; padding: 0.1rem 0.85rem; background: var(--t); color: var(--t-inverse); }\n.compliance-high { --t: var(--pico-t-passed); --t-inverse: var(--pico-t-passed-inverse); }\n.compliance-medium { --t: var(--pico-t-not-reviewed); --t-inverse: var(--pico-t-not-reviewed-inverse); }\n.compliance-low { --t: var(--pico-t-failed); --t-inverse: var(--pico-t-failed-inverse); }\n\ndl { display: grid; grid-template-columns: max-content 1fr; gap: 0.3rem 1.25rem; margin: 0.5rem 0; }\ndt { font-weight: 600; color: var(--pico-muted-color); }\ndd { margin: 0; overflow-wrap: anywhere; }\n.table-wrap { overflow-x: auto; border: var(--pico-border-width) solid var(--pico-muted-border-color); border-radius: var(--pico-border-radius); margin: 0.5rem 0; }\n.table-wrap > table { margin: 0; font-size: 0.9rem; }\nth, td { padding: 0.5rem 0.8rem; vertical-align: top; }\nthead th { background: var(--pico-card-sectioning-background-color); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--pico-muted-color); }\ntfoot th, tfoot td { font-weight: 700; background: var(--pico-card-sectioning-background-color); }\ntd.text { white-space: pre-wrap; overflow-wrap: anywhere; }\ntable.summary td, table.summary thead th:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }\ntable.details th { width: 11.5rem; background: var(--pico-card-sectioning-background-color); color: var(--pico-muted-color); font-weight: 600; }\n\n.component .type { font-weight: 600; font-size: 0.75rem; border-radius: 5rem; padding: 0.05rem 0.6rem; background: var(--pico-primary-background); color: var(--pico-primary-inverse); }\n.chips { list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.25rem 0 0; padding: 0; }\n.chips li { display: inline-flex; border: var(--pico-border-width) solid var(--pico-form-element-border-color); border-radius: var(--pico-border-radius); overflow: hidden; font-size: 0.82rem; background: var(--pico-card-background-color); }\n.chips .k { background: var(--pico-t-unknown); color: var(--pico-t-unknown-inverse); padding: 0.15rem 0.5rem; font-weight: 700; }\n.chips .v { padding: 0.15rem 0.55rem; overflow-wrap: anywhere; }\n\n.toolbar { display: none; flex-wrap: wrap; align-items: center; gap: 0.6rem 0.75rem; margin: 0 0 1.1rem; padding: 0.75rem; border: var(--pico-border-width) solid var(--pico-muted-border-color); border-radius: var(--pico-border-radius); background: var(--pico-card-sectioning-background-color); }\n.js .toolbar { display: flex; }\n.toolbar input { flex: 1 1 16rem; width: auto; margin: 0; }\n.toolbar button { width: auto; margin: 0; font-size: 0.86rem; padding: 0.3rem 0.85rem; }\n.toolbar button[aria-pressed=\"false\"] { --pico-background-color: var(--pico-card-background-color); --pico-border-color: var(--pico-form-element-border-color); --pico-color: var(--pico-contrast); }\n.filters { display: flex; flex-wrap: wrap; gap: 0.35rem; }\n.shown { color: var(--pico-muted-color); font-size: 0.86rem; margin-left: auto; }\n\ndetails.fold.source { border-left: 5px solid var(--pico-primary); }\ndetails.fold.baseline { border-left: 5px solid var(--pico-muted-border-color); }\narticle.requirement { margin: 0.5rem 0; padding: 0; border: var(--pico-border-width) solid var(--pico-muted-border-color); border-left: 6px solid var(--c); border-radius: var(--pico-border-radius); background: var(--pico-card-background-color); box-shadow: none; }\narticle.requirement > details { margin: 0; }\n.req-head, article.requirement summary { display: grid; grid-template-columns: 11rem minmax(6rem, 11rem) 7.5rem minmax(10rem, 2fr) minmax(8rem, 1.4fr); gap: 0.85rem; align-items: center; }\n.req-head { padding: 0.4rem 2.6rem 0.4rem 1.1rem; font-size: 0.74rem; font-weight: 700; color: var(--pico-muted-color); text-transform: uppercase; letter-spacing: 0.06em; }\narticle.requirement summary { line-height: inherit; padding: 0.7rem 2.6rem 0.7rem 0.95rem; position: relative; }\narticle.requirement summary::after { position: absolute; right: 1.1rem; top: calc(50% - 0.5rem); margin-inline-start: 0; }\narticle.requirement summary:hover { background: var(--pico-card-sectioning-background-color); }\narticle.requirement details[open] > summary { border-bottom: var(--pico-border-width) solid var(--pico-muted-border-color); background: var(--pico-card-sectioning-background-color); }\narticle.requirement details[open] > summary { position: sticky; top: 3.4rem; z-index: 5; }\n.req-body { padding: 0.9rem 1.1rem 1.1rem; }\n.req-body > :last-child { margin-bottom: 0; }\n.req-id { font-weight: 700; overflow-wrap: anywhere; }\n.req-title { overflow-wrap: anywhere; }\n.location { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin: 0 0 0.6rem; padding: 0.5rem 0.75rem; border: var(--pico-border-width) solid var(--pico-muted-border-color); border-left: 4px solid var(--pico-primary); border-radius: var(--pico-border-radius); background: var(--pico-card-sectioning-background-color); }\n.location .k, .sub .k { font-size: 0.74rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--pico-muted-color); }\n.sub { display: block; margin-top: 0.35rem; font-size: 0.85rem; }\n.tags { display: flex; flex-wrap: wrap; gap: 0.3rem; }\n.tag { font-size: 0.75rem; font-weight: 600; border: var(--pico-border-width) solid var(--pico-form-element-border-color); border-radius: 5rem; padding: 0.05rem 0.55rem; background: var(--pico-card-background-color); white-space: nowrap; }\n.tag.enriched { background: var(--pico-primary-background); border-color: var(--pico-primary-background); color: var(--pico-primary-inverse); }\n/* Status and severity carry a mark and a word as well as a colour, so neither\n colour vision nor a greyscale printer is needed to read them. */\n.status, .sev { display: inline-flex; align-items: center; gap: 0.35rem; border-radius: 5rem; padding: 0.15rem 0.7rem; font-size: 0.8rem; font-weight: 700; white-space: nowrap; width: fit-content; }\n.status { color: var(--c-inverse); background: var(--c); }\n.status .ico { font-weight: 400; }\n.sev { background: var(--t); color: var(--t-inverse); }\n.sev::before { content: \"\"; width: 0.6rem; height: 0.6rem; border-radius: 50%; background: var(--c); flex: none; }\ndetails.doc { margin: 0.5rem 0 0; }\ndetails.doc > summary { font-weight: 600; font-size: 0.85rem; width: fit-content; }\ndetails.doc > pre { white-space: pre-wrap; }\npre { margin: 0.25rem 0 0.5rem; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 0.85rem; }\npre.prose { font-family: var(--pico-font-family-sans-serif); font-size: 0.92rem; margin: 0; padding: 0; background: none; color: inherit; }\n.req-body > pre.prose { margin-bottom: 0.25rem; }\npre.code { background: var(--pico-code-background-color); color: var(--pico-code-color); border-radius: var(--pico-border-radius); padding: 0.85rem 1rem; }\n\n@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }\n@media (forced-colors: active) { .status, .sev, .tag, .stat, .seg, .gauge, .level, .count, .component .type { border: 1px solid; forced-color-adjust: auto; } }\n@media (max-width: 68rem) { .dashboard { grid-template-columns: 1fr 1fr; } .dashboard .panel:first-child { grid-column: 1 / -1; } }\n@media (max-width: 60rem) { .topbar { position: static; } article.requirement details[open] > summary { top: 0; } .req-head { display: none; } article.requirement summary { grid-template-columns: 1fr 1fr; } .req-title, .tags { grid-column: 1 / -1; } }\n@media (max-width: 40rem) { .dashboard { grid-template-columns: 1fr; } .topbar { padding: 0.75rem 1rem; } dl { grid-template-columns: 1fr; gap: 0 0; } dd { margin-bottom: 0.4rem; } }\n\n/* Pico ships no print rules. Collapsed content is invisible on paper and to the\n reader's find-in-page, so every disclosure is opened and the chrome removed. */\n@media print {\n :root, [data-theme=\"dark\"] { color-scheme: light; }\n body { background: #ffffff; color: #000000; }\n .topbar { position: static; }\n .skip, .toolbar, .theme-toggle, .page-top, .to-top, .topbar nav { display: none !important; }\n details { display: block; }\n details > *:not(summary) { display: revert !important; }\n details > summary::after { display: none; }\n main { padding: 0; max-width: none; }\n section.card, article.requirement, details.fold { box-shadow: none; }\n thead { display: table-header-group; }\n tr, article.requirement, pre, figure, table { break-inside: avoid; }\n h2, h3, h4 { break-after: avoid; }\n p, li { orphans: 3; widows: 3; }\n * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n @page { margin: 1.5cm; }\n}\n";
|
|
8331
|
+
/** The only script the report carries. */
|
|
8332
|
+
const SCRIPT = "(function () {\n 'use strict';\n var slice = Array.prototype.slice;\n var root = document.documentElement;\n var themeKey = 'hdf-report-theme';\n\n function all(selector) {\n return slice.call(document.querySelectorAll(selector));\n }\n\n // Theme: the reader's choice wins over the system setting and is remembered.\n var toggle = document.getElementById('theme-toggle');\n\n function systemDark() {\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n\n function currentTheme() {\n var chosen = root.getAttribute('data-theme');\n if (chosen === 'dark' || chosen === 'light') {\n return chosen;\n }\n return systemDark() ? 'dark' : 'light';\n }\n\n function showTheme() {\n var dark = currentTheme() === 'dark';\n toggle.textContent = dark ? 'Light mode' : 'Dark mode';\n toggle.setAttribute('aria-label', dark ? 'Switch to light mode' : 'Switch to dark mode');\n }\n\n function remember(theme) {\n try {\n window.localStorage.setItem(themeKey, theme);\n } catch (ignore) {\n return;\n }\n }\n\n function recall() {\n try {\n return window.localStorage.getItem(themeKey);\n } catch (ignore) {\n return null;\n }\n }\n\n var saved = recall();\n if (saved === 'dark' || saved === 'light') {\n root.setAttribute('data-theme', saved);\n }\n toggle.addEventListener('click', function () {\n var next = currentTheme() === 'dark' ? 'light' : 'dark';\n root.setAttribute('data-theme', next);\n remember(next);\n showTheme();\n });\n showTheme();\n\n // Print the whole report, in the light palette, whatever is open on screen.\n var beforePrint = null;\n window.addEventListener('beforeprint', function () {\n beforePrint = root.getAttribute('data-theme');\n root.setAttribute('data-theme', 'light');\n all('details').forEach(function (d) {\n d.open = true;\n });\n });\n window.addEventListener('afterprint', function () {\n if (beforePrint === null) {\n root.removeAttribute('data-theme');\n } else {\n root.setAttribute('data-theme', beforePrint);\n }\n });\n\n root.classList.add('js');\n\n // Results filter: only the reports that list requirements carry it.\n var box = document.getElementById('filter-text');\n if (box === null) {\n return;\n }\n var reqs = all('article.requirement');\n var groups = all('#results details.group');\n var shown = document.getElementById('filter-count');\n var buttons = all('button.filter');\n var status = 'all';\n\n // Each requirement is an article holding one disclosure, so opening and\n // closing it reaches past the article to that element.\n function panel(req) {\n return req.querySelector('details');\n }\n\n function matches(d, query) {\n if (status !== 'all') {\n if (d.getAttribute('data-status') !== status) {\n return false;\n }\n }\n if (query === '') {\n return true;\n }\n return d.querySelector('summary').textContent.toLowerCase().indexOf(query) !== -1;\n }\n\n function apply() {\n var query = box.value.trim().toLowerCase();\n var filtering = query !== '' || status !== 'all';\n var visible = 0;\n reqs.forEach(function (d) {\n var ok = matches(d, query);\n d.hidden = !ok;\n if (ok) {\n visible += 1;\n }\n });\n // A filter is no use if its matches sit inside closed groups.\n groups.forEach(function (g) {\n var any = g.querySelector('article.requirement:not([hidden])') !== null;\n g.hidden = filtering ? !any : false;\n if (filtering) {\n g.open = any;\n }\n });\n shown.textContent = visible + ' of ' + reqs.length + ' requirements shown';\n }\n\n function setOpen(open) {\n groups.forEach(function (g) {\n if (!g.hidden) {\n g.open = open;\n }\n });\n reqs.forEach(function (d) {\n if (!d.hidden) {\n panel(d).open = open;\n }\n });\n }\n\n buttons.forEach(function (b) {\n b.addEventListener('click', function () {\n status = b.getAttribute('data-status');\n buttons.forEach(function (other) {\n other.setAttribute('aria-pressed', other === b ? 'true' : 'false');\n });\n apply();\n });\n });\n box.addEventListener('input', apply);\n document.getElementById('expand-all').addEventListener('click', function () {\n setOpen(true);\n });\n document.getElementById('collapse-all').addEventListener('click', function () {\n setOpen(false);\n });\n apply();\n})();\n";
|
|
8333
|
+
/** The CSP source expression for SCRIPT; a test recomputes it from the file. */
|
|
8334
|
+
const SCRIPT_HASH = "sha256-8niHkNd3MWeaLiFb0O47hR5NXEwDnmbycK+zHXeWXN0=";
|
|
8335
|
+
//#endregion
|
|
8336
|
+
//#region converters/hdf-to-html/typescript/converter.ts
|
|
8337
|
+
/**
|
|
8338
|
+
* Renders an HDF results document as one self-contained HTML report. The Go
|
|
8339
|
+
* peer emits the same bytes for the same input, so every choice here that
|
|
8340
|
+
* affects output (ordering, escaping, number and time formatting) mirrors it
|
|
8341
|
+
* and is pinned by the shared goldens.
|
|
8342
|
+
*/
|
|
8343
|
+
const CONVERTER_NAME$2 = "hdf-to-html";
|
|
8344
|
+
/** Reads a report type case-insensitively; empty selects 'administrator'. */
|
|
8345
|
+
function parseReportType(value) {
|
|
8346
|
+
switch ((value ?? "").trim().toLowerCase()) {
|
|
8347
|
+
case "":
|
|
8348
|
+
case "administrator": return "administrator";
|
|
8349
|
+
case "manager": return "manager";
|
|
8350
|
+
case "executive": return "executive";
|
|
8351
|
+
default: throw new Error(`unknown report type ${JSON.stringify(value)}: must be one of executive, manager, administrator`);
|
|
8352
|
+
}
|
|
8353
|
+
}
|
|
8354
|
+
/**
|
|
8355
|
+
* Renders an HDF results document as HTML. The output depends on the input and
|
|
8356
|
+
* options alone: nothing is read from the clock or the host, so the same
|
|
8357
|
+
* document always yields the same report.
|
|
8358
|
+
*/
|
|
8359
|
+
function convertHdfToHtml(input, options = {}) {
|
|
8360
|
+
return render([{
|
|
8361
|
+
name: "",
|
|
8362
|
+
content: input
|
|
8363
|
+
}], options, false);
|
|
8364
|
+
}
|
|
8365
|
+
/**
|
|
8366
|
+
* Renders several results documents as one report: combined status, severity
|
|
8367
|
+
* and compliance, then each document's own context, components and results
|
|
8368
|
+
* under its name. Documents appear in the order given.
|
|
8369
|
+
*/
|
|
8370
|
+
function convertHdfDocumentsToHtml(documents, options = {}) {
|
|
8371
|
+
if (documents.length === 0) throw new Error(`${CONVERTER_NAME$2}: no documents to report on`);
|
|
8372
|
+
return render(documents, options, true);
|
|
8373
|
+
}
|
|
8374
|
+
function render(documents, options, aggregated) {
|
|
8375
|
+
let reportType;
|
|
8376
|
+
try {
|
|
8377
|
+
reportType = parseReportType(options.reportType);
|
|
8378
|
+
} catch (err) {
|
|
8379
|
+
throw new Error(`${CONVERTER_NAME$2}: ${err.message}`);
|
|
8380
|
+
}
|
|
8381
|
+
const sources = documents.map((document) => {
|
|
8382
|
+
let doc;
|
|
8383
|
+
try {
|
|
8384
|
+
({doc} = requireHdfResults(document.content, CONVERTER_NAME$2));
|
|
8385
|
+
} catch (err) {
|
|
8386
|
+
if (!aggregated) throw err;
|
|
8387
|
+
throw new Error(`${document.name}: ${err.message}`);
|
|
8388
|
+
}
|
|
8389
|
+
const baselines = list(doc.baselines).map(obj);
|
|
8390
|
+
return {
|
|
8391
|
+
name: document.name,
|
|
8392
|
+
doc,
|
|
8393
|
+
baselines,
|
|
8394
|
+
baselineLabels: baselineLabels(baselines),
|
|
8395
|
+
attributed: attributesResults(doc, baselines),
|
|
8396
|
+
ref: assessmentTime(doc, baselines)
|
|
8397
|
+
};
|
|
8398
|
+
});
|
|
8399
|
+
const r = new Renderer(reportType, sources, aggregated);
|
|
8400
|
+
r.document();
|
|
8401
|
+
return r.lines.join("");
|
|
8402
|
+
}
|
|
8403
|
+
/**
|
|
8404
|
+
* Names each baseline as the report shows it: its title, which is what carries
|
|
8405
|
+
* the scan target in real scanner output, falling back to its name. A label two
|
|
8406
|
+
* baselines would share takes the 1-based document position of the baseline it
|
|
8407
|
+
* names (hdf-engine's 0-based baselineIndex plus one).
|
|
8408
|
+
*/
|
|
8409
|
+
function baselineLabels(baselines) {
|
|
8410
|
+
const shared = /* @__PURE__ */ new Map();
|
|
8411
|
+
const suffixed = baselines.map((baseline) => {
|
|
8412
|
+
const title = text(baseline.title);
|
|
8413
|
+
const label = title !== "" ? title : text(baseline.name);
|
|
8414
|
+
shared.set(label, (shared.get(label) ?? 0) + 1);
|
|
8415
|
+
return label;
|
|
8416
|
+
}).map((label, i) => (shared.get(label) ?? 0) > 1 ? label + ordinalSuffix(i) : label);
|
|
8417
|
+
const taken = /* @__PURE__ */ new Set();
|
|
8418
|
+
return suffixed.map((label, i) => {
|
|
8419
|
+
let unique = label;
|
|
8420
|
+
while (taken.has(unique)) unique += ordinalSuffix(i);
|
|
8421
|
+
taken.add(unique);
|
|
8422
|
+
return unique;
|
|
8423
|
+
});
|
|
8424
|
+
}
|
|
8425
|
+
function ordinalSuffix(i) {
|
|
8426
|
+
return ` #${i + 1}`;
|
|
8427
|
+
}
|
|
8428
|
+
/** The well-known baseline label naming the component a baseline's requirements were assessed against. */
|
|
8429
|
+
const LABEL_COMPONENT = "component";
|
|
8430
|
+
/**
|
|
8431
|
+
* Whether the document ties any requirement to a component. HDF results carry
|
|
8432
|
+
* that link at baseline granularity only — a baseline's labels.component names a
|
|
8433
|
+
* component, or a component's baselineRefs name a baseline — so a per-component
|
|
8434
|
+
* number means nothing without one.
|
|
8435
|
+
*/
|
|
8436
|
+
function attributesResults(doc, baselines) {
|
|
8437
|
+
for (const baseline of baselines) {
|
|
8438
|
+
const labels = obj(baseline.labels);
|
|
8439
|
+
if (Object.prototype.hasOwnProperty.call(labels, LABEL_COMPONENT) && text(labels[LABEL_COMPONENT]) !== "") return true;
|
|
8440
|
+
}
|
|
8441
|
+
return list(doc.components).map(obj).some((c) => list(c.baselineRefs).length > 0);
|
|
8442
|
+
}
|
|
8443
|
+
/** A JSON value as an object; anything else reads as an empty one, as Go's zero struct does. */
|
|
8444
|
+
function obj(value) {
|
|
8445
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
8446
|
+
}
|
|
8447
|
+
function list(value) {
|
|
8448
|
+
return Array.isArray(value) ? value : [];
|
|
8449
|
+
}
|
|
8450
|
+
/** A string field as text; absent or wrong-typed reads as empty, as Go's zero value does. */
|
|
8451
|
+
function text(value) {
|
|
8452
|
+
return typeof value === "string" ? value : "";
|
|
8453
|
+
}
|
|
8454
|
+
function numeric(value) {
|
|
8455
|
+
return typeof value === "number" ? value : 0;
|
|
8456
|
+
}
|
|
8457
|
+
/** HDF's canonical trimmed-UTC RFC3339; absent or unparseable is empty. */
|
|
8458
|
+
function formatTime(value) {
|
|
8459
|
+
const parsed = toDate$1(value);
|
|
8460
|
+
return parsed ? formatTimestamp(parsed) : "";
|
|
8461
|
+
}
|
|
8462
|
+
function toDate$1(value) {
|
|
8463
|
+
const parsed = parsedDate(value);
|
|
8464
|
+
return parsed && !isGoZeroTime(parsed) ? parsed : null;
|
|
8465
|
+
}
|
|
8466
|
+
function parsedDate(value) {
|
|
8467
|
+
if (value instanceof Date) return value;
|
|
8468
|
+
return typeof value === "string" ? parseTimestamp(value) : null;
|
|
8469
|
+
}
|
|
8470
|
+
/**
|
|
8471
|
+
* When the assessment ran: the document timestamp, else its latest result start
|
|
8472
|
+
* time. Overrides are judged against it instead of the wall clock, which is
|
|
8473
|
+
* what keeps the report reproducible.
|
|
8474
|
+
*/
|
|
8475
|
+
function assessmentTime(doc, baselines) {
|
|
8476
|
+
const latest = toDate$1(doc.timestamp) ?? latestStart(baselines);
|
|
8477
|
+
if (!latest) {
|
|
8478
|
+
const epoch = /* @__PURE__ */ new Date(0);
|
|
8479
|
+
return {
|
|
8480
|
+
stamp: formatTimestamp(epoch),
|
|
8481
|
+
date: epoch,
|
|
8482
|
+
known: false
|
|
8483
|
+
};
|
|
8484
|
+
}
|
|
8485
|
+
return {
|
|
8486
|
+
stamp: formatTimestamp(latest),
|
|
8487
|
+
date: latest,
|
|
8488
|
+
known: true
|
|
8489
|
+
};
|
|
8490
|
+
}
|
|
8491
|
+
/** The latest result start time in the document, if any result carries one. */
|
|
8492
|
+
function latestStart(baselines) {
|
|
8493
|
+
let latest = null;
|
|
8494
|
+
const results = baselines.flatMap((baseline) => list(baseline.requirements).map(obj)).flatMap((req) => list(req.results).map(obj));
|
|
8495
|
+
for (const res of results) {
|
|
8496
|
+
const start = toDate$1(res.startTime);
|
|
8497
|
+
if (start && (!latest || start > latest)) latest = start;
|
|
8498
|
+
}
|
|
8499
|
+
return latest;
|
|
8500
|
+
}
|
|
8501
|
+
/**
|
|
8502
|
+
* The compliance percentage hdf-engine computes, to two places, so the report
|
|
8503
|
+
* never disagrees with `hdf validate threshold` in the last digit. The Go peer
|
|
8504
|
+
* formats the same number with FormatFixed, which is toFixed's rounding.
|
|
8505
|
+
*/
|
|
8506
|
+
function compliancePercent(c) {
|
|
8507
|
+
return calculateCompliance(c).toFixed(2);
|
|
8508
|
+
}
|
|
8509
|
+
function compliance(c) {
|
|
8510
|
+
return `${compliancePercent(c)}%`;
|
|
8511
|
+
}
|
|
8512
|
+
/** The requirement's severity through the engine, exactly as its tally derives it. */
|
|
8513
|
+
function requirementSeverity(req, now) {
|
|
8514
|
+
return deriveSeverity(requirementEffectiveImpact(req, now), req.severity ?? null);
|
|
8515
|
+
}
|
|
8516
|
+
/**
|
|
8517
|
+
* Maps overrides onto the canonical effective-status input shape. formatTime
|
|
8518
|
+
* drops Go's zero time, which the legacy converters leave in `expiresAt` to
|
|
8519
|
+
* mean "no expiry": handing the raw value to the status ladder would read it as
|
|
8520
|
+
* an expiry in the year 1 and disagree with both the overrides table and Go,
|
|
8521
|
+
* whose zero time.Time already means absent. Effective status and the table's
|
|
8522
|
+
* governing/expired column read the same normalized overrides.
|
|
8523
|
+
*/
|
|
8524
|
+
function overrideInputs(overrides) {
|
|
8525
|
+
return overrides.map((o) => ({
|
|
8526
|
+
status: text(o.status) === "" ? void 0 : text(o.status),
|
|
8527
|
+
appliedAt: formatTime(o.appliedAt) || void 0,
|
|
8528
|
+
expiresAt: formatTime(o.expiresAt) || void 0
|
|
8529
|
+
}));
|
|
8530
|
+
}
|
|
8531
|
+
/** Bands the engine's compliance percentage at 90 and 60. */
|
|
8532
|
+
function complianceLevel(pct) {
|
|
8533
|
+
if (pct >= 90) return ["high", "High compliance"];
|
|
8534
|
+
if (pct >= 60) return ["medium", "Medium compliance"];
|
|
8535
|
+
return ["low", "Low compliance"];
|
|
8536
|
+
}
|
|
8537
|
+
/** Maps a severity onto the fixed set the stylesheet knows; an informational, absent or unrecognized one is 'none'. */
|
|
8538
|
+
function severityClass(severity) {
|
|
8539
|
+
const bucket = severityBucket(severity);
|
|
8540
|
+
return bucket === "informational" ? "none" : bucket;
|
|
8541
|
+
}
|
|
8542
|
+
function severityLabel(severity) {
|
|
8543
|
+
switch (severity) {
|
|
8544
|
+
case "critical": return "Critical";
|
|
8545
|
+
case "high": return "High";
|
|
8546
|
+
case "medium": return "Medium";
|
|
8547
|
+
case "low": return "Low";
|
|
8548
|
+
case "":
|
|
8549
|
+
case "none":
|
|
8550
|
+
case "informational": return "None";
|
|
8551
|
+
default: return severity;
|
|
8552
|
+
}
|
|
8553
|
+
}
|
|
8554
|
+
/** Names a description the way the Heimdall report does. */
|
|
8555
|
+
function descriptionHeading(label) {
|
|
8556
|
+
switch (label) {
|
|
8557
|
+
case "default": return "Description";
|
|
8558
|
+
case "check": return "Check Text";
|
|
8559
|
+
case "fix": return "Fix Text";
|
|
8560
|
+
case "rationale": return "Rationale";
|
|
8561
|
+
case "caveat": return "Caveat";
|
|
8562
|
+
default: return label;
|
|
8563
|
+
}
|
|
8564
|
+
}
|
|
8565
|
+
function plural(n, noun) {
|
|
8566
|
+
return n === 1 ? `1 ${noun}` : `${n} ${noun}s`;
|
|
8567
|
+
}
|
|
8568
|
+
function stat(cls, n, label, sub) {
|
|
8569
|
+
return `<li class="stat c-${cls}"><span class="num">${n}</span><span class="lbl">${label}</span>${sub === "" ? "" : `<span class="sub">${sub}</span>`}</li>`;
|
|
8570
|
+
}
|
|
8571
|
+
/**
|
|
8572
|
+
* A proportional strip; a zero count contributes no segment. It is an image to
|
|
8573
|
+
* assistive technology, described by the same counts the list above it states,
|
|
8574
|
+
* so nothing is conveyed by colour alone.
|
|
8575
|
+
*/
|
|
8576
|
+
function bar(label, segments) {
|
|
8577
|
+
return `<div class="bar" role="img" aria-label="${label}: ${segments.map(([, name, n]) => `${n} ${name}`).join(", ")}">${segments.filter(([, , n]) => n > 0).map(([cls, , n]) => `<span class="seg c-${cls}" style="--n:${n}"></span>`).join("")}</div>`;
|
|
8578
|
+
}
|
|
8579
|
+
/** A heading and a value that are already HTML. */
|
|
8580
|
+
function detailRow(heading, value) {
|
|
8581
|
+
return `<tr><th scope="row">${heading}</th><td>${value}</td></tr>`;
|
|
8582
|
+
}
|
|
8583
|
+
/** Carries text verbatim. An HTML parser drops one newline directly after <pre>, so one is always written for it to drop. */
|
|
8584
|
+
function prose(value) {
|
|
8585
|
+
return `<pre class="prose">\n${escapeHtml(value)}</pre>`;
|
|
8586
|
+
}
|
|
8587
|
+
const REPORT_TYPE_LABEL = {
|
|
8588
|
+
executive: "Executive",
|
|
8589
|
+
manager: "Manager",
|
|
8590
|
+
administrator: "Administrator"
|
|
8591
|
+
};
|
|
8592
|
+
const STATUS_FILTERS = [
|
|
8593
|
+
["passed", "Passed"],
|
|
8594
|
+
["failed", "Failed"],
|
|
8595
|
+
["not-applicable", "Not Applicable"],
|
|
8596
|
+
["not-reviewed", "Not Reviewed"],
|
|
8597
|
+
["error", "Error"]
|
|
8598
|
+
];
|
|
8599
|
+
/**
|
|
8600
|
+
* The size past which a block starts closed: anything longer than two lines or
|
|
8601
|
+
* rows is behind its heading, so a long report is a list of headings with
|
|
8602
|
+
* counts instead of a scroll.
|
|
8603
|
+
*/
|
|
8604
|
+
const COLLAPSED_ABOVE = 2;
|
|
8605
|
+
/** How many lines text occupies. */
|
|
8606
|
+
function lineCount(value) {
|
|
8607
|
+
return value.split("\n").length;
|
|
8608
|
+
}
|
|
8609
|
+
/** Text that would take more than two lines on the page: more than two lines of its own, or one long enough to wrap that far. */
|
|
8610
|
+
function isLongText(value) {
|
|
8611
|
+
return lineCount(value) > COLLAPSED_ABOVE || Array.from(value).length > 240;
|
|
8612
|
+
}
|
|
8613
|
+
function countFacts(pairs) {
|
|
8614
|
+
return pairs.filter(([, value]) => value !== "").length;
|
|
8615
|
+
}
|
|
8616
|
+
var Renderer = class {
|
|
8617
|
+
/**
|
|
8618
|
+
* `aggregated` lays the report out by source, even for a single document;
|
|
8619
|
+
* the single-document layout has no source level.
|
|
8620
|
+
*/
|
|
8621
|
+
constructor(reportType, sources, aggregated) {
|
|
8622
|
+
this.reportType = reportType;
|
|
8623
|
+
this.sources = sources;
|
|
8624
|
+
this.aggregated = aggregated;
|
|
8625
|
+
this.lines = [];
|
|
8626
|
+
this.folds = 0;
|
|
8627
|
+
this.requirements = 0;
|
|
8628
|
+
this.src = sources[0];
|
|
8629
|
+
}
|
|
8630
|
+
/** The heading outline stays in order: the aggregated layout has a source heading above the baselines. */
|
|
8631
|
+
get baselineHeading() {
|
|
8632
|
+
return this.aggregated ? "h4" : "h3";
|
|
8633
|
+
}
|
|
8634
|
+
get detailHeading() {
|
|
8635
|
+
return this.aggregated ? "h5" : "h4";
|
|
8636
|
+
}
|
|
8637
|
+
/**
|
|
8638
|
+
* Starts a collapsible block: a heading with a count that opens onto the
|
|
8639
|
+
* content. `title` is HTML; `label` is the plain text the return link names.
|
|
8640
|
+
* An empty id takes the next number.
|
|
8641
|
+
*/
|
|
8642
|
+
openFold(cls, id, tag, title, label, count) {
|
|
8643
|
+
return this.openFoldCounted(cls, id, tag, title, label, count, true);
|
|
8644
|
+
}
|
|
8645
|
+
/** openFold with the choice of whether the heading states its count. A block that drops it still collapses at the same size. */
|
|
8646
|
+
openFoldCounted(cls, id, tag, title, label, count, counted) {
|
|
8647
|
+
if (id === "") {
|
|
8648
|
+
this.folds++;
|
|
8649
|
+
id = `block-${this.folds}`;
|
|
8650
|
+
}
|
|
8651
|
+
const long = count > COLLAPSED_ABOVE;
|
|
8652
|
+
const classes = cls === "" ? "fold" : `fold ${cls}`;
|
|
8653
|
+
const badge = counted ? `<span class="count">${count}</span>` : "";
|
|
8654
|
+
this.line(`<details class="${classes}" id="${id}"${long ? "" : " open=\"open\""}>`);
|
|
8655
|
+
this.line(`<summary><${tag}>${title}</${tag}>${badge}</summary>`);
|
|
8656
|
+
this.line("<div class=\"fold-body\">");
|
|
8657
|
+
return {
|
|
8658
|
+
id,
|
|
8659
|
+
label,
|
|
8660
|
+
long
|
|
8661
|
+
};
|
|
8662
|
+
}
|
|
8663
|
+
/** Ends the block, with a way back to its top when it is long enough to need one. */
|
|
8664
|
+
closeFold(f) {
|
|
8665
|
+
if (f.long) this.line(`<p class="to-top"><a href="#${f.id}">Back to the top of ${escapeHtml(f.label)}</a></p>`);
|
|
8666
|
+
this.line("</div>");
|
|
8667
|
+
this.line("</details>");
|
|
8668
|
+
}
|
|
8669
|
+
line(s) {
|
|
8670
|
+
this.lines.push(s, "\n");
|
|
8671
|
+
}
|
|
8672
|
+
document() {
|
|
8673
|
+
const detailed = this.reportType !== "executive";
|
|
8674
|
+
const policy = `default-src 'none'; style-src 'unsafe-inline'; script-src '${SCRIPT_HASH}'`;
|
|
8675
|
+
this.line("<!DOCTYPE html>");
|
|
8676
|
+
this.line("<html lang=\"en\">");
|
|
8677
|
+
this.line("<head>");
|
|
8678
|
+
this.line("<meta charset=\"utf-8\" />");
|
|
8679
|
+
this.line(`<meta http-equiv="Content-Security-Policy" content="${policy}" />`);
|
|
8680
|
+
this.line("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />");
|
|
8681
|
+
this.line("<meta name=\"color-scheme\" content=\"light dark\" />");
|
|
8682
|
+
this.line("<title>HDF Assessment Report</title>");
|
|
8683
|
+
this.line("<style>");
|
|
8684
|
+
this.lines.push(STYLESHEET);
|
|
8685
|
+
this.line("</style>");
|
|
8686
|
+
this.line("</head>");
|
|
8687
|
+
this.line("<body>");
|
|
8688
|
+
this.line("<a class=\"skip\" href=\"#main\">Skip to content</a>");
|
|
8689
|
+
this.line("<header class=\"topbar\" id=\"top\">");
|
|
8690
|
+
this.line("<h1 class=\"brand\">HDF Assessment Report</h1>");
|
|
8691
|
+
this.line(`<span class="report-type">Report type: ${REPORT_TYPE_LABEL[this.reportType]}</span>`);
|
|
8692
|
+
this.line("<nav aria-label=\"Sections\">");
|
|
8693
|
+
this.line("<ul>");
|
|
8694
|
+
this.line("<li><a href=\"#status\">Status</a></li>");
|
|
8695
|
+
this.line(this.aggregated ? "<li><a href=\"#sources\">Sources</a></li>" : "<li><a href=\"#assessment\">Assessment</a></li>");
|
|
8696
|
+
this.line("<li><a href=\"#components\">Components</a></li>");
|
|
8697
|
+
if (detailed) this.line("<li><a href=\"#results\">Results</a></li>");
|
|
8698
|
+
this.line("</ul>");
|
|
8699
|
+
this.line("</nav>");
|
|
8700
|
+
this.line("<button type=\"button\" id=\"theme-toggle\" class=\"theme-toggle\" aria-label=\"Switch to dark mode\">Dark mode</button>");
|
|
8701
|
+
this.line("</header>");
|
|
8702
|
+
this.line("<main id=\"main\" class=\"container\">");
|
|
8703
|
+
this.status();
|
|
8704
|
+
if (this.aggregated) this.sourceList();
|
|
8705
|
+
else this.assessment(this.sources[0].doc);
|
|
8706
|
+
this.components();
|
|
8707
|
+
if (detailed) this.results();
|
|
8708
|
+
this.line("</main>");
|
|
8709
|
+
this.line("<a class=\"page-top\" href=\"#top\">Top</a>");
|
|
8710
|
+
this.line("<script>");
|
|
8711
|
+
this.lines.push(SCRIPT);
|
|
8712
|
+
this.line("<\/script>");
|
|
8713
|
+
this.line("</body>");
|
|
8714
|
+
this.line("</html>");
|
|
8715
|
+
}
|
|
8716
|
+
/** Writes the non-empty pairs and reports whether it wrote any. */
|
|
8717
|
+
definitionList(pairs) {
|
|
8718
|
+
let wrote = false;
|
|
8719
|
+
for (const [term, value] of pairs) {
|
|
8720
|
+
if (value === "") continue;
|
|
8721
|
+
if (!wrote) {
|
|
8722
|
+
this.line("<dl>");
|
|
8723
|
+
wrote = true;
|
|
8724
|
+
}
|
|
8725
|
+
this.line(`<dt>${escapeHtml(term)}</dt><dd>${escapeHtml(value)}</dd>`);
|
|
8726
|
+
}
|
|
8727
|
+
if (wrote) this.line("</dl>");
|
|
8728
|
+
return wrote;
|
|
8729
|
+
}
|
|
8730
|
+
assessment(doc) {
|
|
8731
|
+
this.line("<section id=\"assessment\" class=\"card\" aria-labelledby=\"assessment-heading\">");
|
|
8732
|
+
this.line("<h2 id=\"assessment-heading\">Assessment</h2>");
|
|
8733
|
+
this.line("<div class=\"facts\">");
|
|
8734
|
+
const facts = documentFacts(doc);
|
|
8735
|
+
const f = this.openFold("", "", "h3", "Document", "the document facts", countFacts(facts));
|
|
8736
|
+
if (!this.definitionList(facts)) this.line("<p class=\"empty\">The document carries no assessment metadata.</p>");
|
|
8737
|
+
this.closeFold(f);
|
|
8738
|
+
if (doc.runner != null) {
|
|
8739
|
+
const runner = runnerFacts(obj(doc.runner));
|
|
8740
|
+
const rf = this.openFold("", "", "h3", "Runner", "the runner facts", countFacts(runner));
|
|
8741
|
+
this.definitionList(runner);
|
|
8742
|
+
this.closeFold(rf);
|
|
8743
|
+
}
|
|
8744
|
+
this.line("</div>");
|
|
8745
|
+
this.externalReferences("h3", "Enrichment and External References", doc.externalReferences);
|
|
8746
|
+
this.line("</section>");
|
|
8747
|
+
}
|
|
8748
|
+
/** The aggregated layout's assessment section: one panel per input document, each the target of the links in the status tables. */
|
|
8749
|
+
sourceList() {
|
|
8750
|
+
this.line("<section id=\"sources\" class=\"card\" aria-labelledby=\"sources-heading\">");
|
|
8751
|
+
this.line(`<h2 id="sources-heading">Sources (${this.sources.length})</h2>`);
|
|
8752
|
+
this.line("<div class=\"facts\">");
|
|
8753
|
+
this.sources.forEach((src, i) => {
|
|
8754
|
+
const facts = documentFacts(src.doc);
|
|
8755
|
+
const f = this.openFold("", sourceId(i), "h3", escapeHtml(src.name), src.name, countFacts(facts));
|
|
8756
|
+
if (!this.definitionList(facts)) this.line("<p class=\"empty\">The document carries no assessment metadata.</p>");
|
|
8757
|
+
if (src.doc.runner != null) {
|
|
8758
|
+
this.line("<h4>Runner</h4>");
|
|
8759
|
+
this.definitionList(runnerFacts(obj(src.doc.runner)));
|
|
8760
|
+
}
|
|
8761
|
+
this.externalReferences("h4", "Enrichment and External References", src.doc.externalReferences);
|
|
8762
|
+
this.closeFold(f);
|
|
8763
|
+
});
|
|
8764
|
+
this.line("</div>");
|
|
8765
|
+
this.line("</section>");
|
|
8766
|
+
}
|
|
8767
|
+
components() {
|
|
8768
|
+
const perSource = this.sources.map((src) => list(src.doc.components).map(obj));
|
|
8769
|
+
const total = perSource.reduce((n, components) => n + components.length, 0);
|
|
8770
|
+
this.line("<section id=\"components\" class=\"card\" aria-labelledby=\"components-heading\">");
|
|
8771
|
+
this.line(`<h2 id="components-heading">Components (${total})</h2>`);
|
|
8772
|
+
if (total > 0) {
|
|
8773
|
+
this.line("<div class=\"components\">");
|
|
8774
|
+
this.sources.forEach((src, i) => {
|
|
8775
|
+
this.src = src;
|
|
8776
|
+
for (const c of perSource[i]) this.component(c);
|
|
8777
|
+
});
|
|
8778
|
+
this.line("</div>");
|
|
8779
|
+
} else if (this.aggregated) this.line("<p class=\"empty\">The documents name no components.</p>");
|
|
8780
|
+
else this.line("<p class=\"empty\">The document names no components.</p>");
|
|
8781
|
+
this.line("</section>");
|
|
8782
|
+
}
|
|
8783
|
+
component(c) {
|
|
8784
|
+
const pairs = [["Source", this.aggregated ? this.src.name : ""]];
|
|
8785
|
+
for (const [term, key] of COMPONENT_FIELDS) pairs.push(componentField(c, term, key));
|
|
8786
|
+
const f = this.openFoldCounted("component", "", "h3", `${escapeHtml(text(c.name))} <span class="type">${escapeHtml(text(c.type))}</span>`, text(c.name), countFacts(pairs) + Object.keys(obj(c.labels)).length + Object.keys(obj(c.externalIds)).length, this.src.attributed);
|
|
8787
|
+
this.definitionList(pairs);
|
|
8788
|
+
this.chips("Labels", c.labels);
|
|
8789
|
+
this.chips("External IDs", c.externalIds);
|
|
8790
|
+
this.closeFold(f);
|
|
8791
|
+
}
|
|
8792
|
+
/** A map as key/value chips in key order. An empty value is kept: the key itself is the information. */
|
|
8793
|
+
chips(heading, value) {
|
|
8794
|
+
const m = obj(value);
|
|
8795
|
+
const keys = Object.keys(m).sort(byCodePoint);
|
|
8796
|
+
if (keys.length === 0) return;
|
|
8797
|
+
this.line(`<h4>${heading}</h4>`);
|
|
8798
|
+
this.line("<ul class=\"chips\">");
|
|
8799
|
+
for (const k of keys) this.line(`<li><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(text(m[k]))}</span></li>`);
|
|
8800
|
+
this.line("</ul>");
|
|
8801
|
+
}
|
|
8802
|
+
effectiveStatus(req) {
|
|
8803
|
+
return computeEffectiveStatus({
|
|
8804
|
+
impact: numeric(req.impact),
|
|
8805
|
+
resultStatuses: list(req.results).map(obj).map((res) => text(res.status)),
|
|
8806
|
+
overrides: overrideInputs(list(req.statusOverrides).map(obj))
|
|
8807
|
+
}, this.src.ref.stamp);
|
|
8808
|
+
}
|
|
8809
|
+
status() {
|
|
8810
|
+
const checks = {
|
|
8811
|
+
underPassed: 0,
|
|
8812
|
+
passedUnderFailed: 0,
|
|
8813
|
+
failedUnderFailed: 0,
|
|
8814
|
+
total: 0
|
|
8815
|
+
};
|
|
8816
|
+
const perBaseline = this.sources.map((src) => {
|
|
8817
|
+
this.src = src;
|
|
8818
|
+
return src.baselines.map((baseline) => {
|
|
8819
|
+
const requirements = list(baseline.requirements).map(obj);
|
|
8820
|
+
for (const req of requirements) {
|
|
8821
|
+
const status = this.effectiveStatus(req);
|
|
8822
|
+
for (const res of list(req.results).map(obj)) {
|
|
8823
|
+
checks.total++;
|
|
8824
|
+
const resultStatus = text(res.status);
|
|
8825
|
+
if (status === "passed" && resultStatus === "passed") checks.underPassed++;
|
|
8826
|
+
else if (status === "failed" && resultStatus === "passed") checks.passedUnderFailed++;
|
|
8827
|
+
else if (status === "failed" && resultStatus === "failed") checks.failedUnderFailed++;
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8830
|
+
return countControlsByStatus({ baselines: [{ requirements }] }, (req) => this.effectiveStatus(req), this.src.ref.stamp);
|
|
8831
|
+
});
|
|
8832
|
+
});
|
|
8833
|
+
const perSource = perBaseline.map((counts) => addCounts(...counts));
|
|
8834
|
+
const all = addCounts(...perSource);
|
|
8835
|
+
const severities = severityTotals(all);
|
|
8836
|
+
const anyRef = this.sources.some((src) => src.ref.known);
|
|
8837
|
+
this.line("<section id=\"status\" class=\"card\" aria-labelledby=\"status-heading\">");
|
|
8838
|
+
this.line("<h2 id=\"status-heading\">Status</h2>");
|
|
8839
|
+
if (severities.total > 0 && anyRef) {
|
|
8840
|
+
if (this.aggregated) this.line("<p class=\"as-of\">Effective status evaluated for each source as of its own assessment time. Overrides that had expired by then are not applied.</p>");
|
|
8841
|
+
else this.line(`<p class="as-of">Effective status evaluated as of ${escapeHtml(this.sources[0].ref.stamp)}, the time of the assessment. Overrides that had expired by then are not applied.</p>`);
|
|
8842
|
+
}
|
|
8843
|
+
this.line("<div class=\"dashboard\">");
|
|
8844
|
+
this.line("<div class=\"panel\">");
|
|
8845
|
+
this.line("<h3>Requirements</h3>");
|
|
8846
|
+
this.line("<ul class=\"stats\">");
|
|
8847
|
+
this.line(stat("passed", all.passed.total, "Passed", `${plural(checks.underPassed, "individual check")} passed`));
|
|
8848
|
+
this.line(stat("failed", all.failed.total, "Failed", `${plural(checks.passedUnderFailed, "individual check")} passed, ${checks.failedUnderFailed} failed out of ${plural(checks.total, "total check")}`));
|
|
8849
|
+
this.line(stat("not-applicable", all.noImpact.total, "Not Applicable", ""));
|
|
8850
|
+
this.line(stat("not-reviewed", all.skipped.total, "Not Reviewed", ""));
|
|
8851
|
+
this.line(stat("error", all.error.total, "Error", ""));
|
|
8852
|
+
this.line(`<li class="stat stat-total"><span class="num">${severities.total}</span><span class="lbl">Total</span></li>`);
|
|
8853
|
+
this.line("</ul>");
|
|
8854
|
+
this.line(bar("Requirements by status", [
|
|
8855
|
+
[
|
|
8856
|
+
"passed",
|
|
8857
|
+
"passed",
|
|
8858
|
+
all.passed.total
|
|
8859
|
+
],
|
|
8860
|
+
[
|
|
8861
|
+
"failed",
|
|
8862
|
+
"failed",
|
|
8863
|
+
all.failed.total
|
|
8864
|
+
],
|
|
8865
|
+
[
|
|
8866
|
+
"not-applicable",
|
|
8867
|
+
"not applicable",
|
|
8868
|
+
all.noImpact.total
|
|
8869
|
+
],
|
|
8870
|
+
[
|
|
8871
|
+
"not-reviewed",
|
|
8872
|
+
"not reviewed",
|
|
8873
|
+
all.skipped.total
|
|
8874
|
+
],
|
|
8875
|
+
[
|
|
8876
|
+
"error",
|
|
8877
|
+
"error",
|
|
8878
|
+
all.error.total
|
|
8879
|
+
]
|
|
8880
|
+
]));
|
|
8881
|
+
this.line("</div>");
|
|
8882
|
+
this.line("<div class=\"panel\">");
|
|
8883
|
+
this.line("<h3>Severity</h3>");
|
|
8884
|
+
this.line("<ul class=\"stats\">");
|
|
8885
|
+
this.line(stat("critical", severities.critical, "Critical", ""));
|
|
8886
|
+
this.line(stat("high", severities.high, "High", ""));
|
|
8887
|
+
this.line(stat("medium", severities.medium, "Medium", ""));
|
|
8888
|
+
this.line(stat("low", severities.low, "Low", ""));
|
|
8889
|
+
this.line(stat("none", severities.informational, "None", ""));
|
|
8890
|
+
this.line("</ul>");
|
|
8891
|
+
this.line(bar("Requirements by severity", [
|
|
8892
|
+
[
|
|
8893
|
+
"critical",
|
|
8894
|
+
"critical",
|
|
8895
|
+
severities.critical
|
|
8896
|
+
],
|
|
8897
|
+
[
|
|
8898
|
+
"high",
|
|
8899
|
+
"high",
|
|
8900
|
+
severities.high
|
|
8901
|
+
],
|
|
8902
|
+
[
|
|
8903
|
+
"medium",
|
|
8904
|
+
"medium",
|
|
8905
|
+
severities.medium
|
|
8906
|
+
],
|
|
8907
|
+
[
|
|
8908
|
+
"low",
|
|
8909
|
+
"low",
|
|
8910
|
+
severities.low
|
|
8911
|
+
],
|
|
8912
|
+
[
|
|
8913
|
+
"none",
|
|
8914
|
+
"none",
|
|
8915
|
+
severities.informational
|
|
8916
|
+
]
|
|
8917
|
+
]));
|
|
8918
|
+
this.line("</div>");
|
|
8919
|
+
const [level, levelLabel] = complianceLevel(calculateCompliance(all));
|
|
8920
|
+
this.line(`<div class="panel compliance compliance-${level}">`);
|
|
8921
|
+
this.line("<h3 id=\"compliance-heading\">Compliance</h3>");
|
|
8922
|
+
this.line(`<div class="gauge" role="meter" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${compliancePercent(all)}" aria-labelledby="compliance-heading" style="--pct:${compliancePercent(all)}"><span class="pct">${compliance(all)}</span></div>`);
|
|
8923
|
+
this.line(`<p class="level">${levelLabel}</p>`);
|
|
8924
|
+
this.line("<p class=\"formula\">Passed / (Passed + Failed + Not Reviewed + Error) × 100</p>");
|
|
8925
|
+
this.line("</div>");
|
|
8926
|
+
this.line("</div>");
|
|
8927
|
+
if (this.aggregated) {
|
|
8928
|
+
const bySource = this.openFold("", "", "h3", "Status by source", "the status by source", this.sources.length);
|
|
8929
|
+
this.line("<div class=\"table-wrap\">");
|
|
8930
|
+
this.line("<table class=\"summary\" aria-label=\"Status by source\">");
|
|
8931
|
+
this.line(summaryHead("Source"));
|
|
8932
|
+
this.line("<tbody>");
|
|
8933
|
+
this.sources.forEach((src, si) => this.line(summaryRow(`<a href="#${sourceId(si)}">${escapeHtml(src.name)}</a>`, perSource[si])));
|
|
8934
|
+
this.line("</tbody>");
|
|
8935
|
+
this.line("<tfoot>");
|
|
8936
|
+
this.line(summaryRow("All sources", all));
|
|
8937
|
+
this.line("</tfoot>");
|
|
8938
|
+
this.line("</table>");
|
|
8939
|
+
this.line("</div>");
|
|
8940
|
+
this.closeFold(bySource);
|
|
8941
|
+
}
|
|
8942
|
+
const baselineCount = this.sources.reduce((n, src) => n + src.baselines.length, 0);
|
|
8943
|
+
const byBaseline = this.openFold("", "", "h3", "Status by baseline", "the status by baseline", baselineCount);
|
|
8944
|
+
this.line("<div class=\"table-wrap\">");
|
|
8945
|
+
this.line("<table class=\"summary\" aria-label=\"Status by baseline\">");
|
|
8946
|
+
this.line(summaryHead("Baseline"));
|
|
8947
|
+
this.line("<tbody>");
|
|
8948
|
+
this.sources.forEach((src, si) => {
|
|
8949
|
+
src.baselineLabels.forEach((own, i) => {
|
|
8950
|
+
const label = this.aggregated ? `${src.name} \u203a ${own}` : own;
|
|
8951
|
+
this.line(summaryRow(escapeHtml(label), perBaseline[si][i]));
|
|
8952
|
+
});
|
|
8953
|
+
});
|
|
8954
|
+
this.line("</tbody>");
|
|
8955
|
+
this.line("<tfoot>");
|
|
8956
|
+
this.line(summaryRow("All baselines", all));
|
|
8957
|
+
this.line("</tfoot>");
|
|
8958
|
+
this.line("</table>");
|
|
8959
|
+
this.line("</div>");
|
|
8960
|
+
this.closeFold(byBaseline);
|
|
8961
|
+
this.line("</section>");
|
|
8962
|
+
}
|
|
8963
|
+
results() {
|
|
8964
|
+
this.line("<section id=\"results\" class=\"card\" aria-labelledby=\"results-heading\">");
|
|
8965
|
+
this.line("<h2 id=\"results-heading\">Results</h2>");
|
|
8966
|
+
this.line("<div class=\"toolbar\">");
|
|
8967
|
+
this.line("<input type=\"search\" id=\"filter-text\" placeholder=\"Filter by ID, title or control\" aria-label=\"Filter requirements by ID, title or control\" />");
|
|
8968
|
+
this.line("<div class=\"filters\" role=\"group\" aria-label=\"Filter by status\">");
|
|
8969
|
+
this.line("<button type=\"button\" class=\"filter\" data-status=\"all\" aria-pressed=\"true\">All</button>");
|
|
8970
|
+
for (const [key, label] of STATUS_FILTERS) this.line(`<button type="button" class="filter" data-status="${key}" aria-pressed="false">${label}</button>`);
|
|
8971
|
+
this.line("</div>");
|
|
8972
|
+
this.line("<button type=\"button\" id=\"expand-all\">Expand all</button>");
|
|
8973
|
+
this.line("<button type=\"button\" id=\"collapse-all\">Collapse all</button>");
|
|
8974
|
+
this.line("<span id=\"filter-count\" class=\"shown\" aria-live=\"polite\"></span>");
|
|
8975
|
+
this.line("</div>");
|
|
8976
|
+
for (const src of this.sources) {
|
|
8977
|
+
this.src = src;
|
|
8978
|
+
let f;
|
|
8979
|
+
if (this.aggregated) {
|
|
8980
|
+
const total = src.baselines.reduce((n, baseline) => n + list(baseline.requirements).length, 0);
|
|
8981
|
+
f = this.openFold("group source", "", "h3", escapeHtml(src.name), src.name, total);
|
|
8982
|
+
}
|
|
8983
|
+
src.baselines.forEach((baseline, i) => this.baseline(baseline, src.baselineLabels[i]));
|
|
8984
|
+
if (f) this.closeFold(f);
|
|
8985
|
+
}
|
|
8986
|
+
this.line("</section>");
|
|
8987
|
+
}
|
|
8988
|
+
baseline(baseline, label) {
|
|
8989
|
+
const requirements = list(baseline.requirements).map(obj);
|
|
8990
|
+
const f = this.openFold("group baseline", "", this.baselineHeading, escapeHtml(label), label, requirements.length);
|
|
8991
|
+
const facts = [
|
|
8992
|
+
["Name", text(baseline.name)],
|
|
8993
|
+
["Title", text(baseline.title)],
|
|
8994
|
+
["Version", text(baseline.version)],
|
|
8995
|
+
["Summary", text(baseline.summary)],
|
|
8996
|
+
["Description", text(baseline.description)],
|
|
8997
|
+
["Maintainer", text(baseline.maintainer)],
|
|
8998
|
+
["License", text(baseline.license)],
|
|
8999
|
+
["Copyright", text(baseline.copyright)],
|
|
9000
|
+
["Status message", text(baseline.statusMessage)]
|
|
9001
|
+
];
|
|
9002
|
+
const factCount = countFacts(facts);
|
|
9003
|
+
if (factCount > 0) {
|
|
9004
|
+
const details = this.openFold("", "", this.detailHeading, "Baseline details", "the baseline details", factCount);
|
|
9005
|
+
this.definitionList(facts);
|
|
9006
|
+
this.closeFold(details);
|
|
9007
|
+
}
|
|
9008
|
+
this.externalReferences(this.detailHeading, "Enrichment and External References", baseline.externalReferences);
|
|
9009
|
+
if (requirements.length > 0) this.line("<div class=\"req-head\" aria-hidden=\"true\"><span>Status</span><span>ID</span><span>Severity</span><span>Title</span><span>800-53 Controls & CCIs</span></div>");
|
|
9010
|
+
for (const req of requirements) this.requirement(req);
|
|
9011
|
+
this.closeFold(f);
|
|
9012
|
+
}
|
|
9013
|
+
requirement(req) {
|
|
9014
|
+
const effective = this.effectiveStatus(req);
|
|
9015
|
+
const results = list(req.results).map(obj);
|
|
9016
|
+
const severity = requirementSeverity(req, this.src.ref.stamp);
|
|
9017
|
+
const tags = obj(req.tags);
|
|
9018
|
+
const nist = tagItems(tags, "nist");
|
|
9019
|
+
const cci = tagItems(tags, "cci");
|
|
9020
|
+
const [cls] = statusPresentation(effective);
|
|
9021
|
+
this.requirements++;
|
|
9022
|
+
const id = `req-${this.requirements}`;
|
|
9023
|
+
this.line(`<article class="requirement c-${cls}" data-status="${cls}" id="${id}">`);
|
|
9024
|
+
this.line("<details>");
|
|
9025
|
+
const referenceCount = list(req.externalReferences).length;
|
|
9026
|
+
const tagHtml = [...nist, ...cci].map((t, i) => `${i === 0 ? "<span class=\"vh\">Controls: </span>" : ""}<span class="tag">${escapeHtml(t)}</span>`).join("") + (referenceCount > 0 ? `<span class="tag enriched">Enriched (${referenceCount})</span>` : "");
|
|
9027
|
+
this.line(`<summary>${statusBadge(effective)}<span class="req-id">${escapeHtml(text(req.id))}</span><span class="sev c-${severityClass(severity)}"><span class="vh">Severity: </span>${escapeHtml(severityLabel(severity))}</span><span class="req-title">${escapeHtml(text(req.title))}</span><span class="tags">${tagHtml}</span></summary>`);
|
|
9028
|
+
this.line("<div class=\"req-body\">");
|
|
9029
|
+
const descriptions = list(req.descriptions).map(obj);
|
|
9030
|
+
const lead = descriptions.findIndex((d) => d.label === "default");
|
|
9031
|
+
const location = sourceLocation(req.sourceLocation);
|
|
9032
|
+
if (location !== "") this.line(`<p class="location"><span class="k">Location</span><code>${escapeHtml(location)}</code></p>`);
|
|
9033
|
+
const leadText = lead >= 0 ? text(descriptions[lead].data) : "";
|
|
9034
|
+
if (leadText !== "" && isLongText(leadText)) {
|
|
9035
|
+
const f = this.openFold("", "", this.detailHeading, "Description", "the description", lineCount(leadText));
|
|
9036
|
+
this.line(prose(leadText));
|
|
9037
|
+
this.closeFold(f);
|
|
9038
|
+
} else if (leadText !== "") this.line(prose(leadText));
|
|
9039
|
+
this.resultRows(results);
|
|
9040
|
+
const impact = numeric(req.impact);
|
|
9041
|
+
const rows = [detailRow("Effective status", statusBadge(effective)), detailRow("Assessed status", statusBadge(worstStatus(results.map((r) => text(r.status)))))];
|
|
9042
|
+
const details = [
|
|
9043
|
+
["Control", text(req.id)],
|
|
9044
|
+
["Title", text(req.title)],
|
|
9045
|
+
["Severity", severity],
|
|
9046
|
+
["Impact", impact.toFixed(2)],
|
|
9047
|
+
["Effective impact", requirementEffectiveImpact(req, this.src.ref.stamp).toFixed(2)],
|
|
9048
|
+
["Disposition", requirementDisposition(req, this.src.ref.stamp)],
|
|
9049
|
+
["Control type", text(req.controlType)],
|
|
9050
|
+
["Verification method", text(req.verificationMethod)],
|
|
9051
|
+
["Applicability", text(req.applicability)],
|
|
9052
|
+
["Source location", location],
|
|
9053
|
+
["NIST Controls", nist.join(", ")],
|
|
9054
|
+
["CCI Controls", cci.join(", ")],
|
|
9055
|
+
["CWE", strings(req.cwe).join(", ")],
|
|
9056
|
+
["CVSS", cvssText(list(req.cvss).map(obj))],
|
|
9057
|
+
["EPSS", epssText(req.epss)],
|
|
9058
|
+
["KEV", kevText(req.kev)],
|
|
9059
|
+
["Affected packages", packagesText(list(req.affectedPackages).map(obj))],
|
|
9060
|
+
["Evidence", evidenceText(list(req.evidence).map(obj))]
|
|
9061
|
+
];
|
|
9062
|
+
for (const [term, value] of details) if (value !== "") rows.push(detailRow(term, escapeHtml(value)));
|
|
9063
|
+
const references = referenceLines(list(req.refs).map(obj));
|
|
9064
|
+
if (references.length > 0) rows.push(detailRow("References", prose(references.join("\n"))));
|
|
9065
|
+
descriptions.forEach((d, i) => {
|
|
9066
|
+
if (i !== lead) rows.push(detailRow(escapeHtml(descriptionHeading(text(d.label))), prose(text(d.data))));
|
|
9067
|
+
});
|
|
9068
|
+
const detailsFold = this.openFold("", "", this.detailHeading, "Result Details", "the result details", rows.length);
|
|
9069
|
+
this.line("<div class=\"table-wrap\">");
|
|
9070
|
+
this.line("<table class=\"details\" aria-label=\"Result details\">");
|
|
9071
|
+
this.line("<tbody>");
|
|
9072
|
+
for (const row of rows) this.line(row);
|
|
9073
|
+
this.line("</tbody>");
|
|
9074
|
+
this.line("</table>");
|
|
9075
|
+
this.line("</div>");
|
|
9076
|
+
this.closeFold(detailsFold);
|
|
9077
|
+
this.tagChips(tags);
|
|
9078
|
+
this.externalReferences(this.detailHeading, "Enrichment and External References", req.externalReferences);
|
|
9079
|
+
const overrides = list(req.statusOverrides).map(obj);
|
|
9080
|
+
this.overrideRows(overrides);
|
|
9081
|
+
overrides.forEach((o, i) => this.externalReferences(this.detailHeading, `References for override ${i + 1}`, o.externalReferences));
|
|
9082
|
+
this.poamRows(list(req.poams).map(obj));
|
|
9083
|
+
const code = text(req.code);
|
|
9084
|
+
if (this.reportType === "administrator" && code !== "") {
|
|
9085
|
+
const f = this.openFold("", "", this.detailHeading, "Code", "the code", lineCount(code));
|
|
9086
|
+
this.line(`<pre class="code">\n${escapeHtml(code)}</pre>`);
|
|
9087
|
+
this.closeFold(f);
|
|
9088
|
+
}
|
|
9089
|
+
this.line(`<p class="to-top"><a href="#${id}">Back to the top of this requirement</a></p>`);
|
|
9090
|
+
this.line("</div>");
|
|
9091
|
+
this.line("</details>");
|
|
9092
|
+
this.line("</article>");
|
|
9093
|
+
}
|
|
9094
|
+
resultRows(results) {
|
|
9095
|
+
if (results.length === 0) return;
|
|
9096
|
+
const f = this.openFold("", "", this.detailHeading, "Test Results", "the test results", results.length);
|
|
9097
|
+
this.line("<div class=\"table-wrap\">");
|
|
9098
|
+
this.line("<table aria-label=\"Test results\">");
|
|
9099
|
+
this.line("<thead><tr><th scope=\"col\">Status</th><th scope=\"col\">Test</th><th scope=\"col\">Result</th><th scope=\"col\">Started</th></tr></thead>");
|
|
9100
|
+
this.line("<tbody>");
|
|
9101
|
+
for (const res of results) {
|
|
9102
|
+
const runTime = typeof res.runTime === "number" ? `${res.runTime.toFixed(3)} s` : "";
|
|
9103
|
+
this.line(`<tr><td>${statusBadge(text(res.status))}</td><td class="text">${escapeHtml(text(res.codeDesc))}` + subLine("Resource", joinNonEmpty(" · ", text(res.resource), text(res.resourceId))) + `</td><td class="text">${escapeHtml(text(res.message))}` + subLine("Exception", text(res.exception)) + subLine("Backtrace", strings(res.backtrace).join("\n")) + `</td><td>${escapeHtml(formatTime(res.startTime))}${subLine("Run time", runTime)}</td></tr>`);
|
|
9104
|
+
}
|
|
9105
|
+
this.line("</tbody>");
|
|
9106
|
+
this.line("</table>");
|
|
9107
|
+
this.line("</div>");
|
|
9108
|
+
this.closeFold(f);
|
|
9109
|
+
}
|
|
9110
|
+
overrideRows(overrides) {
|
|
9111
|
+
if (overrides.length === 0) return;
|
|
9112
|
+
const governing = governingStatusOverrideIndex(overrideInputs(overrides), this.src.ref.stamp);
|
|
9113
|
+
const f = this.openFold("", "", this.detailHeading, "Overrides", "the overrides", overrides.length);
|
|
9114
|
+
this.line("<div class=\"table-wrap\">");
|
|
9115
|
+
this.line("<table aria-label=\"Overrides\">");
|
|
9116
|
+
this.line("<thead><tr><th scope=\"col\">Type</th><th scope=\"col\">Status</th><th scope=\"col\">Reason</th><th scope=\"col\">Applied by</th><th scope=\"col\">Applied at</th><th scope=\"col\">Expires at</th><th scope=\"col\">State</th></tr></thead>");
|
|
9117
|
+
this.line("<tbody>");
|
|
9118
|
+
overrides.forEach((o, i) => {
|
|
9119
|
+
const status = text(o.status) === "" ? "" : statusBadge(text(o.status));
|
|
9120
|
+
const expires = toDate$1(o.expiresAt);
|
|
9121
|
+
let state = "";
|
|
9122
|
+
if (i === governing) state = "governing";
|
|
9123
|
+
else if (expires && expires <= this.src.ref.date) state = "expired";
|
|
9124
|
+
const impact = o.impact == null ? "" : numeric(obj(o.impact).value).toFixed(2);
|
|
9125
|
+
this.line(`<tr><td>${escapeHtml(text(o.type))}</td><td>${status}${subLine("Impact", impact)}` + subLine("CVSS", o.cvss == null ? "" : cvssText([obj(o.cvss)])) + `</td><td class="text">${escapeHtml(text(o.reason))}${subLine("Justification", text(o.justification))}</td><td>${escapeHtml(text(obj(o.appliedBy).identifier))}</td><td>${escapeHtml(formatTime(o.appliedAt))}</td><td>${escapeHtml(formatTime(o.expiresAt))}</td><td>${state}</td></tr>`);
|
|
9126
|
+
});
|
|
9127
|
+
this.line("</tbody>");
|
|
9128
|
+
this.line("</table>");
|
|
9129
|
+
this.line("</div>");
|
|
9130
|
+
this.closeFold(f);
|
|
9131
|
+
}
|
|
9132
|
+
/**
|
|
9133
|
+
* References to artifacts outside the document: a CVE, an ATT&CK technique, a
|
|
9134
|
+
* STIX object. A reference that embeds its payload is an enrichment envelope,
|
|
9135
|
+
* and the payload is shown whole because HDF carries it untouched rather than
|
|
9136
|
+
* mapping it into fields.
|
|
9137
|
+
*/
|
|
9138
|
+
externalReferences(headingTag, heading, value) {
|
|
9139
|
+
const refs = list(value).map(obj);
|
|
9140
|
+
if (refs.length === 0) return;
|
|
9141
|
+
const f = this.openFold("", "", headingTag, heading, heading.charAt(0).toLowerCase() + heading.slice(1), refs.length);
|
|
9142
|
+
this.line("<div class=\"table-wrap\">");
|
|
9143
|
+
this.line(`<table aria-label="${heading}">`);
|
|
9144
|
+
this.line("<thead><tr><th scope=\"col\">Source</th><th scope=\"col\">ID</th><th scope=\"col\">Kind</th><th scope=\"col\">Relation</th><th scope=\"col\">Detail</th></tr></thead>");
|
|
9145
|
+
this.line("<tbody>");
|
|
9146
|
+
for (const ref of refs) {
|
|
9147
|
+
const checksum = ref.checksum == null ? {} : obj(ref.checksum);
|
|
9148
|
+
const added = joinNonEmpty(" · ", ref.addedBy == null ? "" : text(obj(ref.addedBy).identifier), formatTime(ref.addedAt));
|
|
9149
|
+
this.line(`<tr><td>${escapeHtml(text(ref.sourceName))}</td><td>${escapeHtml(text(ref.externalId))}</td><td>${escapeHtml(text(ref.kind))}</td><td>${escapeHtml(text(ref.rel))}</td><td class="text">` + escapeHtml(text(ref.description)) + subLine("Location", text(ref.href)) + subLine("Media type", text(ref.mediaType)) + subLine("Checksum", joinNonEmpty(":", text(checksum.algorithm), text(checksum.value))) + subLine("Added", added) + embeddedDocument(ref.document) + "</td></tr>");
|
|
9150
|
+
}
|
|
9151
|
+
this.line("</tbody>");
|
|
9152
|
+
this.line("</table>");
|
|
9153
|
+
this.line("</div>");
|
|
9154
|
+
this.closeFold(f);
|
|
9155
|
+
}
|
|
9156
|
+
poamRows(poams) {
|
|
9157
|
+
if (poams.length === 0) return;
|
|
9158
|
+
const f = this.openFold("", "", this.detailHeading, "POA&Ms", "the POA&Ms", poams.length);
|
|
9159
|
+
this.line("<div class=\"table-wrap\">");
|
|
9160
|
+
this.line("<table aria-label=\"Plans of action and milestones\">");
|
|
9161
|
+
this.line("<thead><tr><th scope=\"col\">Type</th><th scope=\"col\">Explanation</th><th scope=\"col\">Applied by</th><th scope=\"col\">Applied at</th><th scope=\"col\">Expires at</th></tr></thead>");
|
|
9162
|
+
this.line("<tbody>");
|
|
9163
|
+
for (const p of poams) {
|
|
9164
|
+
const milestones = list(p.milestones).map(obj).map((m) => subLine("Milestone", joinNonEmpty(" · ", text(m.status), text(m.title), text(m.description), formatTime(m.estimatedCompletion)))).join("");
|
|
9165
|
+
this.line(`<tr><td>${escapeHtml(text(p.type))}</td><td class="text">${escapeHtml(text(p.explanation))}${milestones}</td><td>${escapeHtml(text(obj(p.appliedBy).identifier))}</td><td>${escapeHtml(formatTime(p.appliedAt))}</td><td>${escapeHtml(formatTime(p.expiresAt))}</td></tr>`);
|
|
9166
|
+
}
|
|
9167
|
+
this.line("</tbody>");
|
|
9168
|
+
this.line("</table>");
|
|
9169
|
+
this.line("</div>");
|
|
9170
|
+
this.closeFold(f);
|
|
9171
|
+
}
|
|
9172
|
+
/**
|
|
9173
|
+
* The tags the detail table does not show: every tag but nist and cci whose
|
|
9174
|
+
* value is text, a list of text, or a boolean. Numbers and nested values are
|
|
9175
|
+
* left out because the two languages do not print them alike.
|
|
9176
|
+
*/
|
|
9177
|
+
tagChips(tags) {
|
|
9178
|
+
const entries = [];
|
|
9179
|
+
for (const key of Object.keys(tags).sort(byCodePoint)) {
|
|
9180
|
+
if (key === "nist" || key === "cci") continue;
|
|
9181
|
+
const value = tagText(tags[key]);
|
|
9182
|
+
if (value !== null) entries.push([key, value]);
|
|
9183
|
+
}
|
|
9184
|
+
if (entries.length === 0) return;
|
|
9185
|
+
const f = this.openFold("", "", this.detailHeading, "Tags", "the tags", entries.length);
|
|
9186
|
+
this.line("<ul class=\"chips\">");
|
|
9187
|
+
for (const [k, v] of entries) this.line(`<li><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></li>`);
|
|
9188
|
+
this.line("</ul>");
|
|
9189
|
+
this.closeFold(f);
|
|
9190
|
+
}
|
|
9191
|
+
};
|
|
9192
|
+
/**
|
|
9193
|
+
* Counts the external references anywhere in the document and breaks them down
|
|
9194
|
+
* by kind, so even the executive report says whether the results carry
|
|
9195
|
+
* enrichment.
|
|
9196
|
+
*/
|
|
9197
|
+
function referenceCensus(doc) {
|
|
9198
|
+
let total = 0;
|
|
9199
|
+
const kinds = /* @__PURE__ */ new Map();
|
|
9200
|
+
const count = (value) => {
|
|
9201
|
+
for (const ref of list(value).map(obj)) {
|
|
9202
|
+
total++;
|
|
9203
|
+
const kind = text(ref.kind);
|
|
9204
|
+
if (kind !== "") kinds.set(kind, (kinds.get(kind) ?? 0) + 1);
|
|
9205
|
+
}
|
|
9206
|
+
};
|
|
9207
|
+
count(doc.externalReferences);
|
|
9208
|
+
for (const baseline of list(doc.baselines).map(obj)) {
|
|
9209
|
+
count(baseline.externalReferences);
|
|
9210
|
+
for (const req of list(baseline.requirements).map(obj)) {
|
|
9211
|
+
count(req.externalReferences);
|
|
9212
|
+
for (const o of list(req.statusOverrides).map(obj)) count(o.externalReferences);
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
if (total === 0) return "";
|
|
9216
|
+
const parts = [...kinds.keys()].sort(byCodePoint).map((kind) => `${kind} ${kinds.get(kind)}`);
|
|
9217
|
+
return parts.length === 0 ? String(total) : `${total} (${parts.join(", ")})`;
|
|
9218
|
+
}
|
|
9219
|
+
/**
|
|
9220
|
+
* An enrichment payload: what it says it is, then the whole object. Canonical
|
|
9221
|
+
* JSON is what makes the text the same in both languages.
|
|
9222
|
+
*/
|
|
9223
|
+
function embeddedDocument(value) {
|
|
9224
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return "";
|
|
9225
|
+
const document = value;
|
|
9226
|
+
return subLine("Embedded", joinNonEmpty(" · ", text(document.type), text(document.name))) + `<details class="doc"><summary>Embedded document</summary><pre class="code">
|
|
9227
|
+
${escapeHtml(indentJson(canonicalJson(document)))}</pre></details>`;
|
|
9228
|
+
}
|
|
9229
|
+
/**
|
|
9230
|
+
* Lays compact JSON out over lines, two spaces a level. It only inserts
|
|
9231
|
+
* whitespace between tokens, so the values stay exactly as serialized.
|
|
9232
|
+
*/
|
|
9233
|
+
function indentJson(compact) {
|
|
9234
|
+
let out = "";
|
|
9235
|
+
let depth = 0;
|
|
9236
|
+
const newline = () => `\n${" ".repeat(depth)}`;
|
|
9237
|
+
let i = 0;
|
|
9238
|
+
while (i < compact.length) {
|
|
9239
|
+
const c = compact[i];
|
|
9240
|
+
const next = compact[i + 1];
|
|
9241
|
+
let end = i + 1;
|
|
9242
|
+
if (c === "\"") {
|
|
9243
|
+
end = stringEnd(compact, i);
|
|
9244
|
+
out += compact.slice(i, end);
|
|
9245
|
+
} else if ((c === "{" || c === "[") && (next === "}" || next === "]")) {
|
|
9246
|
+
end = i + 2;
|
|
9247
|
+
out += c + next;
|
|
9248
|
+
} else if (c === "{" || c === "[") {
|
|
9249
|
+
depth++;
|
|
9250
|
+
out += c + newline();
|
|
9251
|
+
} else if (c === "}" || c === "]") {
|
|
9252
|
+
depth--;
|
|
9253
|
+
out += newline() + c;
|
|
9254
|
+
} else if (c === ",") out += c + newline();
|
|
9255
|
+
else out += c === ":" ? ": " : c;
|
|
9256
|
+
i = end;
|
|
9257
|
+
}
|
|
9258
|
+
return out;
|
|
9259
|
+
}
|
|
9260
|
+
/** The index just past the JSON string that opens at `start`; the end of the text when it never closes. */
|
|
9261
|
+
function stringEnd(text, start) {
|
|
9262
|
+
let i = start + 1;
|
|
9263
|
+
while (i < text.length) {
|
|
9264
|
+
if (text[i] === "\"") return i + 1;
|
|
9265
|
+
i += text[i] === "\\" ? 2 : 1;
|
|
9266
|
+
}
|
|
9267
|
+
return text.length;
|
|
9268
|
+
}
|
|
9269
|
+
function strings(value) {
|
|
9270
|
+
return list(value).filter((item) => typeof item === "string");
|
|
9271
|
+
}
|
|
9272
|
+
function tagText(value) {
|
|
9273
|
+
if (typeof value === "string") return value;
|
|
9274
|
+
if (typeof value === "boolean") return String(value);
|
|
9275
|
+
if (Array.isArray(value)) {
|
|
9276
|
+
const items = strings(value);
|
|
9277
|
+
return items.length > 0 ? items.join(", ") : null;
|
|
9278
|
+
}
|
|
9279
|
+
return null;
|
|
9280
|
+
}
|
|
9281
|
+
/** A labelled secondary line inside a table cell; empty when there is nothing to say. */
|
|
9282
|
+
function subLine(label, value) {
|
|
9283
|
+
return value === "" ? "" : `<span class="sub"><span class="k">${label}</span> ${escapeHtml(value)}</span>`;
|
|
9284
|
+
}
|
|
9285
|
+
/** Where the finding sits in the assessed source: file, and line when there is one. */
|
|
9286
|
+
function sourceLocation(value) {
|
|
9287
|
+
if (value == null) return "";
|
|
9288
|
+
const loc = obj(value);
|
|
9289
|
+
const ref = text(loc.ref);
|
|
9290
|
+
if (typeof loc.line !== "number") return ref;
|
|
9291
|
+
return ref === "" ? `line ${loc.line}` : `${ref}:${loc.line}`;
|
|
9292
|
+
}
|
|
9293
|
+
function cvssText(entries) {
|
|
9294
|
+
const parts = [];
|
|
9295
|
+
for (const c of entries) {
|
|
9296
|
+
const score = typeof c.computedScore === "number" ? c.computedScore : c.baseScore;
|
|
9297
|
+
const severity = typeof c.computedSeverity === "string" ? c.computedSeverity : text(c.baseSeverity);
|
|
9298
|
+
const version = text(c.version);
|
|
9299
|
+
const source = text(c.source);
|
|
9300
|
+
const part = joinNonEmpty(" ", version === "" ? "" : `v${version}`, typeof score === "number" ? score.toFixed(1) : "", severity, text(c.baseVector), source === "" ? "" : `(${source})`);
|
|
9301
|
+
if (part !== "") parts.push(part);
|
|
9302
|
+
}
|
|
9303
|
+
return parts.join("; ");
|
|
9304
|
+
}
|
|
9305
|
+
function epssText(value) {
|
|
9306
|
+
if (value == null) return "";
|
|
9307
|
+
const e = obj(value);
|
|
9308
|
+
const date = text(e.date);
|
|
9309
|
+
return `score ${numeric(e.score).toFixed(5)}, percentile ${numeric(e.percentile).toFixed(5)}` + (date === "" ? "" : `, as of ${date}`);
|
|
9310
|
+
}
|
|
9311
|
+
function kevText(value) {
|
|
9312
|
+
if (value == null) return "";
|
|
9313
|
+
const k = obj(value);
|
|
9314
|
+
let out = k.inKev === true ? "Listed" : "Not listed";
|
|
9315
|
+
if (text(k.dateAdded) !== "") out += `, added ${text(k.dateAdded)}`;
|
|
9316
|
+
if (text(k.dueDate) !== "") out += `, due ${text(k.dueDate)}`;
|
|
9317
|
+
if (text(k.notes) !== "") out += `, ${text(k.notes)}`;
|
|
9318
|
+
return out;
|
|
9319
|
+
}
|
|
9320
|
+
function packagesText(packages) {
|
|
9321
|
+
const parts = [];
|
|
9322
|
+
for (const p of packages) {
|
|
9323
|
+
let part = joinNonEmpty(" ", text(p.name), text(p.version));
|
|
9324
|
+
if (part === "") part = joinNonEmpty(" ", text(p.purl), text(p.cpe));
|
|
9325
|
+
const fixed = text(p.fixedInVersion);
|
|
9326
|
+
if (fixed !== "") part = joinNonEmpty(" ", part, `(fixed in ${fixed})`);
|
|
9327
|
+
if (part !== "") parts.push(part);
|
|
9328
|
+
}
|
|
9329
|
+
return parts.join("; ");
|
|
9330
|
+
}
|
|
9331
|
+
function evidenceText(evidence) {
|
|
9332
|
+
return evidence.map((e) => joinNonEmpty(": ", text(e.type), text(e.description))).filter((part) => part !== "").join("; ");
|
|
9333
|
+
}
|
|
9334
|
+
/**
|
|
9335
|
+
* Each reference's text. A structured ref is reduced to the URLs inside it,
|
|
9336
|
+
* sorted, because the Go peer decodes it to a map, which has no order to
|
|
9337
|
+
* preserve.
|
|
9338
|
+
*/
|
|
9339
|
+
function referenceLines(refs) {
|
|
9340
|
+
const lines = [];
|
|
9341
|
+
for (const ref of refs) {
|
|
9342
|
+
if (typeof ref.ref === "string") {
|
|
9343
|
+
if (ref.ref !== "") lines.push(ref.ref);
|
|
9344
|
+
} else if (Array.isArray(ref.ref)) {
|
|
9345
|
+
const urls = /* @__PURE__ */ new Set();
|
|
9346
|
+
collectUrls(ref.ref, urls);
|
|
9347
|
+
lines.push(...[...urls].sort(byCodePoint));
|
|
9348
|
+
}
|
|
9349
|
+
for (const s of [text(ref.url), text(ref.uri)]) if (s !== "") lines.push(s);
|
|
9350
|
+
}
|
|
9351
|
+
return lines;
|
|
9352
|
+
}
|
|
9353
|
+
function collectUrls(value, into) {
|
|
9354
|
+
if (Array.isArray(value)) for (const child of value) collectUrls(child, into);
|
|
9355
|
+
else if (typeof value === "object" && value !== null) for (const [k, child] of Object.entries(value)) if (k === "url" && typeof child === "string" && child !== "") into.add(child);
|
|
9356
|
+
else collectUrls(child, into);
|
|
9357
|
+
}
|
|
9358
|
+
/** Component fields in display order; the second member is the JSON key. */
|
|
9359
|
+
const COMPONENT_FIELDS = [
|
|
9360
|
+
["Component ID", "componentId"],
|
|
9361
|
+
["Description", "description"],
|
|
9362
|
+
["Hostname", "hostname"],
|
|
9363
|
+
["FQDN", "fqdn"],
|
|
9364
|
+
["Domain", "domain"],
|
|
9365
|
+
["IP address", "ipAddress"],
|
|
9366
|
+
["MAC address", "macAddress"],
|
|
9367
|
+
["OS name", "osName"],
|
|
9368
|
+
["OS version", "osVersion"],
|
|
9369
|
+
["Image ID", "imageId"],
|
|
9370
|
+
["Registry", "registry"],
|
|
9371
|
+
["Repository", "repository"],
|
|
9372
|
+
["Tag", "tag"],
|
|
9373
|
+
["Container ID", "containerId"],
|
|
9374
|
+
["Image", "image"],
|
|
9375
|
+
["Runtime", "runtime"],
|
|
9376
|
+
["Cluster name", "clusterName"],
|
|
9377
|
+
["Namespace", "namespace"],
|
|
9378
|
+
["Platform type", "platformType"],
|
|
9379
|
+
["Version", "version"],
|
|
9380
|
+
["Account ID", "accountId"],
|
|
9381
|
+
["Provider", "provider"],
|
|
9382
|
+
["Region", "region"],
|
|
9383
|
+
["ARN", "arn"],
|
|
9384
|
+
["Resource ID", "resourceId"],
|
|
9385
|
+
["Resource type", "resourceType"],
|
|
9386
|
+
["Branch", "branch"],
|
|
9387
|
+
["Commit", "commit"],
|
|
9388
|
+
["URL", "url"],
|
|
9389
|
+
["Environment", "environment"],
|
|
9390
|
+
["Package manager", "packageManager"],
|
|
9391
|
+
["Package name", "packageName"],
|
|
9392
|
+
["CIDR", "cidr"],
|
|
9393
|
+
["Gateway", "gateway"],
|
|
9394
|
+
["Engine", "engine"],
|
|
9395
|
+
["Host", "host"],
|
|
9396
|
+
["Port", "port"],
|
|
9397
|
+
["Model ID", "modelId"],
|
|
9398
|
+
["Dataset ID", "datasetId"],
|
|
9399
|
+
["Owner", "owner"]
|
|
9400
|
+
];
|
|
9401
|
+
function identity(value) {
|
|
9402
|
+
if (value == null) return "";
|
|
9403
|
+
const id = obj(value);
|
|
9404
|
+
const type = text(id.type);
|
|
9405
|
+
if (type === "") return text(id.identifier);
|
|
9406
|
+
return joinNonEmpty(" ", text(id.identifier), `(${type})`);
|
|
9407
|
+
}
|
|
9408
|
+
/** A summary table row; `name` is already HTML. */
|
|
9409
|
+
function summaryRow(name, c) {
|
|
9410
|
+
return `<tr><th scope="row">${name}</th>${[
|
|
9411
|
+
c.passed.total,
|
|
9412
|
+
c.failed.total,
|
|
9413
|
+
c.skipped.total,
|
|
9414
|
+
c.noImpact.total,
|
|
9415
|
+
c.error.total,
|
|
9416
|
+
severityTotals(c).total
|
|
9417
|
+
].map((n) => `<td>${n}</td>`).join("")}<td>${compliance(c)}</td></tr>`;
|
|
9418
|
+
}
|
|
9419
|
+
function summaryHead(first) {
|
|
9420
|
+
return `<thead><tr><th scope="col">${first}</th><th scope="col">Passed</th><th scope="col">Failed</th><th scope="col">Not Reviewed</th><th scope="col">Not Applicable</th><th scope="col">Error</th><th scope="col">Total</th><th scope="col">Compliance</th></tr></thead>`;
|
|
9421
|
+
}
|
|
9422
|
+
function sourceId(i) {
|
|
9423
|
+
return `source-${i + 1}`;
|
|
9424
|
+
}
|
|
9425
|
+
function componentField(c, term, key) {
|
|
9426
|
+
if (key === "port") return [term, typeof c.port === "number" ? String(c.port) : ""];
|
|
9427
|
+
if (key === "owner") return [term, identity(c.owner)];
|
|
9428
|
+
return [term, text(c[key])];
|
|
9429
|
+
}
|
|
9430
|
+
function documentFacts(doc) {
|
|
9431
|
+
const tool = obj(doc.tool);
|
|
9432
|
+
const generator = obj(doc.generator);
|
|
9433
|
+
const duration = obj(doc.statistics).duration;
|
|
9434
|
+
return [
|
|
9435
|
+
["Tool", joinNonEmpty(" ", text(tool.name), text(tool.version))],
|
|
9436
|
+
["Tool format", text(tool.format)],
|
|
9437
|
+
["Generator", joinNonEmpty(" ", text(generator.name), text(generator.version))],
|
|
9438
|
+
["Assessed", formatTime(doc.timestamp)],
|
|
9439
|
+
["Duration", typeof duration === "number" ? `${duration.toFixed(2)} s` : ""],
|
|
9440
|
+
["System reference", text(doc.systemRef)],
|
|
9441
|
+
["Plan reference", text(doc.planRef)],
|
|
9442
|
+
["Document ID", text(doc.id)],
|
|
9443
|
+
["External references", referenceCensus(doc)]
|
|
9444
|
+
];
|
|
9445
|
+
}
|
|
9446
|
+
function runnerFacts(runner) {
|
|
9447
|
+
return [
|
|
9448
|
+
["Name", text(runner.name)],
|
|
9449
|
+
["Hostname", text(runner.hostname)],
|
|
9450
|
+
["FQDN", text(runner.fqdn)],
|
|
9451
|
+
["Domain", text(runner.domain)],
|
|
9452
|
+
["Architecture", text(runner.architecture)],
|
|
9453
|
+
["Release", text(runner.release)],
|
|
9454
|
+
["Container image", text(runner.containerImage)],
|
|
9455
|
+
["Container ID", text(runner.containerId)],
|
|
9456
|
+
["Operator", identity(runner.operator)]
|
|
9457
|
+
];
|
|
9458
|
+
}
|
|
9459
|
+
const STATUS_PRESENTATION = {
|
|
9460
|
+
passed: ["passed", "Passed"],
|
|
9461
|
+
failed: ["failed", "Failed"],
|
|
9462
|
+
notReviewed: ["not-reviewed", "Not Reviewed"],
|
|
9463
|
+
notApplicable: ["not-applicable", "Not Applicable"],
|
|
9464
|
+
error: ["error", "Error"]
|
|
9465
|
+
};
|
|
9466
|
+
/** A status with a class drawn from a fixed set, never from the document: one outside the enum is shown as text under one class. */
|
|
9467
|
+
function statusPresentation(status) {
|
|
9468
|
+
return Object.prototype.hasOwnProperty.call(STATUS_PRESENTATION, status) ? STATUS_PRESENTATION[status] : ["unknown", status];
|
|
9469
|
+
}
|
|
9470
|
+
/**
|
|
9471
|
+
* The mark beside the status word. Colour is the least reliable of the three
|
|
9472
|
+
* channels — it is lost to colour-vision deficiency and to a greyscale printer —
|
|
9473
|
+
* so every status carries a shape and a word as well.
|
|
9474
|
+
*/
|
|
9475
|
+
const STATUS_ICON = {
|
|
9476
|
+
passed: "✓",
|
|
9477
|
+
failed: "✗",
|
|
9478
|
+
"not-applicable": "–",
|
|
9479
|
+
"not-reviewed": "?",
|
|
9480
|
+
error: "!"
|
|
9481
|
+
};
|
|
9482
|
+
function statusIcon(cls) {
|
|
9483
|
+
return Object.prototype.hasOwnProperty.call(STATUS_ICON, cls) ? STATUS_ICON[cls] : "·";
|
|
9484
|
+
}
|
|
9485
|
+
function statusBadge(status) {
|
|
9486
|
+
const [cls, label] = statusPresentation(status);
|
|
9487
|
+
return `<span class="status c-${cls}"><span class="ico" aria-hidden="true">${statusIcon(cls)}</span>${escapeHtml(label)}</span>`;
|
|
9488
|
+
}
|
|
9489
|
+
/** The string members of an array-valued tag; a bare string tag is a one-member list. */
|
|
9490
|
+
function tagItems(tags, key) {
|
|
9491
|
+
const value = Object.prototype.hasOwnProperty.call(tags, key) ? tags[key] : void 0;
|
|
9492
|
+
if (typeof value === "string") return [value];
|
|
9493
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
|
|
9494
|
+
return [];
|
|
9495
|
+
}
|
|
9496
|
+
function joinNonEmpty(sep, ...parts) {
|
|
9497
|
+
return parts.filter((p) => p !== "").join(sep);
|
|
9498
|
+
}
|
|
9499
|
+
/**
|
|
9500
|
+
* Makes text safe as HTML content and inside a double-quoted attribute. A
|
|
9501
|
+
* carriage return is written as a character reference because a parser would
|
|
9502
|
+
* otherwise fold it into the following newline. Characters HTML cannot carry at
|
|
9503
|
+
* all, and unpaired surrogates, become U+FFFD.
|
|
9504
|
+
*/
|
|
9505
|
+
function escapeHtml(value) {
|
|
9506
|
+
let out = "";
|
|
9507
|
+
for (const ch of value) {
|
|
9508
|
+
const c = ch.codePointAt(0);
|
|
9509
|
+
if (ch === "&") out += "&";
|
|
9510
|
+
else if (ch === "<") out += "<";
|
|
9511
|
+
else if (ch === ">") out += ">";
|
|
9512
|
+
else if (ch === "\"") out += """;
|
|
9513
|
+
else if (ch === "'") out += "'";
|
|
9514
|
+
else if (ch === "\r") out += " ";
|
|
9515
|
+
else if (ch === " " || ch === "\n") out += ch;
|
|
9516
|
+
else if (c < 32 || c === 127 || c === 65534 || c === 65535 || c >= 55296 && c <= 57343) out += "�";
|
|
9517
|
+
else out += ch;
|
|
7930
9518
|
}
|
|
7931
|
-
return out
|
|
9519
|
+
return out;
|
|
7932
9520
|
}
|
|
7933
9521
|
//#endregion
|
|
7934
9522
|
//#region converters/hdf-to-ecs/typescript/converter.ts
|
|
@@ -8017,15 +9605,17 @@ function buildEvent(req, baseline, docTimestamp, tool, generator, component, con
|
|
|
8017
9605
|
}
|
|
8018
9606
|
/**
|
|
8019
9607
|
* Surface override provenance into ECS labels.* (keyword bag): the disposition
|
|
8020
|
-
* plus the
|
|
8021
|
-
*
|
|
9608
|
+
* plus the fields of the override that GOVERNS — the most recently applied
|
|
9609
|
+
* non-expired one, resolved by appliedAt rather than by array position, because
|
|
9610
|
+
* this repo's writers append and so put the newest override last. Returns
|
|
9611
|
+
* undefined when the requirement carries no disposition or overrides.
|
|
8022
9612
|
*/
|
|
8023
9613
|
function buildLabels(req) {
|
|
8024
9614
|
const labels = {};
|
|
8025
|
-
setIf(labels, "hdf_disposition",
|
|
9615
|
+
setIf(labels, "hdf_disposition", disposition(req));
|
|
8026
9616
|
const overrides = asArr(req.statusOverrides);
|
|
8027
9617
|
if (overrides && overrides.length > 0) {
|
|
8028
|
-
const ov =
|
|
9618
|
+
const ov = governingOverrideOf(overrides);
|
|
8029
9619
|
if (ov) {
|
|
8030
9620
|
setIf(labels, "hdf_override_type", getStr(ov, "type"));
|
|
8031
9621
|
setIf(labels, "hdf_override_reason", getStr(ov, "reason"));
|
|
@@ -8389,13 +9979,13 @@ function complianceStatusCaption(statusID) {
|
|
|
8389
9979
|
}
|
|
8390
9980
|
}
|
|
8391
9981
|
function overrideComment(req) {
|
|
8392
|
-
const
|
|
9982
|
+
const disp = disposition(req);
|
|
8393
9983
|
const overrides = asArr(req.statusOverrides) ?? [];
|
|
8394
|
-
if (
|
|
9984
|
+
if (disp === "" && overrides.length === 0) return "";
|
|
8395
9985
|
let reason = "";
|
|
8396
|
-
if (overrides.length > 0) reason = getStr(
|
|
8397
|
-
if (
|
|
8398
|
-
if (
|
|
9986
|
+
if (overrides.length > 0) reason = getStr(governingOverrideOf(overrides), "reason");
|
|
9987
|
+
if (disp !== "" && reason !== "") return `${disp}: ${reason}`;
|
|
9988
|
+
if (disp !== "") return disp;
|
|
8399
9989
|
return reason;
|
|
8400
9990
|
}
|
|
8401
9991
|
function buildMetadata$1(tool, generator, converterVersion) {
|
|
@@ -8616,7 +10206,7 @@ function floatMetric(f) {
|
|
|
8616
10206
|
* byte-identical with the Go implementation via canonicalize + stringifyLine.
|
|
8617
10207
|
*/
|
|
8618
10208
|
const ASFF_SCHEMA_VERSION = "2018-10-08";
|
|
8619
|
-
const CONVERTER_NAME = "hdf-to-asff";
|
|
10209
|
+
const CONVERTER_NAME$1 = "hdf-to-asff";
|
|
8620
10210
|
const PLACEHOLDER_REGION = "us-east-1";
|
|
8621
10211
|
const PLACEHOLDER_ACCOUNT_ID = "000000000000";
|
|
8622
10212
|
const DEFAULT_PARTITION = "aws";
|
|
@@ -8624,7 +10214,7 @@ const MAX_TITLE = 256;
|
|
|
8624
10214
|
const MAX_DESCRIPTION = 1024;
|
|
8625
10215
|
const EPOCH_SENTINEL = "1970-01-01T00:00:00Z";
|
|
8626
10216
|
function convertHdfToAsff(input, converterVersion = "0.1.0") {
|
|
8627
|
-
const name = CONVERTER_NAME;
|
|
10217
|
+
const name = CONVERTER_NAME$1;
|
|
8628
10218
|
validateInputSize$1(input, name);
|
|
8629
10219
|
const doc = parseHdf(input);
|
|
8630
10220
|
const baselines = asArr(doc.baselines);
|
|
@@ -8670,7 +10260,7 @@ function recoverAccountID(doc) {
|
|
|
8670
10260
|
return PLACEHOLDER_ACCOUNT_ID;
|
|
8671
10261
|
}
|
|
8672
10262
|
function buildFinding(req, ctx) {
|
|
8673
|
-
const controlID = firstNonEmpty(getStr(req, "id"), getStr(req, "title"), CONVERTER_NAME);
|
|
10263
|
+
const controlID = firstNonEmpty(getStr(req, "id"), getStr(req, "title"), CONVERTER_NAME$1);
|
|
8674
10264
|
const st = statusOf(req);
|
|
8675
10265
|
const title = getStr(req, "title") || controlID;
|
|
8676
10266
|
const desc = defaultDescription(req) || title;
|
|
@@ -9161,7 +10751,7 @@ function convertHdfToXml(input) {
|
|
|
9161
10751
|
}
|
|
9162
10752
|
//#endregion
|
|
9163
10753
|
//#region converters/gitlab-to-hdf/typescript/converter.ts
|
|
9164
|
-
function buildRefs$
|
|
10754
|
+
function buildRefs$7(vuln) {
|
|
9165
10755
|
const refs = [];
|
|
9166
10756
|
const seen = /* @__PURE__ */ new Set();
|
|
9167
10757
|
const appendUrl = (url) => {
|
|
@@ -9201,7 +10791,7 @@ function scanTypeLabel(scanType) {
|
|
|
9201
10791
|
api_fuzzing: "API Fuzzing"
|
|
9202
10792
|
}[scanType] ?? scanType.toUpperCase();
|
|
9203
10793
|
}
|
|
9204
|
-
function buildNistTags(identifiers) {
|
|
10794
|
+
function buildNistTags$1(identifiers) {
|
|
9205
10795
|
const seen = /* @__PURE__ */ new Set();
|
|
9206
10796
|
const controls = [];
|
|
9207
10797
|
for (const id of identifiers) if (id.type === "cwe" && id.value) {
|
|
@@ -9226,14 +10816,14 @@ function collectIdentifierTags(identifiers) {
|
|
|
9226
10816
|
}
|
|
9227
10817
|
return result;
|
|
9228
10818
|
}
|
|
9229
|
-
function buildSourceLocation$
|
|
10819
|
+
function buildSourceLocation$3(location) {
|
|
9230
10820
|
if (!location?.file) return void 0;
|
|
9231
10821
|
const sourceLocation = { ref: location.file };
|
|
9232
10822
|
const line = location.start_line ?? location.end_line;
|
|
9233
10823
|
if (line != null) sourceLocation.line = line;
|
|
9234
10824
|
return sourceLocation;
|
|
9235
10825
|
}
|
|
9236
|
-
function buildCodeDesc$
|
|
10826
|
+
function buildCodeDesc$6(scanType, location) {
|
|
9237
10827
|
if (!location) return "";
|
|
9238
10828
|
switch (scanType) {
|
|
9239
10829
|
case "sast":
|
|
@@ -9295,7 +10885,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
9295
10885
|
const requirements = [];
|
|
9296
10886
|
for (const vuln of limitedVulns) {
|
|
9297
10887
|
const identifiers = vuln.identifiers ?? [];
|
|
9298
|
-
const nistTags = buildNistTags(identifiers);
|
|
10888
|
+
const nistTags = buildNistTags$1(identifiers);
|
|
9299
10889
|
const cciTags = nistToCci(nistTags);
|
|
9300
10890
|
const idTags = collectIdentifierTags(identifiers);
|
|
9301
10891
|
const extras = {};
|
|
@@ -9322,7 +10912,7 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
9322
10912
|
}
|
|
9323
10913
|
const result = {
|
|
9324
10914
|
status: ResultStatus.Failed,
|
|
9325
|
-
codeDesc: buildCodeDesc$
|
|
10915
|
+
codeDesc: buildCodeDesc$6(scanType, vuln.location),
|
|
9326
10916
|
startTime: startTime ? parseTimestamp(startTime) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z") : /* @__PURE__ */ new Date("0001-01-01T00:00:00Z")
|
|
9327
10917
|
};
|
|
9328
10918
|
const impact = gitlabSeverityToImpact(vuln.severity ?? "Unknown");
|
|
@@ -9336,11 +10926,11 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
9336
10926
|
descriptions,
|
|
9337
10927
|
verificationMethod: VerificationMethodEnum.Automated
|
|
9338
10928
|
};
|
|
9339
|
-
const refs = buildRefs$
|
|
10929
|
+
const refs = buildRefs$7(vuln);
|
|
9340
10930
|
if (refs) req.refs = refs;
|
|
9341
10931
|
const controlType = deriveControlTypeFromTags(nistTags);
|
|
9342
10932
|
if (controlType !== void 0) req.controlType = controlType;
|
|
9343
|
-
const sourceLocation = buildSourceLocation$
|
|
10933
|
+
const sourceLocation = buildSourceLocation$3(vuln.location);
|
|
9344
10934
|
if (sourceLocation) req.sourceLocation = sourceLocation;
|
|
9345
10935
|
requirements.push(req);
|
|
9346
10936
|
}
|
|
@@ -9379,6 +10969,678 @@ async function convertGitlabToHdf(input, converterVersion = "1.0.0") {
|
|
|
9379
10969
|
return serializeHdf(hdf);
|
|
9380
10970
|
}
|
|
9381
10971
|
//#endregion
|
|
10972
|
+
//#region converters/gitlab-vulnerabilities-to-hdf/typescript/converter.ts
|
|
10973
|
+
/**
|
|
10974
|
+
* GitLab Vulnerability Report (GraphQL) to HDF Results — TypeScript twin of
|
|
10975
|
+
* go/converter.go. Converts the envelope the gitlab-vulnerabilities fetcher
|
|
10976
|
+
* assembles for one project into HDF, keeping triage state lossless.
|
|
10977
|
+
*
|
|
10978
|
+
* Status is raw-primary: results[].status is what the scanner last reported on
|
|
10979
|
+
* the default branch, and every human triage decision rides as an attributed,
|
|
10980
|
+
* expiring Status_Override so effectiveStatus, effectiveImpact and disposition
|
|
10981
|
+
* are computed through the shared ladder rather than assigned.
|
|
10982
|
+
*/
|
|
10983
|
+
const CONVERTER_NAME = "gitlab-vulnerabilities";
|
|
10984
|
+
const SOURCE_NAME = "GitLab Vulnerability Report";
|
|
10985
|
+
const GENERATOR_NAME = "gitlab-vulnerabilities-to-hdf";
|
|
10986
|
+
function filtersActive(f) {
|
|
10987
|
+
return !!f && ((f.states?.length ?? 0) > 0 || (f.reportTypes?.length ?? 0) > 0);
|
|
10988
|
+
}
|
|
10989
|
+
function describeFilters(f) {
|
|
10990
|
+
const parts = [];
|
|
10991
|
+
if (f.states?.length) parts.push(`states ${f.states.join(", ")}`);
|
|
10992
|
+
if (f.reportTypes?.length) parts.push(`report types ${f.reportTypes.join(", ")}`);
|
|
10993
|
+
return parts.join("; ");
|
|
10994
|
+
}
|
|
10995
|
+
function parseInput(input) {
|
|
10996
|
+
if (!input || input.trim() === "") throw new Error(`${CONVERTER_NAME}: empty input`);
|
|
10997
|
+
validateInputSize$1(input, CONVERTER_NAME);
|
|
10998
|
+
let env;
|
|
10999
|
+
try {
|
|
11000
|
+
env = parseJSON(input);
|
|
11001
|
+
} catch (e) {
|
|
11002
|
+
throw new Error(`${CONVERTER_NAME}: invalid envelope JSON: ${e.message}`);
|
|
11003
|
+
}
|
|
11004
|
+
if (typeof env !== "object" || env === null || Array.isArray(env)) throw new Error(`${CONVERTER_NAME}: input is not a Vulnerability Report envelope (project.fullPath missing)`);
|
|
11005
|
+
if (!env.project?.fullPath) throw new Error(`${CONVERTER_NAME}: input is not a Vulnerability Report envelope (project.fullPath missing)`);
|
|
11006
|
+
if (!env.metadata?.enterprise) throw new Error(`${CONVERTER_NAME}: GitLab Community Edition has no Vulnerability Report; use the gitlab (CI artifact) converter instead`);
|
|
11007
|
+
if (!Array.isArray(env.vulnerabilities)) throw new Error(`${CONVERTER_NAME}: envelope has no vulnerabilities array; an absent or null list is a malformed envelope, not an empty report`);
|
|
11008
|
+
return env;
|
|
11009
|
+
}
|
|
11010
|
+
function sortedSectionKeys(pipeline) {
|
|
11011
|
+
const summary = pipeline.securityReportSummary ?? {};
|
|
11012
|
+
return Object.keys(summary).filter((k) => summary[k] != null).sort();
|
|
11013
|
+
}
|
|
11014
|
+
function scansOf(pipeline, key) {
|
|
11015
|
+
return pipeline.securityReportSummary?.[key]?.scans?.nodes ?? [];
|
|
11016
|
+
}
|
|
11017
|
+
function ingestionError(env) {
|
|
11018
|
+
const project = env.project;
|
|
11019
|
+
if (project.vulnerabilityStatistic != null) return void 0;
|
|
11020
|
+
const pipeline = project.latestDefaultBranchPipeline;
|
|
11021
|
+
if (!pipeline) return /* @__PURE__ */ new Error(`${CONVERTER_NAME}: ${project.fullPath} has no default-branch pipeline; no security scanner has run`);
|
|
11022
|
+
const created = [];
|
|
11023
|
+
const failed = [];
|
|
11024
|
+
let succeeded = false;
|
|
11025
|
+
for (const reportType of sortedSectionKeys(pipeline)) for (const scan of scansOf(pipeline, reportType)) switch (scan.status ?? "") {
|
|
11026
|
+
case "SUCCEEDED":
|
|
11027
|
+
succeeded = true;
|
|
11028
|
+
break;
|
|
11029
|
+
case "CREATED":
|
|
11030
|
+
case "PREPARING":
|
|
11031
|
+
created.push(reportType);
|
|
11032
|
+
break;
|
|
11033
|
+
case "":
|
|
11034
|
+
failed.push(`${reportType}: status unknown`);
|
|
11035
|
+
break;
|
|
11036
|
+
default: {
|
|
11037
|
+
let desc = `${reportType}: ${scan.status}`;
|
|
11038
|
+
const notes = [...scan.errors ?? [], ...scan.warnings ?? []];
|
|
11039
|
+
if (notes.length > 0) desc += ` (${notes.join("; ")})`;
|
|
11040
|
+
failed.push(desc);
|
|
11041
|
+
}
|
|
11042
|
+
}
|
|
11043
|
+
if (succeeded) return void 0;
|
|
11044
|
+
if (failed.length > 0) return /* @__PURE__ */ new Error(`${CONVERTER_NAME}: scanner jobs on the default branch of ${project.fullPath} produced no usable report: ${failed.join(", ")}`);
|
|
11045
|
+
if (created.length > 0) return /* @__PURE__ */ new Error(`${CONVERTER_NAME}: security reports for ${project.fullPath} were produced but never ingested (${created.join(", ")} stuck at CREATED); the Vulnerability Report requires GitLab Ultimate`);
|
|
11046
|
+
return /* @__PURE__ */ new Error(`${CONVERTER_NAME}: no security scanner has run on the default branch of ${project.fullPath}`);
|
|
11047
|
+
}
|
|
11048
|
+
function ingestedReportTypes(pipeline) {
|
|
11049
|
+
if (!pipeline) return [];
|
|
11050
|
+
return sortedSectionKeys(pipeline).filter((key) => scansOf(pipeline, key).some((s) => s.status === "SUCCEEDED"));
|
|
11051
|
+
}
|
|
11052
|
+
async function convertGitlabVulnerabilitiesToHdf(input, converterVersion = "1.0.0") {
|
|
11053
|
+
const env = parseInput(input);
|
|
11054
|
+
const fetchedAt = parseTimestamp(env.fetchedAt ?? "");
|
|
11055
|
+
if (!fetchedAt) throw new Error(`${CONVERTER_NAME}: envelope fetchedAt "${env.fetchedAt ?? ""}" is not a valid timestamp`);
|
|
11056
|
+
const resultsChecksum = await inputChecksum(input);
|
|
11057
|
+
const project = env.project;
|
|
11058
|
+
const vulnerabilities = env.vulnerabilities ?? [];
|
|
11059
|
+
let baselines;
|
|
11060
|
+
if (vulnerabilities.length === 0) {
|
|
11061
|
+
const err = ingestionError(env);
|
|
11062
|
+
if (err) throw err;
|
|
11063
|
+
baselines = filtersActive(env.filters) ? filteredNoMatchBaselines(env.filters, project, fetchedAt, resultsChecksum) : noFindingsBaselines(project, fetchedAt, resultsChecksum);
|
|
11064
|
+
} else baselines = findingBaselines(vulnerabilities, project, fetchedAt, resultsChecksum);
|
|
11065
|
+
return buildHdfResults({
|
|
11066
|
+
generatorName: GENERATOR_NAME,
|
|
11067
|
+
converterVersion,
|
|
11068
|
+
toolName: SOURCE_NAME,
|
|
11069
|
+
toolVersion: env.metadata?.version,
|
|
11070
|
+
baselines,
|
|
11071
|
+
components: [buildComponent$2(project)],
|
|
11072
|
+
timestamp: fetchedAt
|
|
11073
|
+
});
|
|
11074
|
+
}
|
|
11075
|
+
function findingBaselines(vulnerabilities, project, fetchedAt, checksum) {
|
|
11076
|
+
const limited = limitArrayWithWarning(vulnerabilities, "vulnerability");
|
|
11077
|
+
const byType = /* @__PURE__ */ new Map();
|
|
11078
|
+
const scanners = /* @__PURE__ */ new Map();
|
|
11079
|
+
for (const v of limited) {
|
|
11080
|
+
const reportType = v.reportType || "GENERIC";
|
|
11081
|
+
if (!byType.has(reportType)) {
|
|
11082
|
+
byType.set(reportType, []);
|
|
11083
|
+
scanners.set(reportType, /* @__PURE__ */ new Set());
|
|
11084
|
+
}
|
|
11085
|
+
if (v.scanner?.name) scanners.get(reportType).add(v.scanner.name);
|
|
11086
|
+
byType.get(reportType).push(convertVulnerability(v, project, fetchedAt));
|
|
11087
|
+
}
|
|
11088
|
+
return [...byType.keys()].sort().map((reportType) => {
|
|
11089
|
+
const title = reportTypeLabel(reportType);
|
|
11090
|
+
const names = [...scanners.get(reportType)].sort();
|
|
11091
|
+
return {
|
|
11092
|
+
name: `${SOURCE_NAME}: ${title}`,
|
|
11093
|
+
title,
|
|
11094
|
+
summary: names.length > 0 ? `Scanner: ${names.join(", ")}` : "Scanner: unknown",
|
|
11095
|
+
requirements: byType.get(reportType),
|
|
11096
|
+
resultsChecksum: checksum
|
|
11097
|
+
};
|
|
11098
|
+
});
|
|
11099
|
+
}
|
|
11100
|
+
function noFindingsBaselines(project, fetchedAt, checksum) {
|
|
11101
|
+
let types = ingestedReportTypes(project.latestDefaultBranchPipeline);
|
|
11102
|
+
if (types.length === 0) types = ["generic"];
|
|
11103
|
+
const requirements = types.map((key) => {
|
|
11104
|
+
const reportType = summaryKeyToReportType(key);
|
|
11105
|
+
return buildNoFindingsRequirement(`gitlab-vulnerability-report-no-findings-${reportType.toLowerCase()}`, `GitLab Vulnerability Report for ${project.fullPath} lists zero ${reportTypeLabel(reportType)} vulnerabilities after a successful scan ingestion.`, fetchedAt);
|
|
11106
|
+
});
|
|
11107
|
+
return [{
|
|
11108
|
+
name: SOURCE_NAME,
|
|
11109
|
+
title: "No findings",
|
|
11110
|
+
summary: `Ingested scanner types: ${types.join(", ")}`,
|
|
11111
|
+
requirements,
|
|
11112
|
+
resultsChecksum: checksum
|
|
11113
|
+
}];
|
|
11114
|
+
}
|
|
11115
|
+
/**
|
|
11116
|
+
* Renders "nothing matched what you asked for", deliberately not the
|
|
11117
|
+
* no-findings wording: this describes the selection, not the project.
|
|
11118
|
+
*/
|
|
11119
|
+
function filteredNoMatchBaselines(filters, project, fetchedAt, checksum) {
|
|
11120
|
+
const selection = describeFilters(filters);
|
|
11121
|
+
const req = buildNoFindingsRequirement("gitlab-vulnerability-report-no-match", `No vulnerability in the GitLab Vulnerability Report for ${project.fullPath} matches the requested selection (${selection}). This describes the selection only, not the project's overall posture.`, fetchedAt);
|
|
11122
|
+
req.tags["gitlab/filtered"] = true;
|
|
11123
|
+
req.tags["gitlab/filterStates"] = filters.states ?? [];
|
|
11124
|
+
req.tags["gitlab/filterReportTypes"] = filters.reportTypes ?? [];
|
|
11125
|
+
return [{
|
|
11126
|
+
name: SOURCE_NAME,
|
|
11127
|
+
title: "No matching findings",
|
|
11128
|
+
summary: `Filtered selection: ${selection}`,
|
|
11129
|
+
requirements: [req],
|
|
11130
|
+
resultsChecksum: checksum
|
|
11131
|
+
}];
|
|
11132
|
+
}
|
|
11133
|
+
function summaryKeyToReportType(key) {
|
|
11134
|
+
return key.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
11135
|
+
}
|
|
11136
|
+
const REPORT_TYPE_LABELS = {
|
|
11137
|
+
SAST: "SAST",
|
|
11138
|
+
DEPENDENCY_SCANNING: "Dependency Scanning",
|
|
11139
|
+
CONTAINER_SCANNING: "Container Scanning",
|
|
11140
|
+
CONTAINER_SCANNING_FOR_REGISTRY: "Container Scanning for Registry",
|
|
11141
|
+
DAST: "DAST",
|
|
11142
|
+
SECRET_DETECTION: "Secret Detection",
|
|
11143
|
+
COVERAGE_FUZZING: "Coverage Fuzzing",
|
|
11144
|
+
API_FUZZING: "API Fuzzing",
|
|
11145
|
+
CLUSTER_IMAGE_SCANNING: "Cluster Image Scanning",
|
|
11146
|
+
GENERIC: "Generic"
|
|
11147
|
+
};
|
|
11148
|
+
function reportTypeLabel(reportType) {
|
|
11149
|
+
return REPORT_TYPE_LABELS[reportType] ?? reportType;
|
|
11150
|
+
}
|
|
11151
|
+
function buildComponent$2(project) {
|
|
11152
|
+
const c = {
|
|
11153
|
+
name: project.fullPath ?? "",
|
|
11154
|
+
type: TargetType.Repository,
|
|
11155
|
+
labels: {
|
|
11156
|
+
"gitlab/project-id": project.id ?? "",
|
|
11157
|
+
"gitlab/full-path": project.fullPath ?? ""
|
|
11158
|
+
}
|
|
11159
|
+
};
|
|
11160
|
+
if (project.webUrl) c.url = project.webUrl;
|
|
11161
|
+
if (project.repository?.rootRef) c.branch = project.repository.rootRef;
|
|
11162
|
+
if (project.latestDefaultBranchPipeline?.sha) c.commit = project.latestDefaultBranchPipeline.sha;
|
|
11163
|
+
return c;
|
|
11164
|
+
}
|
|
11165
|
+
function convertVulnerability(v, project, fetchedAt) {
|
|
11166
|
+
const { original, current } = severityPair(v);
|
|
11167
|
+
const code = buildVulnCode$1(v);
|
|
11168
|
+
const tags = buildTags$3(v, project, original, current);
|
|
11169
|
+
const result = {
|
|
11170
|
+
status: rawStatus(v),
|
|
11171
|
+
codeDesc: buildCodeDesc$5(v),
|
|
11172
|
+
startTime: resultStartTime(v, fetchedAt)
|
|
11173
|
+
};
|
|
11174
|
+
const req = {
|
|
11175
|
+
id: v.uuid ?? "",
|
|
11176
|
+
title: v.title ?? "",
|
|
11177
|
+
descriptions: buildDescriptions(v),
|
|
11178
|
+
impact: severityToImpact$1(original),
|
|
11179
|
+
tags,
|
|
11180
|
+
code,
|
|
11181
|
+
results: [result]
|
|
11182
|
+
};
|
|
11183
|
+
const refs = buildRefs$6(v);
|
|
11184
|
+
if (refs.length > 0) req.refs = refs;
|
|
11185
|
+
const sourceLocation = buildSourceLocation$2(v.location);
|
|
11186
|
+
if (sourceLocation) req.sourceLocation = sourceLocation;
|
|
11187
|
+
const cvss = buildCvss$2(v);
|
|
11188
|
+
if (cvss.length > 0) req.cvss = cvss;
|
|
11189
|
+
const verificationMethod = deriveVerificationMethod(code);
|
|
11190
|
+
if (verificationMethod !== void 0) req.verificationMethod = verificationMethod;
|
|
11191
|
+
const controlType = deriveControlTypeFromTags(tags.nist ?? []);
|
|
11192
|
+
if (controlType !== void 0) req.controlType = controlType;
|
|
11193
|
+
const overrides = [...buildStatusOverrides(v, fetchedAt), ...buildSeverityOverrides(v, fetchedAt)];
|
|
11194
|
+
if (overrides.length > 0) {
|
|
11195
|
+
sortMostRecentFirst(overrides);
|
|
11196
|
+
req.statusOverrides = overrides;
|
|
11197
|
+
applyEffectivePosture(req, fetchedAt);
|
|
11198
|
+
}
|
|
11199
|
+
return req;
|
|
11200
|
+
}
|
|
11201
|
+
function rawStatus(v) {
|
|
11202
|
+
if (v.state === "RESOLVED" && v.resolvedOnDefaultBranch) return ResultStatus.Passed;
|
|
11203
|
+
return ResultStatus.Failed;
|
|
11204
|
+
}
|
|
11205
|
+
/**
|
|
11206
|
+
* GitLab's `severity` already reflects human overrides, so the original comes
|
|
11207
|
+
* from the earliest one. The connection's order is not part of GitLab's
|
|
11208
|
+
* contract, so the earliest is found by timestamp rather than by position.
|
|
11209
|
+
*/
|
|
11210
|
+
function severityPair(v) {
|
|
11211
|
+
const current = v.severity ?? "";
|
|
11212
|
+
let original = current;
|
|
11213
|
+
let earliest;
|
|
11214
|
+
let found = false;
|
|
11215
|
+
for (const o of v.severityOverrides?.nodes ?? []) {
|
|
11216
|
+
if (!o.originalSeverity) continue;
|
|
11217
|
+
const at = parseTimestamp(o.createdAt ?? "");
|
|
11218
|
+
if (!found) {
|
|
11219
|
+
original = o.originalSeverity;
|
|
11220
|
+
earliest = at;
|
|
11221
|
+
found = true;
|
|
11222
|
+
continue;
|
|
11223
|
+
}
|
|
11224
|
+
if (!at) continue;
|
|
11225
|
+
if (!earliest || at < earliest) {
|
|
11226
|
+
original = o.originalSeverity;
|
|
11227
|
+
earliest = at;
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
return {
|
|
11231
|
+
original,
|
|
11232
|
+
current
|
|
11233
|
+
};
|
|
11234
|
+
}
|
|
11235
|
+
function resultStartTime(v, fetchedAt) {
|
|
11236
|
+
return firstTime(v.latestDetectedPipeline?.createdAt, v.detectedAt) ?? fetchedAt;
|
|
11237
|
+
}
|
|
11238
|
+
/**
|
|
11239
|
+
* Renders the node as canonical JSON. Echoing the source layout instead would
|
|
11240
|
+
* make the twins disagree: one language's parser spells 3.0 where the other
|
|
11241
|
+
* spells 3.
|
|
11242
|
+
*/
|
|
11243
|
+
function buildVulnCode$1(v) {
|
|
11244
|
+
return JSON.stringify(sortKeysDeep(v), null, 2);
|
|
11245
|
+
}
|
|
11246
|
+
function sortKeysDeep(value) {
|
|
11247
|
+
if (Array.isArray(value)) return value.map(sortKeysDeep);
|
|
11248
|
+
if (value !== null && typeof value === "object") {
|
|
11249
|
+
const out = {};
|
|
11250
|
+
for (const key of Object.keys(value).sort()) out[key] = sortKeysDeep(value[key]);
|
|
11251
|
+
return out;
|
|
11252
|
+
}
|
|
11253
|
+
return value;
|
|
11254
|
+
}
|
|
11255
|
+
function buildDescriptions(v) {
|
|
11256
|
+
const descriptions = [{
|
|
11257
|
+
label: "default",
|
|
11258
|
+
data: v.description || v.title || ""
|
|
11259
|
+
}];
|
|
11260
|
+
if (v.solution) descriptions.push({
|
|
11261
|
+
label: "fix",
|
|
11262
|
+
data: v.solution
|
|
11263
|
+
});
|
|
11264
|
+
return descriptions;
|
|
11265
|
+
}
|
|
11266
|
+
function buildTags$3(v, project, original, current) {
|
|
11267
|
+
const nist = buildNistTags(v.identifiers ?? []);
|
|
11268
|
+
const tags = buildNistCciTags(nist, nistToCci(nist), collectIdentifierExtras(v.identifiers ?? []));
|
|
11269
|
+
markUnratedSeverity(tags, original);
|
|
11270
|
+
const transitions = v.stateTransitions?.nodes ?? [];
|
|
11271
|
+
tags["gitlab/id"] = v.id ?? "";
|
|
11272
|
+
tags["gitlab/uuid"] = v.uuid ?? "";
|
|
11273
|
+
tags["gitlab/webUrl"] = v.webUrl ?? "";
|
|
11274
|
+
tags["gitlab/project"] = project.fullPath ?? "";
|
|
11275
|
+
tags["gitlab/reportType"] = v.reportType ?? "";
|
|
11276
|
+
tags["gitlab/severity"] = current;
|
|
11277
|
+
tags["gitlab/originalSeverity"] = original;
|
|
11278
|
+
tags["gitlab/state"] = v.state ?? "";
|
|
11279
|
+
tags["gitlab/dismissalReason"] = v.dismissalReason ?? null;
|
|
11280
|
+
tags["gitlab/stateComment"] = v.stateComment ?? null;
|
|
11281
|
+
tags["gitlab/falsePositive"] = v.falsePositive ?? false;
|
|
11282
|
+
tags["gitlab/presentOnDefaultBranch"] = v.presentOnDefaultBranch ?? false;
|
|
11283
|
+
tags["gitlab/resolvedOnDefaultBranch"] = v.resolvedOnDefaultBranch ?? false;
|
|
11284
|
+
tags["gitlab/detectedAt"] = v.detectedAt ?? "";
|
|
11285
|
+
tags["gitlab/updatedAt"] = v.updatedAt ?? "";
|
|
11286
|
+
tags["gitlab/confirmedAt"] = v.confirmedAt ?? null;
|
|
11287
|
+
tags["gitlab/confirmedBy"] = userTag(v.confirmedBy);
|
|
11288
|
+
tags["gitlab/dismissedAt"] = v.dismissedAt ?? null;
|
|
11289
|
+
tags["gitlab/dismissedBy"] = userTag(v.dismissedBy);
|
|
11290
|
+
tags["gitlab/resolvedAt"] = v.resolvedAt ?? null;
|
|
11291
|
+
tags["gitlab/resolvedBy"] = userTag(v.resolvedBy);
|
|
11292
|
+
tags["gitlab/initialDetectedPipeline"] = pipelineTag(v.initialDetectedPipeline);
|
|
11293
|
+
tags["gitlab/latestDetectedPipeline"] = pipelineTag(v.latestDetectedPipeline);
|
|
11294
|
+
tags["gitlab/scanner"] = v.scanner ? {
|
|
11295
|
+
name: v.scanner.name ?? "",
|
|
11296
|
+
vendor: v.scanner.vendor ?? "",
|
|
11297
|
+
externalId: v.scanner.externalId ?? ""
|
|
11298
|
+
} : null;
|
|
11299
|
+
tags["gitlab/primaryIdentifier"] = v.primaryIdentifier ? {
|
|
11300
|
+
externalType: v.primaryIdentifier.externalType ?? "",
|
|
11301
|
+
externalId: v.primaryIdentifier.externalId ?? "",
|
|
11302
|
+
name: v.primaryIdentifier.name ?? ""
|
|
11303
|
+
} : null;
|
|
11304
|
+
tags["gitlab/stateTransitionCount"] = transitions.length;
|
|
11305
|
+
if (transitions.length > 0 && transitions[transitions.length - 1].toState !== v.state) tags["gitlab/stateHistoryInconsistent"] = true;
|
|
11306
|
+
return tags;
|
|
11307
|
+
}
|
|
11308
|
+
function userTag(u) {
|
|
11309
|
+
return u ? u.username ?? "" : null;
|
|
11310
|
+
}
|
|
11311
|
+
function pipelineTag(p) {
|
|
11312
|
+
if (!p) return null;
|
|
11313
|
+
return {
|
|
11314
|
+
iid: p.iid ?? "",
|
|
11315
|
+
sha: p.sha ?? "",
|
|
11316
|
+
ref: p.ref ?? "",
|
|
11317
|
+
createdAt: p.createdAt ?? ""
|
|
11318
|
+
};
|
|
11319
|
+
}
|
|
11320
|
+
function buildNistTags(identifiers) {
|
|
11321
|
+
return mapCWEToNIST(identifiers.filter((id) => (id.externalType ?? "").toLowerCase() === "cwe" && !!id.externalId).map((id) => id.externalId), DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
11322
|
+
}
|
|
11323
|
+
/**
|
|
11324
|
+
* Maps every vendor assessment GitLab attached to the finding, in the order
|
|
11325
|
+
* GitLab returned them. GitLab's own severity band is preferred over one
|
|
11326
|
+
* derived from the score.
|
|
11327
|
+
*/
|
|
11328
|
+
function buildCvss$2(v) {
|
|
11329
|
+
const entries = v.cvss ?? [];
|
|
11330
|
+
if (entries.length === 0) return [];
|
|
11331
|
+
const source = cveIdentifier$1(v.identifiers ?? []);
|
|
11332
|
+
return entries.map((e) => {
|
|
11333
|
+
const entry = buildCvss$4({
|
|
11334
|
+
version: cvssVersion$1(e),
|
|
11335
|
+
baseScore: e.baseScore,
|
|
11336
|
+
baseVector: e.vector,
|
|
11337
|
+
source
|
|
11338
|
+
});
|
|
11339
|
+
const sev = cvssSeverity$1(e.severity);
|
|
11340
|
+
if (sev) entry.baseSeverity = sev;
|
|
11341
|
+
if (typeof e.overallScore === "number" && Number.isFinite(e.overallScore)) {
|
|
11342
|
+
entry.computedScore = e.overallScore;
|
|
11343
|
+
entry.computedSeverity = cvssSeverityFromScore(e.overallScore);
|
|
11344
|
+
}
|
|
11345
|
+
return entry;
|
|
11346
|
+
});
|
|
11347
|
+
}
|
|
11348
|
+
/**
|
|
11349
|
+
* Prefers the vector's own prefix and falls back to GitLab's numeric version
|
|
11350
|
+
* field, which is a Float and so cannot name 3.1 exactly without the ladder.
|
|
11351
|
+
*/
|
|
11352
|
+
function cvssVersion$1(e) {
|
|
11353
|
+
const n = e.version ?? 0;
|
|
11354
|
+
let fallback = Version.The31;
|
|
11355
|
+
if (n >= 4) fallback = Version.The40;
|
|
11356
|
+
else if (n >= 3.1) fallback = Version.The31;
|
|
11357
|
+
else if (n >= 3) fallback = Version.The30;
|
|
11358
|
+
else if (n >= 2) fallback = Version.The20;
|
|
11359
|
+
return cvssVersionFromVector(e.vector, fallback);
|
|
11360
|
+
}
|
|
11361
|
+
/**
|
|
11362
|
+
* Maps GitLab's CvssSeverity enum; an unknown value yields undefined so the
|
|
11363
|
+
* score-derived band stands instead of an invented one.
|
|
11364
|
+
*/
|
|
11365
|
+
function cvssSeverity$1(s) {
|
|
11366
|
+
switch ((s ?? "").toUpperCase()) {
|
|
11367
|
+
case "CRITICAL": return CVSSSeverity.Critical;
|
|
11368
|
+
case "HIGH": return CVSSSeverity.High;
|
|
11369
|
+
case "MEDIUM": return CVSSSeverity.Medium;
|
|
11370
|
+
case "LOW": return CVSSSeverity.Low;
|
|
11371
|
+
case "NONE": return CVSSSeverity.None;
|
|
11372
|
+
default: return;
|
|
11373
|
+
}
|
|
11374
|
+
}
|
|
11375
|
+
/**
|
|
11376
|
+
* What the schema's cvss[].source names: the advisory the scores belong to,
|
|
11377
|
+
* not the scanner that reported them.
|
|
11378
|
+
*/
|
|
11379
|
+
function cveIdentifier$1(identifiers) {
|
|
11380
|
+
for (const id of identifiers) if ((id.externalType ?? "").toLowerCase() === "cve" && id.externalId) return id.externalId;
|
|
11381
|
+
return "";
|
|
11382
|
+
}
|
|
11383
|
+
function collectIdentifierExtras(identifiers) {
|
|
11384
|
+
const grouped = {};
|
|
11385
|
+
for (const id of identifiers) {
|
|
11386
|
+
if (!id.externalType || !id.externalId) continue;
|
|
11387
|
+
const key = id.externalType.toLowerCase();
|
|
11388
|
+
(grouped[key] ??= []).push(id.externalId);
|
|
11389
|
+
}
|
|
11390
|
+
return grouped;
|
|
11391
|
+
}
|
|
11392
|
+
function buildRefs$6(v) {
|
|
11393
|
+
const refs = [];
|
|
11394
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11395
|
+
const add = (url) => {
|
|
11396
|
+
if (!url || seen.has(url)) return;
|
|
11397
|
+
seen.add(url);
|
|
11398
|
+
refs.push({ url });
|
|
11399
|
+
};
|
|
11400
|
+
add(v.webUrl);
|
|
11401
|
+
for (const id of v.identifiers ?? []) add(id.url);
|
|
11402
|
+
for (const link of v.links ?? []) add(link.url);
|
|
11403
|
+
return refs;
|
|
11404
|
+
}
|
|
11405
|
+
function parseLine(s) {
|
|
11406
|
+
if (!s) return void 0;
|
|
11407
|
+
const n = parseInt(s, 10);
|
|
11408
|
+
return Number.isNaN(n) || String(n) !== s ? void 0 : n;
|
|
11409
|
+
}
|
|
11410
|
+
function buildSourceLocation$2(loc) {
|
|
11411
|
+
if (!loc || !loc.file) return void 0;
|
|
11412
|
+
const sl = { ref: loc.file };
|
|
11413
|
+
const line = parseLine(loc.startLine) ?? parseLine(loc.endLine);
|
|
11414
|
+
if (line !== void 0) sl.line = line;
|
|
11415
|
+
return sl;
|
|
11416
|
+
}
|
|
11417
|
+
function packageLabel(d) {
|
|
11418
|
+
return packageInstanceId$2("", d?.package?.name ?? "", d?.version ?? "");
|
|
11419
|
+
}
|
|
11420
|
+
function buildCodeDesc$5(v) {
|
|
11421
|
+
const loc = v.location;
|
|
11422
|
+
if (!loc) return `Report type: ${v.reportType ?? ""}`;
|
|
11423
|
+
const parts = [];
|
|
11424
|
+
switch (loc.__typename) {
|
|
11425
|
+
case "VulnerabilityLocationGeneric":
|
|
11426
|
+
if (loc.description) parts.push(`Location: ${loc.description}`);
|
|
11427
|
+
break;
|
|
11428
|
+
case "VulnerabilityLocationSast":
|
|
11429
|
+
case "VulnerabilityLocationSecretDetection":
|
|
11430
|
+
case "VulnerabilityLocationCoverageFuzzing":
|
|
11431
|
+
if (loc.file) parts.push(`File: ${loc.file}`);
|
|
11432
|
+
if (loc.startLine) {
|
|
11433
|
+
if (loc.endLine && loc.endLine !== loc.startLine) parts.push(`Line: ${loc.startLine}-${loc.endLine}`);
|
|
11434
|
+
else parts.push(`Line: ${loc.startLine}`);
|
|
11435
|
+
}
|
|
11436
|
+
if (loc.vulnerableClass) parts.push(`Class: ${loc.vulnerableClass}`);
|
|
11437
|
+
if (loc.vulnerableMethod) parts.push(`Method: ${loc.vulnerableMethod}`);
|
|
11438
|
+
break;
|
|
11439
|
+
case "VulnerabilityLocationDependencyScanning": {
|
|
11440
|
+
if (loc.file) parts.push(`File: ${loc.file}`);
|
|
11441
|
+
const pkg = packageLabel(loc.dependency);
|
|
11442
|
+
if (pkg) parts.push(`Package: ${pkg}`);
|
|
11443
|
+
break;
|
|
11444
|
+
}
|
|
11445
|
+
case "VulnerabilityLocationContainerScanning":
|
|
11446
|
+
case "VulnerabilityLocationClusterImageScanning": {
|
|
11447
|
+
if (loc.image) parts.push(`Image: ${loc.image}`);
|
|
11448
|
+
if (loc.operatingSystem) parts.push(`OS: ${loc.operatingSystem}`);
|
|
11449
|
+
const pkg = packageLabel(loc.dependency);
|
|
11450
|
+
if (pkg) parts.push(`Package: ${pkg}`);
|
|
11451
|
+
break;
|
|
11452
|
+
}
|
|
11453
|
+
case "VulnerabilityLocationDast":
|
|
11454
|
+
if (loc.hostname) parts.push(`URL: ${loc.hostname}${loc.path ?? ""}`);
|
|
11455
|
+
if (loc.requestMethod) parts.push(`Method: ${loc.requestMethod}`);
|
|
11456
|
+
if (loc.param) parts.push(`Param: ${loc.param}`);
|
|
11457
|
+
break;
|
|
11458
|
+
default: return `Location: ${JSON.stringify(loc)}`;
|
|
11459
|
+
}
|
|
11460
|
+
return parts.length > 0 ? parts.join(" | ") : `Report type: ${v.reportType ?? ""}`;
|
|
11461
|
+
}
|
|
11462
|
+
function decisionFor(toState, dismissalReason, scannerResolved) {
|
|
11463
|
+
switch (toState) {
|
|
11464
|
+
case "DISMISSED": return dismissalDecision(dismissalReason ?? "");
|
|
11465
|
+
case "RESOLVED":
|
|
11466
|
+
if (scannerResolved) return void 0;
|
|
11467
|
+
return {
|
|
11468
|
+
overrideType: OverrideType.Attestation,
|
|
11469
|
+
status: ResultStatus.Passed,
|
|
11470
|
+
defaultReason: "Marked resolved in GitLab"
|
|
11471
|
+
};
|
|
11472
|
+
default: return;
|
|
11473
|
+
}
|
|
11474
|
+
}
|
|
11475
|
+
function dismissalDecision(reason) {
|
|
11476
|
+
switch (reason) {
|
|
11477
|
+
case "FALSE_POSITIVE": return {
|
|
11478
|
+
overrideType: OverrideType.FalsePositive,
|
|
11479
|
+
status: ResultStatus.NotApplicable,
|
|
11480
|
+
defaultReason: "Dismissed as FALSE_POSITIVE in GitLab"
|
|
11481
|
+
};
|
|
11482
|
+
case "ACCEPTABLE_RISK": return {
|
|
11483
|
+
overrideType: OverrideType.Waiver,
|
|
11484
|
+
status: ResultStatus.Passed,
|
|
11485
|
+
defaultReason: "Dismissed as ACCEPTABLE_RISK in GitLab"
|
|
11486
|
+
};
|
|
11487
|
+
case "MITIGATING_CONTROL": return {
|
|
11488
|
+
overrideType: OverrideType.Waiver,
|
|
11489
|
+
status: ResultStatus.Passed,
|
|
11490
|
+
justification: Justification.InlineMitigationsAlreadyExist,
|
|
11491
|
+
defaultReason: "Dismissed as MITIGATING_CONTROL in GitLab"
|
|
11492
|
+
};
|
|
11493
|
+
case "USED_IN_TESTS": return {
|
|
11494
|
+
overrideType: OverrideType.Waiver,
|
|
11495
|
+
status: ResultStatus.NotApplicable,
|
|
11496
|
+
justification: Justification.VulnerableCodeNotInExecutePath,
|
|
11497
|
+
defaultReason: "Dismissed as USED_IN_TESTS in GitLab"
|
|
11498
|
+
};
|
|
11499
|
+
case "NOT_APPLICABLE":
|
|
11500
|
+
case "": return {
|
|
11501
|
+
overrideType: OverrideType.Waiver,
|
|
11502
|
+
status: ResultStatus.NotApplicable,
|
|
11503
|
+
defaultReason: "Dismissed as NOT_APPLICABLE in GitLab"
|
|
11504
|
+
};
|
|
11505
|
+
default: return {
|
|
11506
|
+
overrideType: OverrideType.Waiver,
|
|
11507
|
+
status: ResultStatus.NotApplicable,
|
|
11508
|
+
defaultReason: `Dismissed as ${reason} in GitLab`
|
|
11509
|
+
};
|
|
11510
|
+
}
|
|
11511
|
+
}
|
|
11512
|
+
function buildStatusOverrides(v, fetchedAt) {
|
|
11513
|
+
const scannerResolved = v.state === "RESOLVED" && !!v.resolvedOnDefaultBranch;
|
|
11514
|
+
const overrides = [];
|
|
11515
|
+
const transitions = v.stateTransitions?.nodes ?? [];
|
|
11516
|
+
for (const tr of transitions) {
|
|
11517
|
+
if (tr.toState === "DETECTED") {
|
|
11518
|
+
const at = firstTime(tr.createdAt);
|
|
11519
|
+
if (overrides.length > 0 && at) overrides[overrides.length - 1].expiresAt = at;
|
|
11520
|
+
continue;
|
|
11521
|
+
}
|
|
11522
|
+
const d = decisionFor(tr.toState, tr.dismissalReason, scannerResolved);
|
|
11523
|
+
if (!d) continue;
|
|
11524
|
+
const appliedAt = firstTime(tr.createdAt, v.updatedAt, v.detectedAt) ?? fetchedAt;
|
|
11525
|
+
overrides.push(newOverride(v, d, appliedAt, tr.comment, tr.author));
|
|
11526
|
+
}
|
|
11527
|
+
if (transitions.length > 0 && transitions[transitions.length - 1].toState === v.state) return overrides;
|
|
11528
|
+
closeOpenOverrides(overrides, firstTime(v.updatedAt) ?? fetchedAt);
|
|
11529
|
+
const d = decisionFor(v.state, v.dismissalReason, scannerResolved);
|
|
11530
|
+
if (d) {
|
|
11531
|
+
const { at, by } = currentDecisionProvenance(v);
|
|
11532
|
+
overrides.push(newOverride(v, d, at ?? fetchedAt, v.stateComment, by));
|
|
11533
|
+
}
|
|
11534
|
+
return overrides;
|
|
11535
|
+
}
|
|
11536
|
+
/**
|
|
11537
|
+
* Brings every expiry back to `at`, never earlier than the override's own
|
|
11538
|
+
* appliedAt so the record stays internally consistent.
|
|
11539
|
+
*/
|
|
11540
|
+
function closeOpenOverrides(overrides, at) {
|
|
11541
|
+
for (const o of overrides) {
|
|
11542
|
+
if (o.expiresAt <= at) continue;
|
|
11543
|
+
o.expiresAt = at < o.appliedAt ? o.appliedAt : at;
|
|
11544
|
+
}
|
|
11545
|
+
}
|
|
11546
|
+
function currentDecisionProvenance(v) {
|
|
11547
|
+
switch (v.state) {
|
|
11548
|
+
case "DISMISSED": return {
|
|
11549
|
+
at: firstTime(v.dismissedAt, v.updatedAt, v.detectedAt),
|
|
11550
|
+
by: v.dismissedBy
|
|
11551
|
+
};
|
|
11552
|
+
case "RESOLVED": return {
|
|
11553
|
+
at: firstTime(v.resolvedAt, v.updatedAt, v.detectedAt),
|
|
11554
|
+
by: v.resolvedBy
|
|
11555
|
+
};
|
|
11556
|
+
default: return {
|
|
11557
|
+
at: firstTime(v.updatedAt, v.detectedAt),
|
|
11558
|
+
by: void 0
|
|
11559
|
+
};
|
|
11560
|
+
}
|
|
11561
|
+
}
|
|
11562
|
+
function firstTime(...candidates) {
|
|
11563
|
+
for (const c of candidates) {
|
|
11564
|
+
if (!c) continue;
|
|
11565
|
+
const ts = parseTimestamp(c);
|
|
11566
|
+
if (ts) return ts;
|
|
11567
|
+
}
|
|
11568
|
+
}
|
|
11569
|
+
function newOverride(v, d, appliedAt, comment, author) {
|
|
11570
|
+
const o = {
|
|
11571
|
+
type: d.overrideType,
|
|
11572
|
+
status: d.status,
|
|
11573
|
+
reason: comment && comment.trim() !== "" ? comment : d.defaultReason,
|
|
11574
|
+
appliedBy: identityFor$3(author),
|
|
11575
|
+
appliedAt,
|
|
11576
|
+
expiresAt: defaultOverrideExpiry(appliedAt)
|
|
11577
|
+
};
|
|
11578
|
+
if (d.justification !== void 0) o.justification = d.justification;
|
|
11579
|
+
if (v.webUrl) o.externalReferences = [{
|
|
11580
|
+
sourceName: SOURCE_NAME,
|
|
11581
|
+
href: v.webUrl
|
|
11582
|
+
}];
|
|
11583
|
+
return o;
|
|
11584
|
+
}
|
|
11585
|
+
function identityFor$3(u) {
|
|
11586
|
+
if (u) {
|
|
11587
|
+
const description = u.name ? u.name : void 0;
|
|
11588
|
+
if (u.publicEmail) {
|
|
11589
|
+
const id = {
|
|
11590
|
+
type: IdentityType.Email,
|
|
11591
|
+
identifier: u.publicEmail
|
|
11592
|
+
};
|
|
11593
|
+
if (description) id.description = description;
|
|
11594
|
+
return id;
|
|
11595
|
+
}
|
|
11596
|
+
if (u.username) {
|
|
11597
|
+
const id = {
|
|
11598
|
+
type: IdentityType.Username,
|
|
11599
|
+
identifier: u.username
|
|
11600
|
+
};
|
|
11601
|
+
if (description) id.description = description;
|
|
11602
|
+
return id;
|
|
11603
|
+
}
|
|
11604
|
+
}
|
|
11605
|
+
return {
|
|
11606
|
+
type: IdentityType.System,
|
|
11607
|
+
identifier: "gitlab",
|
|
11608
|
+
description: "GitLab automatic state change"
|
|
11609
|
+
};
|
|
11610
|
+
}
|
|
11611
|
+
function buildSeverityOverrides(v, fetchedAt) {
|
|
11612
|
+
return (v.severityOverrides?.nodes ?? []).map((sc) => {
|
|
11613
|
+
const appliedAt = firstTime(sc.createdAt, v.updatedAt, v.detectedAt) ?? fetchedAt;
|
|
11614
|
+
const o = {
|
|
11615
|
+
type: OverrideType.RiskAdjustment,
|
|
11616
|
+
impact: { value: severityToImpact$1(sc.newSeverity ?? "") },
|
|
11617
|
+
reason: `Severity changed from ${sc.originalSeverity ?? ""} to ${sc.newSeverity ?? ""} in GitLab`,
|
|
11618
|
+
appliedBy: identityFor$3(sc.author),
|
|
11619
|
+
appliedAt,
|
|
11620
|
+
expiresAt: defaultOverrideExpiry(appliedAt)
|
|
11621
|
+
};
|
|
11622
|
+
if (v.webUrl) o.externalReferences = [{
|
|
11623
|
+
sourceName: SOURCE_NAME,
|
|
11624
|
+
href: v.webUrl
|
|
11625
|
+
}];
|
|
11626
|
+
return o;
|
|
11627
|
+
});
|
|
11628
|
+
}
|
|
11629
|
+
function sortMostRecentFirst(overrides) {
|
|
11630
|
+
overrides.reverse();
|
|
11631
|
+
overrides.sort((a, b) => b.appliedAt.getTime() - a.appliedAt.getTime());
|
|
11632
|
+
}
|
|
11633
|
+
function applyEffectivePosture(req, fetchedAt) {
|
|
11634
|
+
const overrides = req.statusOverrides ?? [];
|
|
11635
|
+
const ref = fetchedAt.toISOString();
|
|
11636
|
+
const inputs = requirementStatusInput(req).overrides ?? [];
|
|
11637
|
+
req.effectiveStatus = computeEffectiveStatus(requirementStatusInput(req), ref);
|
|
11638
|
+
const impactIdx = governingOverrideIndex(inputs, (i) => overrides[i]?.impact !== void 0, ref);
|
|
11639
|
+
if (impactIdx >= 0) req.effectiveImpact = overrides[impactIdx].impact.value;
|
|
11640
|
+
const dispositionIdx = governingOverrideIndex(inputs, (i) => overrides[i]?.status !== void 0 || overrides[i]?.impact !== void 0, ref);
|
|
11641
|
+
if (dispositionIdx >= 0) req.disposition = overrides[dispositionIdx].type;
|
|
11642
|
+
}
|
|
11643
|
+
//#endregion
|
|
9382
11644
|
//#region converters/trivy-to-hdf/typescript/converter.ts
|
|
9383
11645
|
const BASELINE_NAME = "Trivy Scan";
|
|
9384
11646
|
const STATIC_CCI = nistToCci(DEFAULT_STATIC_ANALYSIS_NIST_TAGS);
|
|
@@ -9613,19 +11875,19 @@ function buildCvssEntries$4(m) {
|
|
|
9613
11875
|
for (const src of Object.keys(m).sort()) {
|
|
9614
11876
|
const c = m[src];
|
|
9615
11877
|
if (!c) continue;
|
|
9616
|
-
if (c.V2Vector || c.V2Score !== void 0) entries.push(buildCvss$
|
|
11878
|
+
if (c.V2Vector || c.V2Score !== void 0) entries.push(buildCvss$4({
|
|
9617
11879
|
version: Version.The20,
|
|
9618
11880
|
baseVector: c.V2Vector,
|
|
9619
11881
|
baseScore: c.V2Score,
|
|
9620
11882
|
source: src
|
|
9621
11883
|
}));
|
|
9622
|
-
if (c.V3Vector || c.V3Score !== void 0) entries.push(buildCvss$
|
|
11884
|
+
if (c.V3Vector || c.V3Score !== void 0) entries.push(buildCvss$4({
|
|
9623
11885
|
version: cvssVersionFromVector(c.V3Vector, Version.The31),
|
|
9624
11886
|
baseVector: c.V3Vector,
|
|
9625
11887
|
baseScore: c.V3Score,
|
|
9626
11888
|
source: src
|
|
9627
11889
|
}));
|
|
9628
|
-
if (c.V40Vector || c.V40Score !== void 0) entries.push(buildCvss$
|
|
11890
|
+
if (c.V40Vector || c.V40Score !== void 0) entries.push(buildCvss$4({
|
|
9629
11891
|
version: Version.The40,
|
|
9630
11892
|
baseVector: c.V40Vector,
|
|
9631
11893
|
baseScore: c.V40Score,
|
|
@@ -10482,7 +12744,7 @@ function buildCvss$1(vuln) {
|
|
|
10482
12744
|
const hasScore = typeof vuln.cvss === "number" && Number.isFinite(vuln.cvss);
|
|
10483
12745
|
const hasVector = !!vuln.vector;
|
|
10484
12746
|
if (!hasScore && !hasVector) return void 0;
|
|
10485
|
-
return buildCvss$
|
|
12747
|
+
return buildCvss$4({
|
|
10486
12748
|
version: cvssVersionFromVector(vuln.vector),
|
|
10487
12749
|
baseScore: hasScore ? vuln.cvss : void 0,
|
|
10488
12750
|
baseVector: vuln.vector,
|
|
@@ -10568,6 +12830,17 @@ function buildAffectedPackage(vuln, packageTypes, distro) {
|
|
|
10568
12830
|
});
|
|
10569
12831
|
}
|
|
10570
12832
|
/**
|
|
12833
|
+
* The per-result instance identity for a Twistlock vulnerability: the vulnerable
|
|
12834
|
+
* package, which is what distinguishes the several entries that report the same
|
|
12835
|
+
* CVE. Twistlock's output carries no purl, so the identity is name@version —
|
|
12836
|
+
* grype's fallback form. Falls back to the name alone when a version is absent,
|
|
12837
|
+
* and returns undefined when there is no package name at all, in which case the
|
|
12838
|
+
* caller leaves the fields unset rather than inventing an id.
|
|
12839
|
+
*/
|
|
12840
|
+
function packageInstanceId(vuln) {
|
|
12841
|
+
return packageInstanceId$2("", vuln.packageName ?? "", vuln.packageVersion ?? "") || void 0;
|
|
12842
|
+
}
|
|
12843
|
+
/**
|
|
10571
12844
|
* Indexes packageName → packageType from the result-level packages array.
|
|
10572
12845
|
*/
|
|
10573
12846
|
function buildPackageTypeIndex(pkgs) {
|
|
@@ -10646,9 +12919,14 @@ function buildRequirement$9(vuln, packageTypes, distro) {
|
|
|
10646
12919
|
data: vuln.description
|
|
10647
12920
|
}];
|
|
10648
12921
|
const startTime = (vuln.discoveredDate ? parseTimestamp(vuln.discoveredDate) : null) ?? /* @__PURE__ */ new Date("0001-01-01T00:00:00Z");
|
|
12922
|
+
const instanceId = packageInstanceId(vuln);
|
|
10649
12923
|
const results = [createResult(ResultStatus.Failed, formatMessage$2(vuln), {
|
|
10650
12924
|
codeDesc: formatCodeDesc$2(vuln),
|
|
10651
|
-
startTime
|
|
12925
|
+
startTime,
|
|
12926
|
+
...instanceId ? {
|
|
12927
|
+
resource: "package",
|
|
12928
|
+
resourceId: instanceId
|
|
12929
|
+
} : {}
|
|
10652
12930
|
})];
|
|
10653
12931
|
const req = createRequirement(vuln.id, vuln.id, descriptions, twistlockSeverityToImpact(vuln.severity), results, { tags });
|
|
10654
12932
|
const controlType = deriveControlTypeFromTags(nist);
|
|
@@ -10797,12 +13075,12 @@ function resolveCVE(vuln) {
|
|
|
10797
13075
|
function buildCvssEntries$3(vuln) {
|
|
10798
13076
|
const source = resolveCVE(vuln);
|
|
10799
13077
|
const entries = [];
|
|
10800
|
-
if (typeof vuln.cvssV3BaseScore === "number") entries.push(buildCvss$
|
|
13078
|
+
if (typeof vuln.cvssV3BaseScore === "number") entries.push(buildCvss$4({
|
|
10801
13079
|
version: Version.The31,
|
|
10802
13080
|
baseScore: vuln.cvssV3BaseScore,
|
|
10803
13081
|
source
|
|
10804
13082
|
}));
|
|
10805
|
-
if (typeof vuln.cvssV2BaseScore === "number") entries.push(buildCvss$
|
|
13083
|
+
if (typeof vuln.cvssV2BaseScore === "number") entries.push(buildCvss$4({
|
|
10806
13084
|
version: Version.The20,
|
|
10807
13085
|
baseScore: vuln.cvssV2BaseScore,
|
|
10808
13086
|
source
|
|
@@ -11020,14 +13298,14 @@ function buildCvssEntries$2(cves) {
|
|
|
11020
13298
|
const out = [];
|
|
11021
13299
|
for (const cve of cves ?? []) {
|
|
11022
13300
|
const v3 = parseCvssField(cve.cvss_v3);
|
|
11023
|
-
if (v3.score !== void 0 || v3.vector !== "") out.push(buildCvss$
|
|
13301
|
+
if (v3.score !== void 0 || v3.vector !== "") out.push(buildCvss$4({
|
|
11024
13302
|
version: cvssVersionFromVector(v3.vector),
|
|
11025
13303
|
baseScore: v3.score,
|
|
11026
13304
|
baseVector: v3.vector,
|
|
11027
13305
|
source: cve.cve
|
|
11028
13306
|
}));
|
|
11029
13307
|
const v2 = parseCvssField(cve.cvss_v2);
|
|
11030
|
-
if (v2.score !== void 0 || v2.vector !== "") out.push(buildCvss$
|
|
13308
|
+
if (v2.score !== void 0 || v2.vector !== "") out.push(buildCvss$4({
|
|
11031
13309
|
version: cvssVersionFromString("2.0"),
|
|
11032
13310
|
baseScore: v2.score,
|
|
11033
13311
|
baseVector: v2.vector,
|
|
@@ -11285,13 +13563,13 @@ function extractCVEs(vuln) {
|
|
|
11285
13563
|
* its score still drives impact.
|
|
11286
13564
|
*/
|
|
11287
13565
|
function buildCvssEntries$1(vuln) {
|
|
11288
|
-
if (vuln.vectors_v3) return [buildCvss$
|
|
13566
|
+
if (vuln.vectors_v3) return [buildCvss$4({
|
|
11289
13567
|
version: cvssVersionFromVector(vuln.vectors_v3),
|
|
11290
13568
|
baseScore: vuln.score_v3 > 0 ? vuln.score_v3 : void 0,
|
|
11291
13569
|
baseVector: vuln.vectors_v3,
|
|
11292
13570
|
source: "NeuVector"
|
|
11293
13571
|
})];
|
|
11294
|
-
if (vuln.vectors) return [buildCvss$
|
|
13572
|
+
if (vuln.vectors) return [buildCvss$4({
|
|
11295
13573
|
version: cvssVersionFromString("2.0"),
|
|
11296
13574
|
baseScore: vuln.score > 0 ? vuln.score : void 0,
|
|
11297
13575
|
baseVector: vuln.vectors,
|
|
@@ -11316,6 +13594,30 @@ function vulnID(vuln) {
|
|
|
11316
13594
|
return `${vuln.name}/${vuln.package_name}/${vuln.package_version}`;
|
|
11317
13595
|
}
|
|
11318
13596
|
/**
|
|
13597
|
+
* The per-result instance identity for a NeuVector finding, as a (resource kind,
|
|
13598
|
+
* id) pair. file_name is the discriminator — the only field separating the two
|
|
13599
|
+
* braces@3.0.2 entries that both report CVE-2024-4068 — but NeuVector populates
|
|
13600
|
+
* it on a minority of entries. An entry without one names no file, so it
|
|
13601
|
+
* identifies the vulnerable package instead (the identity
|
|
13602
|
+
* name/package_name/package_version already keys the requirement on) rather than
|
|
13603
|
+
* claiming a 'file' resource with nothing to point at. With neither, the id is ''
|
|
13604
|
+
* and the caller leaves the fields unset.
|
|
13605
|
+
*/
|
|
13606
|
+
function vulnInstance(vuln) {
|
|
13607
|
+
if (vuln.file_name) return {
|
|
13608
|
+
resource: "file",
|
|
13609
|
+
id: vuln.file_name
|
|
13610
|
+
};
|
|
13611
|
+
const id = packageInstanceId$2("", vuln.package_name ?? "", vuln.package_version ?? "");
|
|
13612
|
+
return id ? {
|
|
13613
|
+
resource: "package",
|
|
13614
|
+
id
|
|
13615
|
+
} : {
|
|
13616
|
+
resource: "",
|
|
13617
|
+
id: ""
|
|
13618
|
+
};
|
|
13619
|
+
}
|
|
13620
|
+
/**
|
|
11319
13621
|
* Generates a human-readable title for the vulnerability.
|
|
11320
13622
|
*/
|
|
11321
13623
|
function vulnTitle(vuln) {
|
|
@@ -11394,9 +13696,14 @@ function buildRequirement$6(vuln, scanTime, ml) {
|
|
|
11394
13696
|
label: "default",
|
|
11395
13697
|
data: vuln.description
|
|
11396
13698
|
}];
|
|
13699
|
+
const instance = vulnInstance(vuln);
|
|
11397
13700
|
const results = [createResult(ResultStatus.Failed, vulnMessage(vuln), {
|
|
11398
13701
|
codeDesc: buildCodeDesc$3(vuln),
|
|
11399
|
-
startTime: scanTime
|
|
13702
|
+
startTime: scanTime,
|
|
13703
|
+
...instance.id ? {
|
|
13704
|
+
resource: instance.resource,
|
|
13705
|
+
resourceId: instance.id
|
|
13706
|
+
} : {}
|
|
11400
13707
|
})];
|
|
11401
13708
|
const req = createRequirement(vulnID(vuln), vulnTitle(vuln), descriptions, getImpact$4(vuln), results, { tags });
|
|
11402
13709
|
req.code = JSON.stringify(vuln, null, 2);
|
|
@@ -11939,7 +14246,7 @@ function buildRefs$1(rec) {
|
|
|
11939
14246
|
function buildCvssEntries(rec) {
|
|
11940
14247
|
const score = parseCvssScore(rec.CVSS);
|
|
11941
14248
|
if (score === void 0) return void 0;
|
|
11942
|
-
return [buildCvss$
|
|
14249
|
+
return [buildCvss$4({
|
|
11943
14250
|
version: cvssVersionFromString(void 0),
|
|
11944
14251
|
baseScore: score,
|
|
11945
14252
|
source: rec["CVE ID"] || void 0
|
|
@@ -12132,7 +14439,7 @@ function buildNetsparkerCvss(classification) {
|
|
|
12132
14439
|
const baseScore = baseScoreFromBlock(block);
|
|
12133
14440
|
const vector = block?.vector ?? "";
|
|
12134
14441
|
if (!vector && baseScore === void 0) continue;
|
|
12135
|
-
out.push(buildCvss$
|
|
14442
|
+
out.push(buildCvss$4({
|
|
12136
14443
|
version: cvssVersionFromVector(vector, elementVersion),
|
|
12137
14444
|
baseScore,
|
|
12138
14445
|
baseVector: vector
|
|
@@ -12882,14 +15189,33 @@ function formatFlawMessage(flaw) {
|
|
|
12882
15189
|
return notes.join("\n");
|
|
12883
15190
|
}
|
|
12884
15191
|
/**
|
|
15192
|
+
* A static flaw's source position — sourcefilepath+sourcefile, with :line
|
|
15193
|
+
* appended when both are present. It is both the per-result instance identity
|
|
15194
|
+
* (resourceId) and the position half of the synthesized code snippet, so the two
|
|
15195
|
+
* cannot drift apart. Returns '' for the NOT-IN-SOURCE case.
|
|
15196
|
+
*/
|
|
15197
|
+
function flawLocus(flaw) {
|
|
15198
|
+
const locus = attr(flaw, "sourcefilepath") + attr(flaw, "sourcefile");
|
|
15199
|
+
const line = attr(flaw, "line");
|
|
15200
|
+
return locus && line ? `${locus}:${line}` : locus;
|
|
15201
|
+
}
|
|
15202
|
+
/**
|
|
15203
|
+
* The per-result instance identity for an SCA finding: the component_id Veracode
|
|
15204
|
+
* assigns the third-party component. Falls back to the component's sha1 —
|
|
15205
|
+
* component_id is optional in the report and sha1 is the documented secondary
|
|
15206
|
+
* identity — and returns '' when neither is present, in which case the caller
|
|
15207
|
+
* leaves the fields unset rather than inventing an id.
|
|
15208
|
+
*/
|
|
15209
|
+
function scaInstanceId(comp) {
|
|
15210
|
+
return attr(comp, "component_id") || attr(comp, "sha1");
|
|
15211
|
+
}
|
|
15212
|
+
/**
|
|
12885
15213
|
* Synthesize a static flaw's source-context locus from its function prototype
|
|
12886
15214
|
* and source-file position. Returns '' when the flaw carries neither a prototype
|
|
12887
15215
|
* nor a source location (the NOT-IN-SOURCE case).
|
|
12888
15216
|
*/
|
|
12889
15217
|
function synthesizeFlawCode(flaw) {
|
|
12890
|
-
|
|
12891
|
-
const line = attr(flaw, "line");
|
|
12892
|
-
if (locus && line) locus += `:${line}`;
|
|
15218
|
+
const locus = flawLocus(flaw);
|
|
12893
15219
|
const proto = attr(flaw, "functionprototype");
|
|
12894
15220
|
if (proto && locus) return `${proto} at ${locus}`;
|
|
12895
15221
|
if (proto) return proto;
|
|
@@ -12997,10 +15323,17 @@ function buildCWERequirement(cat, sevLevel, impact, firstBuildDate) {
|
|
|
12997
15323
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
12998
15324
|
const results = cwes.flatMap((c) => {
|
|
12999
15325
|
const staticflaws = c.staticflaws;
|
|
13000
|
-
return ensureArray(staticflaws?.flaw).map((flaw) =>
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
15326
|
+
return ensureArray(staticflaws?.flaw).map((flaw) => {
|
|
15327
|
+
const locus = flawLocus(flaw);
|
|
15328
|
+
return createResult(ResultStatus.Failed, formatFlawMessage(flaw) || void 0, {
|
|
15329
|
+
codeDesc: formatFlawCodeDesc(flaw),
|
|
15330
|
+
startTime,
|
|
15331
|
+
...locus ? {
|
|
15332
|
+
resource: "file",
|
|
15333
|
+
resourceId: locus
|
|
15334
|
+
} : {}
|
|
15335
|
+
});
|
|
15336
|
+
});
|
|
13004
15337
|
});
|
|
13005
15338
|
const sourceRef = cwes.flatMap((c) => {
|
|
13006
15339
|
const staticflaws = c.staticflaws;
|
|
@@ -13076,7 +15409,7 @@ function buildVeracodeCvss(vuln, components) {
|
|
|
13076
15409
|
if (!scoreStr) return [];
|
|
13077
15410
|
const score = Number(scoreStr);
|
|
13078
15411
|
if (!Number.isFinite(score)) return [];
|
|
13079
|
-
return [buildCvss$
|
|
15412
|
+
return [buildCvss$4({
|
|
13080
15413
|
version: cvssVersionFromString(void 0),
|
|
13081
15414
|
baseScore: score
|
|
13082
15415
|
})];
|
|
@@ -13092,10 +15425,17 @@ function buildCVERequirement(vuln, components, firstBuildDate) {
|
|
|
13092
15425
|
const tags = buildNistCciTags(nist, cciTags, {});
|
|
13093
15426
|
markUnratedSeverity(tags, attr(vuln, "severity"));
|
|
13094
15427
|
const startTime = parseVeracodeTimestamp(firstBuildDate) ?? /* @__PURE__ */ new Date();
|
|
13095
|
-
const results = components.map((comp) =>
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
15428
|
+
const results = components.map((comp) => {
|
|
15429
|
+
const instanceId = scaInstanceId(comp);
|
|
15430
|
+
return createResult(ResultStatus.Failed, void 0, {
|
|
15431
|
+
codeDesc: formatSCACodeDesc(comp),
|
|
15432
|
+
startTime,
|
|
15433
|
+
...instanceId ? {
|
|
15434
|
+
resource: "component",
|
|
15435
|
+
resourceId: instanceId
|
|
15436
|
+
} : {}
|
|
15437
|
+
});
|
|
15438
|
+
});
|
|
13099
15439
|
const cveSummary = attr(vuln, "cve_summary");
|
|
13100
15440
|
const cveId = attr(vuln, "cve_id");
|
|
13101
15441
|
const descriptions = [{
|
|
@@ -16434,11 +18774,11 @@ function oscalStateFromStatus(status) {
|
|
|
16434
18774
|
*/
|
|
16435
18775
|
function overrideRemarks(req) {
|
|
16436
18776
|
const parts = [];
|
|
16437
|
-
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
parts.push("Override: " + String(o.type));
|
|
18777
|
+
const disposition = requirementDisposition(req);
|
|
18778
|
+
if (disposition) parts.push("Disposition: " + disposition);
|
|
18779
|
+
const o = governingOverride(req);
|
|
18780
|
+
if (o) {
|
|
18781
|
+
parts.push("Override: " + String(o.type ?? ""));
|
|
16442
18782
|
if (o.reason) parts.push("Reason: " + o.reason);
|
|
16443
18783
|
if (o.appliedBy?.identifier) parts.push("Applied by: " + o.appliedBy.identifier);
|
|
16444
18784
|
const appliedAt = hdfTime(o.appliedAt);
|
|
@@ -16533,13 +18873,14 @@ function buildRemediations(req) {
|
|
|
16533
18873
|
description: fix.data,
|
|
16534
18874
|
props: [descriptionLabelProp("fix")]
|
|
16535
18875
|
});
|
|
16536
|
-
const
|
|
16537
|
-
if (
|
|
16538
|
-
const
|
|
18876
|
+
const o = governingOverride(req);
|
|
18877
|
+
if (o) {
|
|
18878
|
+
const disp = String(o.type ?? "");
|
|
18879
|
+
const desc = o.reason || "Risk accepted via " + disp;
|
|
16539
18880
|
rems.push({
|
|
16540
18881
|
uuid: crypto.randomUUID(),
|
|
16541
18882
|
lifecycle: "accepted",
|
|
16542
|
-
title:
|
|
18883
|
+
title: disp,
|
|
16543
18884
|
description: desc
|
|
16544
18885
|
});
|
|
16545
18886
|
}
|
|
@@ -16550,9 +18891,9 @@ function buildRemediations(req) {
|
|
|
16550
18891
|
* (the field the OSCAL POA&M importer reads back). Returns '' when none applies.
|
|
16551
18892
|
*/
|
|
16552
18893
|
function riskDeadline(req) {
|
|
16553
|
-
const
|
|
16554
|
-
if (
|
|
16555
|
-
const expiresAt = hdfTime(
|
|
18894
|
+
const o = governingOverride(req);
|
|
18895
|
+
if (o) {
|
|
18896
|
+
const expiresAt = hdfTime(o.expiresAt);
|
|
16556
18897
|
if (expiresAt) return formatTimestampSeconds(expiresAt);
|
|
16557
18898
|
}
|
|
16558
18899
|
return "";
|
|
@@ -20912,7 +23253,7 @@ function toRfc3339(d) {
|
|
|
20912
23253
|
* each STIX object as an inert externalReferences[] entry: a CVE-bearing object
|
|
20913
23254
|
* attaches to the finding whose requirementId is that CVE (fanning out to every
|
|
20914
23255
|
* match), and everything else — non-CVE objects and CVEs with no matching
|
|
20915
|
-
* finding — attaches to the
|
|
23256
|
+
* finding — attaches to the document root. Each entry carries the raw STIX object
|
|
20916
23257
|
* losslessly in `document`.
|
|
20917
23258
|
*
|
|
20918
23259
|
* Informational only: authors no overrides and fabricates no status/impact (the
|
|
@@ -20944,7 +23285,7 @@ function enrichStix(resultsInput, bundleInput, opts) {
|
|
|
20944
23285
|
if (!matched) appendExternalReference(doc, buildStixRef(obj, "reference"));
|
|
20945
23286
|
}
|
|
20946
23287
|
for (const reqs of reqById.values()) for (const req of reqs) capStixExternalRefs(req, "finding");
|
|
20947
|
-
capStixExternalRefs(doc, "
|
|
23288
|
+
capStixExternalRefs(doc, "document root");
|
|
20948
23289
|
if (opts?.recomputeCvss) recomputeExploitation(bundle, reqById, opts);
|
|
20949
23290
|
return JSON.stringify(doc, null, 2);
|
|
20950
23291
|
}
|
|
@@ -21086,6 +23427,6 @@ function capStixExternalRefs(container, label) {
|
|
|
21086
23427
|
container.externalReferences = [...other, ...capped];
|
|
21087
23428
|
}
|
|
21088
23429
|
//#endregion
|
|
21089
|
-
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 };
|
|
23430
|
+
export { convertAsffToHdf, convertAwsConfigToHdf, convertBurpsuiteToHdf, convertCheckovToHdf, convertCklToHdf, convertCklbToHdf, convertConveyorToHdf, convertCsafVexToHdf, convertCyclonedxToHdf, convertCyclonedxVexToHdf, convertDbprotectToHdf, convertDefectDojoToHdf, convertDeptrackToHdf, convertFortifyToHdf, convertGitlabToHdf, convertGitlabVulnerabilitiesToHdf, convertGosecToHdf, convertGrypeToHdf, convertHadolintToHdf, convertHdfDocumentsToHtml, convertHdfToAsff, convertHdfToCkl, convertHdfToCklb, convertHdfToCsafVex, convertHdfToCsv, convertHdfToCyclonedxVex, convertHdfToEcs, convertHdfToHtml, 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, parseReportType, parseStixBundle };
|
|
21090
23431
|
|
|
21091
23432
|
//# sourceMappingURL=index.js.map
|