🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@quantakrypto/core

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@quantakrypto/core - npm Package Compare versions

Comparing version
0.8.0
to
0.9.0
+15
-0
dist/evidence.d.ts
import type { ScanResult } from "./types.js";
import type { CryptoPolicy, PolicyMapping } from "./policy.js";
import type { MandateEvaluation } from "./mandates.js";
/** Stable per-finding record for the evidence body (deterministic per commit). */

@@ -29,2 +30,10 @@ export interface EvidenceFinding {

policyMapping?: PolicyMapping;
/**
* Compliance-mandate verdicts (`--mandate`), present only when mandates were
* evaluated. DATE-PINNED for reproducibility: its `now` is stored as a plain
* `YYYY-MM-DD` (not the volatile scan timestamp) so the same scan of the same
* commit ON THE SAME DAY yields the same attestation hash, while a genuinely
* different compliance date (a passed deadline) correctly changes it.
*/
mandateMapping?: MandateEvaluation;
cbom: unknown;

@@ -58,2 +67,8 @@ attestation: {

policy?: CryptoPolicy;
/**
* Optional compliance-mandate evaluation ({@link evaluateMandates}) — adds the
* `mandateMapping` block. Date-pinned into the hashed body (see
* {@link ReadinessReport.mandateMapping}).
*/
mandate?: MandateEvaluation;
}

@@ -60,0 +75,0 @@ /**

+1
-1

@@ -1,1 +0,1 @@

{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,wBAAwB,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC;IACf,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IACnC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE;QACX,+EAA+E;QAC/E,WAAW,EAAE,MAAM,CAAC;QACpB;;;;WAIG;QACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB;;;WAGG;QACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,+EAA+E;QAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,6EAA6E;QAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH;AAeD,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE,sBAA2B,GAChC,eAAe,CA2CjB;AAED,mDAAmD;AACnD,MAAM,WAAW,qBAAqB;IACpC,oFAAoF;IACpF,KAAK,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,qBAAqB,CA6BpF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACjD;AAED,+FAA+F;AAC/F,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,eAAe,CAAC,CAgB1B"}
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"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"}

@@ -57,2 +57,11 @@ /**

const policyMapping = opts.policy ? buildPolicyMapping(result.findings, opts.policy) : undefined;
// Compliance mandates: attest the dated verdicts too. The evaluation is a pure
// function of (findings, date), and the findings are already hashed — so we
// DATE-PIN its `now` to a plain `YYYY-MM-DD` (dropping the volatile scan
// timestamp) and hash that. Two runs on the same commit ON THE SAME DAY then
// reproduce; a run after a deadline has passed correctly attests a different
// status (and a different hash).
const mandateMapping = opts.mandate
? { ...opts.mandate, now: opts.mandate.now.slice(0, 10) }
: undefined;
const hashableBody = {

@@ -70,2 +79,3 @@ reportType: "quantakrypto-readiness",

...(policyMapping ? { policyMapping } : {}),
...(mandateMapping ? { mandateMapping } : {}),
};

@@ -113,2 +123,3 @@ const contentHash = "sha256:" +

...(report.policyMapping ? { policyMapping: report.policyMapping } : {}),
...(report.mandateMapping ? { mandateMapping: report.mandateMapping } : {}),
};

@@ -115,0 +126,0 @@ const computedHash = "sha256:" +

@@ -1,1 +0,1 @@

{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ISO/IEC 27001:2022 Annex A 8.24 evidence-chain export\n * (docs/compliance/iso27001-a8.24-evidence.md).\n *\n * Emits a self-describing readiness report bundling the scan result, crypto\n * inventory, and CycloneDX CBOM, plus an attestation carrying a DETERMINISTIC\n * content hash — the same scan over the same commit + config yields the same\n * hash (the volatile scan timestamp is deliberately excluded from the hashed\n * body). Signing + RFC-3161 timestamping are left to an EXTERNAL, vetted signer\n * (ADR-0004: this project performs no cryptography itself — it orchestrates a\n * signer, it does not implement one). SHA-256 here is an integrity hash (a Node\n * built-in), not an asymmetric primitive.\n *\n * Honesty boundary: this artifact is EVIDENCE for A.8.24, not the control. The\n * organization still owns the cryptography policy, key management, and the\n * conformance judgment. A clean scan is the absence of detected candidates, not\n * proof of quantum-safety (qScan is lexical). See docs/COMPLIANCE.md §3.\n */\nimport { createHash } from \"node:crypto\";\n\nimport type { ScanResult } from \"./types.js\";\nimport { toCbom } from \"./cbom.js\";\nimport { VERSION } from \"./version.js\";\nimport { buildPolicyMapping } from \"./policy.js\";\nimport type { CryptoPolicy, PolicyMapping } from \"./policy.js\";\n\n/** Stable per-finding record for the evidence body (deterministic per commit). */\nexport interface EvidenceFinding {\n ruleId: string;\n algorithm?: string;\n severity: string;\n hndl: boolean;\n file: string;\n line: number;\n}\n\nexport interface ReadinessReport {\n reportType: \"quantakrypto-readiness\";\n specVersion: 1;\n subject: {\n repository: string | null;\n commit: string | null;\n scannedRoot: string;\n scanTimeUtc: string;\n };\n tool: { name: \"qScan\"; version: string };\n inventory: ScanResult[\"inventory\"];\n findings: EvidenceFinding[];\n /** §4 policy verdicts, present only when a crypto policy was supplied. */\n policyMapping?: PolicyMapping;\n cbom: unknown;\n attestation: {\n /** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */\n contentHash: string;\n /**\n * RFC-3161 / transparency-log token over `contentHash`, produced by an EXTERNAL\n * timestamper (opaque string, e.g. base64). `null` until {@link signReadinessReport}\n * runs one.\n */\n timestamp: string | null;\n /**\n * Detached signature over `contentHash`, produced by an EXTERNAL signer (opaque\n * string, e.g. base64/PEM). `null` until {@link signReadinessReport} runs one.\n */\n signature: string | null;\n /** Non-sensitive provenance label of the signer (e.g. \"openssl\", \"cosign\"). */\n signedWith?: string;\n /** Non-sensitive provenance label of the timestamper (e.g. \"openssl-ts\"). */\n timestampedWith?: string;\n };\n}\n\n/** Canonical JSON: object keys sorted recursively, so the hash is reproducible. */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n out[k] = canonicalize((value as Record<string, unknown>)[k]);\n }\n return out;\n }\n return value;\n}\n\nexport interface ReadinessReportOptions {\n /** Repository URL (e.g. from `GITHUB_REPOSITORY`); omitted → null. */\n repository?: string;\n /** Full commit SHA (e.g. from `GITHUB_SHA`); omitted → null. */\n commit?: string;\n /** Optional org cryptography policy — adds the §4 `policyMapping` verdicts. */\n policy?: CryptoPolicy;\n}\n\n/**\n * Build the A.8.24 readiness report for a scan result. The attestation's\n * `contentHash` covers everything EXCEPT the scan timestamp and the attestation\n * block itself, so re-running the same scan on the same commit is verifiable.\n */\nexport function buildReadinessReport(\n result: ScanResult,\n opts: ReadinessReportOptions = {},\n): ReadinessReport {\n const findings: EvidenceFinding[] = result.findings.map((f) => ({\n ruleId: f.ruleId,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n severity: f.severity,\n hndl: f.hndl,\n file: f.location.file,\n line: f.location.line,\n }));\n\n // The CBOM is a deterministic *view* of the (hashed) findings + inventory, but\n // its CycloneDX envelope carries a volatile timestamp/serial — so it is\n // EXCLUDED from the hashed body (its integrity follows from its hashed inputs)\n // to keep the content hash reproducible across scan runs on the same commit.\n // §4: if the org supplied a crypto policy, attest the per-finding verdicts too.\n // Deterministic (same findings + policy → same mapping), so it is hashed.\n const policyMapping = opts.policy ? buildPolicyMapping(result.findings, opts.policy) : undefined;\n\n const hashableBody = {\n reportType: \"quantakrypto-readiness\",\n specVersion: 1,\n subject: {\n repository: opts.repository ?? null,\n commit: opts.commit ?? null,\n scannedRoot: result.root,\n },\n tool: { name: \"qScan\", version: VERSION },\n inventory: result.inventory,\n findings,\n ...(policyMapping ? { policyMapping } : {}),\n };\n const contentHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n\n return {\n ...hashableBody,\n subject: { ...hashableBody.subject, scanTimeUtc: result.finishedAt },\n cbom: toCbom(result),\n attestation: { contentHash, timestamp: null, signature: null },\n } as ReadinessReport;\n}\n\n/** The result of {@link verifyReadinessReport}. */\nexport interface VerifyReadinessResult {\n /** True iff the recomputed body hash equals the hash claimed in the attestation. */\n valid: boolean;\n /** The hash recomputed over the report's CURRENT body. */\n computedHash: string;\n /** The hash claimed in the report's attestation (`attestation.contentHash`). */\n claimedHash: string;\n /** A short human reason; present only when `valid` is false. */\n reason?: string;\n}\n\n/**\n * Recompute the deterministic content hash over a readiness report's body and\n * compare it to the hash the attestation claims. Detects tampering with ANY\n * hashed field — a finding, the inventory, a policy verdict, or subject/tool\n * metadata: editing it after the fact changes the recomputed hash, so `valid`\n * becomes false.\n *\n * By construction the scan timestamp, the CBOM envelope, and the attestation\n * block itself are EXCLUDED from the hash (see {@link buildReadinessReport}), so\n * touching those does not fail verification — their integrity follows from their\n * hashed inputs. The body is reconstructed from the report's OWN stored fields\n * (including `tool.version`), so a report built by an older qScan still verifies.\n *\n * This checks the INTEGRITY hash only. It does NOT validate the detached\n * signature or RFC-3161 timestamp: those are opaque tokens from an external\n * signer (ADR-0004) and are verified with that signer's own tooling.\n */\nexport function verifyReadinessReport(report: ReadinessReport): VerifyReadinessResult {\n const hashableBody = {\n reportType: report.reportType,\n specVersion: report.specVersion,\n subject: {\n repository: report.subject.repository,\n commit: report.subject.commit,\n scannedRoot: report.subject.scannedRoot,\n },\n tool: { name: report.tool.name, version: report.tool.version },\n inventory: report.inventory,\n findings: report.findings,\n ...(report.policyMapping ? { policyMapping: report.policyMapping } : {}),\n };\n const computedHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n const claimedHash = report.attestation.contentHash;\n if (computedHash === claimedHash) {\n return { valid: true, computedHash, claimedHash };\n }\n return {\n valid: false,\n computedHash,\n claimedHash,\n reason: \"content-hash mismatch: the report body was modified after it was built\",\n };\n}\n\n/**\n * An EXTERNAL signer/timestamper the tool orchestrates. Per ADR-0004 the tool\n * implements no cryptography: it hands the payload to an operator-provided signer\n * (an `openssl`/`cosign` invocation, an RFC-3161 TSA client, …) and records what\n * comes back. `label` is a short, non-sensitive provenance string (e.g. the signer\n * program name) — NOT the full command, which may contain a key path.\n */\nexport interface EvidenceSigner {\n label: string;\n /**\n * Produce a detached signature / timestamp token (opaque string) over `payload`.\n * May be async so a future signer can shell out OR call a KMS / RFC-3161 TSA over\n * the network without foreclosing that once this contract freezes at 1.0.\n */\n sign(payload: string): string | Promise<string>;\n}\n\n/** Options for {@link signReadinessReport}: a detached-signature and/or a timestamp signer. */\nexport interface SignEvidenceOptions {\n signer?: EvidenceSigner;\n timestamper?: EvidenceSigner;\n}\n\n/**\n * Fill a readiness report's attestation with a detached signature and/or RFC-3161\n * timestamp, produced by EXTERNAL signers over the report's `contentHash`. Pure\n * orchestration: it invokes the injected signers and records their opaque output\n * plus a provenance label — it performs no cryptography itself (ADR-0004). Returns a\n * NEW report; the hashed body is untouched (attestation is excluded from the hash),\n * so signing never changes `contentHash`.\n */\nexport async function signReadinessReport(\n report: ReadinessReport,\n opts: SignEvidenceOptions,\n): Promise<ReadinessReport> {\n const payload = report.attestation.contentHash;\n const signature = opts.signer ? await opts.signer.sign(payload) : report.attestation.signature;\n const timestamp = opts.timestamper\n ? await opts.timestamper.sign(payload)\n : report.attestation.timestamp;\n return {\n ...report,\n attestation: {\n ...report.attestation,\n signature,\n timestamp,\n ...(opts.signer ? { signedWith: opts.signer.label } : {}),\n ...(opts.timestamper ? { timestampedWith: opts.timestamper.label } : {}),\n },\n };\n}\n"]}
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ISO/IEC 27001:2022 Annex A 8.24 evidence-chain export\n * (docs/compliance/iso27001-a8.24-evidence.md).\n *\n * Emits a self-describing readiness report bundling the scan result, crypto\n * inventory, and CycloneDX CBOM, plus an attestation carrying a DETERMINISTIC\n * content hash — the same scan over the same commit + config yields the same\n * hash (the volatile scan timestamp is deliberately excluded from the hashed\n * body). Signing + RFC-3161 timestamping are left to an EXTERNAL, vetted signer\n * (ADR-0004: this project performs no cryptography itself — it orchestrates a\n * signer, it does not implement one). SHA-256 here is an integrity hash (a Node\n * built-in), not an asymmetric primitive.\n *\n * Honesty boundary: this artifact is EVIDENCE for A.8.24, not the control. The\n * organization still owns the cryptography policy, key management, and the\n * conformance judgment. A clean scan is the absence of detected candidates, not\n * proof of quantum-safety (qScan is lexical). See docs/COMPLIANCE.md §3.\n */\nimport { createHash } from \"node:crypto\";\n\nimport type { ScanResult } from \"./types.js\";\nimport { toCbom } from \"./cbom.js\";\nimport { VERSION } from \"./version.js\";\nimport { buildPolicyMapping } from \"./policy.js\";\nimport type { CryptoPolicy, PolicyMapping } from \"./policy.js\";\nimport type { MandateEvaluation } from \"./mandates.js\";\n\n/** Stable per-finding record for the evidence body (deterministic per commit). */\nexport interface EvidenceFinding {\n ruleId: string;\n algorithm?: string;\n severity: string;\n hndl: boolean;\n file: string;\n line: number;\n}\n\nexport interface ReadinessReport {\n reportType: \"quantakrypto-readiness\";\n specVersion: 1;\n subject: {\n repository: string | null;\n commit: string | null;\n scannedRoot: string;\n scanTimeUtc: string;\n };\n tool: { name: \"qScan\"; version: string };\n inventory: ScanResult[\"inventory\"];\n findings: EvidenceFinding[];\n /** §4 policy verdicts, present only when a crypto policy was supplied. */\n policyMapping?: PolicyMapping;\n /**\n * Compliance-mandate verdicts (`--mandate`), present only when mandates were\n * evaluated. DATE-PINNED for reproducibility: its `now` is stored as a plain\n * `YYYY-MM-DD` (not the volatile scan timestamp) so the same scan of the same\n * commit ON THE SAME DAY yields the same attestation hash, while a genuinely\n * different compliance date (a passed deadline) correctly changes it.\n */\n mandateMapping?: MandateEvaluation;\n cbom: unknown;\n attestation: {\n /** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */\n contentHash: string;\n /**\n * RFC-3161 / transparency-log token over `contentHash`, produced by an EXTERNAL\n * timestamper (opaque string, e.g. base64). `null` until {@link signReadinessReport}\n * runs one.\n */\n timestamp: string | null;\n /**\n * Detached signature over `contentHash`, produced by an EXTERNAL signer (opaque\n * string, e.g. base64/PEM). `null` until {@link signReadinessReport} runs one.\n */\n signature: string | null;\n /** Non-sensitive provenance label of the signer (e.g. \"openssl\", \"cosign\"). */\n signedWith?: string;\n /** Non-sensitive provenance label of the timestamper (e.g. \"openssl-ts\"). */\n timestampedWith?: string;\n };\n}\n\n/** Canonical JSON: object keys sorted recursively, so the hash is reproducible. */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n out[k] = canonicalize((value as Record<string, unknown>)[k]);\n }\n return out;\n }\n return value;\n}\n\nexport interface ReadinessReportOptions {\n /** Repository URL (e.g. from `GITHUB_REPOSITORY`); omitted → null. */\n repository?: string;\n /** Full commit SHA (e.g. from `GITHUB_SHA`); omitted → null. */\n commit?: string;\n /** Optional org cryptography policy — adds the §4 `policyMapping` verdicts. */\n policy?: CryptoPolicy;\n /**\n * Optional compliance-mandate evaluation ({@link evaluateMandates}) — adds the\n * `mandateMapping` block. Date-pinned into the hashed body (see\n * {@link ReadinessReport.mandateMapping}).\n */\n mandate?: MandateEvaluation;\n}\n\n/**\n * Build the A.8.24 readiness report for a scan result. The attestation's\n * `contentHash` covers everything EXCEPT the scan timestamp and the attestation\n * block itself, so re-running the same scan on the same commit is verifiable.\n */\nexport function buildReadinessReport(\n result: ScanResult,\n opts: ReadinessReportOptions = {},\n): ReadinessReport {\n const findings: EvidenceFinding[] = result.findings.map((f) => ({\n ruleId: f.ruleId,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n severity: f.severity,\n hndl: f.hndl,\n file: f.location.file,\n line: f.location.line,\n }));\n\n // The CBOM is a deterministic *view* of the (hashed) findings + inventory, but\n // its CycloneDX envelope carries a volatile timestamp/serial — so it is\n // EXCLUDED from the hashed body (its integrity follows from its hashed inputs)\n // to keep the content hash reproducible across scan runs on the same commit.\n // §4: if the org supplied a crypto policy, attest the per-finding verdicts too.\n // Deterministic (same findings + policy → same mapping), so it is hashed.\n const policyMapping = opts.policy ? buildPolicyMapping(result.findings, opts.policy) : undefined;\n\n // Compliance mandates: attest the dated verdicts too. The evaluation is a pure\n // function of (findings, date), and the findings are already hashed — so we\n // DATE-PIN its `now` to a plain `YYYY-MM-DD` (dropping the volatile scan\n // timestamp) and hash that. Two runs on the same commit ON THE SAME DAY then\n // reproduce; a run after a deadline has passed correctly attests a different\n // status (and a different hash).\n const mandateMapping = opts.mandate\n ? { ...opts.mandate, now: opts.mandate.now.slice(0, 10) }\n : undefined;\n\n const hashableBody = {\n reportType: \"quantakrypto-readiness\",\n specVersion: 1,\n subject: {\n repository: opts.repository ?? null,\n commit: opts.commit ?? null,\n scannedRoot: result.root,\n },\n tool: { name: \"qScan\", version: VERSION },\n inventory: result.inventory,\n findings,\n ...(policyMapping ? { policyMapping } : {}),\n ...(mandateMapping ? { mandateMapping } : {}),\n };\n const contentHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n\n return {\n ...hashableBody,\n subject: { ...hashableBody.subject, scanTimeUtc: result.finishedAt },\n cbom: toCbom(result),\n attestation: { contentHash, timestamp: null, signature: null },\n } as ReadinessReport;\n}\n\n/** The result of {@link verifyReadinessReport}. */\nexport interface VerifyReadinessResult {\n /** True iff the recomputed body hash equals the hash claimed in the attestation. */\n valid: boolean;\n /** The hash recomputed over the report's CURRENT body. */\n computedHash: string;\n /** The hash claimed in the report's attestation (`attestation.contentHash`). */\n claimedHash: string;\n /** A short human reason; present only when `valid` is false. */\n reason?: string;\n}\n\n/**\n * Recompute the deterministic content hash over a readiness report's body and\n * compare it to the hash the attestation claims. Detects tampering with ANY\n * hashed field — a finding, the inventory, a policy verdict, or subject/tool\n * metadata: editing it after the fact changes the recomputed hash, so `valid`\n * becomes false.\n *\n * By construction the scan timestamp, the CBOM envelope, and the attestation\n * block itself are EXCLUDED from the hash (see {@link buildReadinessReport}), so\n * touching those does not fail verification — their integrity follows from their\n * hashed inputs. The body is reconstructed from the report's OWN stored fields\n * (including `tool.version`), so a report built by an older qScan still verifies.\n *\n * This checks the INTEGRITY hash only. It does NOT validate the detached\n * signature or RFC-3161 timestamp: those are opaque tokens from an external\n * signer (ADR-0004) and are verified with that signer's own tooling.\n */\nexport function verifyReadinessReport(report: ReadinessReport): VerifyReadinessResult {\n const hashableBody = {\n reportType: report.reportType,\n specVersion: report.specVersion,\n subject: {\n repository: report.subject.repository,\n commit: report.subject.commit,\n scannedRoot: report.subject.scannedRoot,\n },\n tool: { name: report.tool.name, version: report.tool.version },\n inventory: report.inventory,\n findings: report.findings,\n ...(report.policyMapping ? { policyMapping: report.policyMapping } : {}),\n ...(report.mandateMapping ? { mandateMapping: report.mandateMapping } : {}),\n };\n const computedHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n const claimedHash = report.attestation.contentHash;\n if (computedHash === claimedHash) {\n return { valid: true, computedHash, claimedHash };\n }\n return {\n valid: false,\n computedHash,\n claimedHash,\n reason: \"content-hash mismatch: the report body was modified after it was built\",\n };\n}\n\n/**\n * An EXTERNAL signer/timestamper the tool orchestrates. Per ADR-0004 the tool\n * implements no cryptography: it hands the payload to an operator-provided signer\n * (an `openssl`/`cosign` invocation, an RFC-3161 TSA client, …) and records what\n * comes back. `label` is a short, non-sensitive provenance string (e.g. the signer\n * program name) — NOT the full command, which may contain a key path.\n */\nexport interface EvidenceSigner {\n label: string;\n /**\n * Produce a detached signature / timestamp token (opaque string) over `payload`.\n * May be async so a future signer can shell out OR call a KMS / RFC-3161 TSA over\n * the network without foreclosing that once this contract freezes at 1.0.\n */\n sign(payload: string): string | Promise<string>;\n}\n\n/** Options for {@link signReadinessReport}: a detached-signature and/or a timestamp signer. */\nexport interface SignEvidenceOptions {\n signer?: EvidenceSigner;\n timestamper?: EvidenceSigner;\n}\n\n/**\n * Fill a readiness report's attestation with a detached signature and/or RFC-3161\n * timestamp, produced by EXTERNAL signers over the report's `contentHash`. Pure\n * orchestration: it invokes the injected signers and records their opaque output\n * plus a provenance label — it performs no cryptography itself (ADR-0004). Returns a\n * NEW report; the hashed body is untouched (attestation is excluded from the hash),\n * so signing never changes `contentHash`.\n */\nexport async function signReadinessReport(\n report: ReadinessReport,\n opts: SignEvidenceOptions,\n): Promise<ReadinessReport> {\n const payload = report.attestation.contentHash;\n const signature = opts.signer ? await opts.signer.sign(payload) : report.attestation.signature;\n const timestamp = opts.timestamper\n ? await opts.timestamper.sign(payload)\n : report.attestation.timestamp;\n return {\n ...report,\n attestation: {\n ...report.attestation,\n signature,\n timestamp,\n ...(opts.signer ? { signedWith: opts.signer.label } : {}),\n ...(opts.timestamper ? { timestampedWith: opts.timestamper.label } : {}),\n },\n };\n}\n"]}

@@ -6,3 +6,3 @@ /**

* date-blind. A mandate adds the missing dimension: named clauses with an effective
* DATE ("CNSA 2.0 disallows classical public-key crypto after 2035"). The evaluator
* DATE ("CNSA 2.0 disallows classical public-key crypto after 2033"). The evaluator
* compares each finding's algorithm against the selected mandates and today's date,

@@ -24,2 +24,3 @@ * so a finding on a prohibited family reads as `due` (every deadline still ahead),

import type { AlgorithmFamily, Finding } from "./types.js";
import type { CryptoPolicy, PolicyVerdict } from "./policy.js";
/** Which enforcement tier a clause encodes: warn (`deprecate`) or fail (`disallow`). */

@@ -94,2 +95,17 @@ export type MandateRuleTier = "deprecate" | "disallow";

citation: string;
/**
* The org cryptography policy's verdict on this algorithm family when a policy
* was composed in via {@link evaluateMandates}' `policy` argument, else null.
* Purely informational — it records the org's own stance next to the mandate's
* dated clause so a machine-readable report shows both.
*/
policyVerdict: PolicyVerdict | null;
/**
* True when the org policy EXPLICITLY permits or is transitioning this family —
* an owned, tracked decision. Acknowledged findings are exempt from the EARLY
* gates (`failNow` / `leadMonths`); a passed DISALLOW deadline (`violation`)
* still fails regardless, because an org cannot self-exempt from a dated legal
* disallow. `false` when no policy was supplied.
*/
acknowledged: boolean;
}

@@ -118,2 +134,11 @@ export interface MandateEvaluation {

hasViolation: boolean;
/** Name of the org policy composed in via `policy`, or null when none was supplied. */
policyName: string | null;
/**
* How many distinct prohibited FINDINGS the org policy explicitly acknowledged
* (family listed as `permitted` or `inTransition`) — counted per finding, not
* per verdict row, so a family prohibited by two mandates counts once, matching
* the per-finding `summary`. 0 when no policy was supplied.
*/
acknowledged: number;
}

@@ -126,4 +151,11 @@ /**

* contributes a verdict row.
*
* When an org `policy` is supplied (the `--policy` composition), every verdict
* row is annotated with the org's own `policyVerdict` and an `acknowledged` flag
* (family explicitly permitted / in-transition). Acknowledgement is purely
* additive here — it changes no status — but {@link mandateGateFails} honours it
* to keep the early gates from double-flagging crypto the org is knowingly,
* traceably managing. A passed DISALLOW deadline is never acknowledgeable away.
*/
export declare function evaluateMandates(findings: readonly Finding[], mandateIdList: readonly string[], now: Date): MandateEvaluation;
export declare function evaluateMandates(findings: readonly Finding[], mandateIdList: readonly string[], now: Date, policy?: CryptoPolicy): MandateEvaluation;
export interface MandateGateOptions {

@@ -141,4 +173,11 @@ /** Fail when a DISALLOW deadline is within this many months (early enforcement). */

* immediately.
*
* Policy composition: when a finding was `acknowledged` by the org policy
* (`--policy`), it is exempt from the EARLY gates (`failNow` / `leadMonths`) —
* the org is knowingly, traceably managing that family, so its own early
* enforcement should not re-flag it. A passed DISALLOW deadline (`violation`)
* still fails regardless: a dated legal disallow is not something an org can
* self-exempt from.
*/
export declare function mandateGateFails(ev: MandateEvaluation, opts?: MandateGateOptions): boolean;
//# sourceMappingURL=mandates.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"mandates.d.ts","sourceRoot":"","sources":["../src/mandates.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"mandates.d.ts","sourceRoot":"","sources":["../src/mandates.ts"],"names":[],"mappings":"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"}
import { PQC_STANDARDS } from "./standards.js";
import { verdictForAlgorithm } from "./policy.js";
/**

@@ -30,5 +31,8 @@ * All Shor-broken classical asymmetric families — the mandate's SCOPE. A finding

/**
* Effective dates derived from the standards source of truth
* (`PQC_STANDARDS.transitionTimeline`), so a quarterly standards update moves the
* mandate deadlines automatically (test/standards.test.ts asserts they agree).
* Effective dates derived from the standards source of truth, so a quarterly
* standards update moves the mandate deadlines automatically (test/standards.test.ts
* asserts they agree). Each regime uses its OWN dated timeline: NIST IR 8547
* disallows after 2035, while CNSA 2.0 sets its general exclusive-use milestone
* at 2033 (both deprecate after 2030) — so the two mandates carry different
* disallow years rather than sharing one.
*

@@ -40,5 +44,8 @@ * Boundary choice: "deprecate AFTER 2030" leaves the whole stated year permitted,

*/
const { deprecateAfter, disallowAfter } = PQC_STANDARDS.transitionTimeline;
const DEPRECATE_EFFECTIVE = `${deprecateAfter}-12-31`;
const DISALLOW_EFFECTIVE = `${disallowAfter}-12-31`;
const IR8547 = PQC_STANDARDS.transitionTimeline; // 2030 deprecate / 2035 disallow
const CNSA = PQC_STANDARDS.cnsaTimeline; // 2030 deprecate / 2033 disallow
const NIST_DEPRECATE_EFFECTIVE = `${IR8547.deprecateAfter}-12-31`;
const NIST_DISALLOW_EFFECTIVE = `${IR8547.disallowAfter}-12-31`;
const CNSA_DEPRECATE_EFFECTIVE = `${CNSA.deprecateAfter}-12-31`;
const CNSA_DISALLOW_EFFECTIVE = `${CNSA.disallowAfter}-12-31`;
/** The bundled mandate catalog. Keyed by `--mandate <id>`. */

@@ -54,14 +61,14 @@ export const MANDATES = {

{
clause: `CNSA 2.0 — deprecate classical PKC after ${deprecateAfter}`,
clause: `CNSA 2.0 — deprecate classical PKC after ${CNSA.deprecateAfter}`,
tier: "deprecate",
prohibits: [...PROHIBITED_FAMILIES],
effective: DEPRECATE_EFFECTIVE,
note: "Classical public-key cryptography deprecated; systems should use CNSA 2.0 PQC exclusively.",
effective: CNSA_DEPRECATE_EFFECTIVE,
note: `Classical public-key cryptography deprecated (${CNSA.deprecateAfter}: software/firmware signing exclusive-use); systems should use CNSA 2.0 PQC.`,
},
{
clause: `CNSA 2.0 — disallow classical PKC after ${disallowAfter}`,
clause: `CNSA 2.0 — disallow classical PKC after ${CNSA.disallowAfter}`,
tier: "disallow",
prohibits: [...PROHIBITED_FAMILIES],
effective: DISALLOW_EFFECTIVE,
note: "Classical public-key cryptography disallowed; the migration must be complete.",
effective: CNSA_DISALLOW_EFFECTIVE,
note: `Classical public-key cryptography disallowed (${CNSA.disallowAfter}: general NSS exclusive-use milestone); the migration must be complete.`,
},

@@ -78,14 +85,14 @@ ],

{
clause: `NIST IR 8547 — deprecate classical PKC after ${deprecateAfter}`,
clause: `NIST IR 8547 — deprecate classical PKC after ${IR8547.deprecateAfter}`,
tier: "deprecate",
prohibits: [...PROHIBITED_FAMILIES],
effective: DEPRECATE_EFFECTIVE,
note: `112-bit-security classical public-key algorithms deprecated after ${deprecateAfter}.`,
effective: NIST_DEPRECATE_EFFECTIVE,
note: `112-bit-security classical public-key algorithms deprecated after ${IR8547.deprecateAfter}.`,
},
{
clause: `NIST IR 8547 — disallow classical PKC after ${disallowAfter}`,
clause: `NIST IR 8547 — disallow classical PKC after ${IR8547.disallowAfter}`,
tier: "disallow",
prohibits: [...PROHIBITED_FAMILIES],
effective: DISALLOW_EFFECTIVE,
note: `Classical public-key algorithms disallowed after ${disallowAfter}.`,
effective: NIST_DISALLOW_EFFECTIVE,
note: `Classical public-key algorithms disallowed after ${IR8547.disallowAfter}.`,
},

@@ -121,2 +128,20 @@ ],

/**
* True when the org policy EXPLICITLY accepts a family — listed in `permitted`
* (an owned exception) or `inTransition` (a tracked migration). A `prohibited`
* family or one covered only by the policy's default fallback is NOT
* acknowledged: silence is not consent, so an unnamed family never earns a gate
* exemption.
*
* `prohibited` takes precedence, matching {@link verdictForAlgorithm}: a policy
* that lists a family in BOTH `prohibited` and `permitted` (a plausible merge of
* two policy fragments) resolves to `violation`, and must not then be silently
* acknowledged away — that would produce a self-contradictory verdict (verdict
* `violation`, yet exempt from the gate).
*/
function policyAcknowledges(algo, policy) {
if (policy.prohibited?.includes(algo))
return false;
return Boolean(policy.permitted?.includes(algo) || policy.inTransition?.includes(algo));
}
/**
* Evaluate findings against the selected mandates as of `now`. Unknown mandate

@@ -127,5 +152,21 @@ * ids are ignored — callers validate up front with {@link assertKnownMandates}.

* contributes a verdict row.
*
* When an org `policy` is supplied (the `--policy` composition), every verdict
* row is annotated with the org's own `policyVerdict` and an `acknowledged` flag
* (family explicitly permitted / in-transition). Acknowledgement is purely
* additive here — it changes no status — but {@link mandateGateFails} honours it
* to keep the early gates from double-flagging crypto the org is knowingly,
* traceably managing. A passed DISALLOW deadline is never acknowledgeable away.
*/
export function evaluateMandates(findings, mandateIdList, now) {
const nowMs = now.getTime();
export function evaluateMandates(findings, mandateIdList, now, policy) {
// A compliance verdict is as-of a DAY: the clauses take effect on date
// boundaries (YYYY-MM-DD), so the exact clock time carries no compliance
// meaning. Pin `now` to UTC midnight of its date before any arithmetic — this
// makes the whole evaluation (statuses AND the monthsUntil / monthsUntilDisallow
// counters) identical for any two runs on the same day, which is what keeps the
// attested evidence hash reproducible per commit per day. Truncating changes no
// status: every `effective` date is itself UTC-midnight, so `nowMs >= effMs` has
// the same truth value at midnight as at any other time that day.
const nowMs = Date.parse(`${now.toISOString().slice(0, 10)}T00:00:00.000Z`);
const nowIso = new Date(nowMs).toISOString();
const selected = mandateIdList.map(getMandate).filter((m) => Boolean(m));

@@ -135,2 +176,3 @@ const rows = [];

let notInScope = 0;
let acknowledged = 0;
let nextDeadlineMs = null;

@@ -144,2 +186,9 @@ for (const f of findings) {

let worst = "conformant";
// Acknowledgement is a property of the FAMILY (fixed for this finding), so it
// is computed once here and stamped on every row. The tally counts distinct
// acknowledged findings (not rows), so one family under two mandates is one
// acknowledgement, matching the per-finding status counts in `summary`.
const family = algo;
const isAcknowledged = policy ? policyAcknowledges(family, policy) : false;
let producedRow = false;
for (const mandate of selected) {

@@ -152,2 +201,3 @@ // The applicable clauses for this family, earliest deadline first.

continue;
producedRow = true;
// Tier the clauses so both stay live: a passed DISALLOW clause is a

@@ -198,4 +248,10 @@ // violation; a passed DEPRECATE clause (disallow still ahead) is the

citation: mandate.citation,
policyVerdict: policy ? verdictForAlgorithm(family, policy).verdict : null,
acknowledged: isAcknowledged,
});
}
// Count the acknowledged FINDING once (it produced at least one prohibited
// row and the org policy owns/tracks its family), not once per mandate row.
if (producedRow && isAcknowledged)
acknowledged++;
perFindingWorst.push(worst);

@@ -212,3 +268,3 @@ }

return {
now: now.toISOString(),
now: nowIso,
mandates: selected.map((m) => m.id),

@@ -220,2 +276,4 @@ summary,

hasViolation: summary.violation > 0,
policyName: policy?.name ?? null,
acknowledged,
};

@@ -229,2 +287,9 @@ }

* immediately.
*
* Policy composition: when a finding was `acknowledged` by the org policy
* (`--policy`), it is exempt from the EARLY gates (`failNow` / `leadMonths`) —
* the org is knowingly, traceably managing that family, so its own early
* enforcement should not re-flag it. A passed DISALLOW deadline (`violation`)
* still fails regardless: a dated legal disallow is not something an org can
* self-exempt from.
*/

@@ -234,6 +299,8 @@ export function mandateGateFails(ev, opts = {}) {

return true;
// Early gates skip policy-acknowledged findings; the hard `violation` above did not.
const gated = ev.findings.filter((v) => !v.acknowledged);
if (opts.failNow)
return ev.findings.length > 0;
return gated.length > 0;
if (opts.leadMonths !== undefined) {
return ev.findings.some((v) => v.monthsUntilDisallow !== null && v.monthsUntilDisallow <= opts.leadMonths);
return gated.some((v) => v.monthsUntilDisallow !== null && v.monthsUntilDisallow <= opts.leadMonths);
}

@@ -240,0 +307,0 @@ return false;

@@ -1,1 +0,1 @@

{"version":3,"file":"mandates.js","sourceRoot":"","sources":["../src/mandates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Policy-as-code compliance mandates → dated, clause-named verdicts for findings.\n *\n * `CryptoPolicy` (policy.ts) classifies findings by algorithm family but is\n * date-blind. A mandate adds the missing dimension: named clauses with an effective\n * DATE (\"CNSA 2.0 disallows classical public-key crypto after 2035\"). The evaluator\n * compares each finding's algorithm against the selected mandates and today's date,\n * so a finding on a prohibited family reads as `due` (every deadline still ahead),\n * `deprecated` (the DEPRECATE deadline has passed — a warning), or `violation` (the\n * DISALLOW deadline has passed — a failure), always naming the governing clause,\n * deadline, and citation. This is what turns the inventory into an enforceable,\n * mandate-mapped gate rather than a neutral list.\n *\n * Pure and deterministic (the caller supplies `now`), so it is trivially testable.\n * qScan consumes it today for the `--mandate` gate; because it operates on the\n * shared `Finding[]`, qProbe or the GitHub Action can reuse it unchanged.\n *\n * Catalog scope: the two regimes that carry hard algorithm deadlines — CNSA 2.0 and\n * NIST IR 8547. DORA / NIS2 / PCI DSS require approved cryptography but set no\n * independent algorithm date; they inherit these timelines and are cited in docs.\n */\nimport type { AlgorithmFamily, Finding } from \"./types.js\";\nimport { PQC_STANDARDS } from \"./standards.js\";\n\n/**\n * All Shor-broken classical asymmetric families — the mandate's SCOPE. A finding\n * on one of these is adjudicated against the selected mandates; findings on\n * anything else (hashes, RNG, dependency, or TLS-configuration findings) are out\n * of scope for a PQC-asymmetric mandate and are tallied as `notInScope` instead\n * of inflating the conformant count.\n */\nconst CLASSICAL_PUBLIC_KEY: readonly AlgorithmFamily[] = [\n \"RSA\",\n \"ECDH\",\n \"ECDSA\",\n \"EdDSA\",\n \"DH\",\n \"DSA\",\n \"X25519\",\n \"X448\",\n \"ECIES\",\n];\n\n/**\n * The PROHIBITED subset the dated clauses apply to. X25519 and X448 are\n * deliberately excluded: they are the classical half of the recommended hybrid\n * key exchange (X25519MLKEM768 — permitted and recommended under the NIST\n * profile), and a static scan cannot distinguish a standalone exchange from the\n * hybrid's classical leg. Prohibiting them would false-positive exactly the orgs\n * that hybridized correctly, so they stay in scope but read `conformant`.\n */\nconst PROHIBITED_FAMILIES: readonly AlgorithmFamily[] = CLASSICAL_PUBLIC_KEY.filter(\n (family) => family !== \"X25519\" && family !== \"X448\",\n);\n\n/**\n * Effective dates derived from the standards source of truth\n * (`PQC_STANDARDS.transitionTimeline`), so a quarterly standards update moves the\n * mandate deadlines automatically (test/standards.test.ts asserts they agree).\n *\n * Boundary choice: \"deprecate AFTER 2030\" leaves the whole stated year permitted,\n * so each clause takes effect on the LAST day of its year (`YYYY-12-31`) —\n * conservative by a single day, unlike `YYYY-01-01`, which would bite roughly a\n * year early.\n */\nconst { deprecateAfter, disallowAfter } = PQC_STANDARDS.transitionTimeline;\nconst DEPRECATE_EFFECTIVE = `${deprecateAfter}-12-31`;\nconst DISALLOW_EFFECTIVE = `${disallowAfter}-12-31`;\n\n/** Which enforcement tier a clause encodes: warn (`deprecate`) or fail (`disallow`). */\nexport type MandateRuleTier = \"deprecate\" | \"disallow\";\n\nexport interface MandateRule {\n /** The named clause this rule encodes (verbatim in the gate's failure message). */\n clause: string;\n /** Enforcement tier: a passed `deprecate` date warns; a passed `disallow` date fails. */\n tier: MandateRuleTier;\n /** Algorithm families prohibited from the effective date onward. */\n prohibits: AlgorithmFamily[];\n /** ISO date (YYYY-MM-DD) the prohibition takes effect. */\n effective: string;\n /** One-line human description of the clause. */\n note: string;\n}\n\nexport interface Mandate {\n id: string;\n name: string;\n authority: string;\n citation: string;\n /** When this catalog entry was last reviewed against the source. */\n asOf: string;\n rules: MandateRule[];\n}\n\n/** The bundled mandate catalog. Keyed by `--mandate <id>`. */\nexport const MANDATES: Record<string, Mandate> = {\n \"cnsa-2.0\": {\n id: \"cnsa-2.0\",\n name: \"CNSA 2.0\",\n authority: \"NSA\",\n citation: \"NSA Commercial National Security Algorithm Suite 2.0 (CNSA 2.0)\",\n asOf: \"2026-07\",\n rules: [\n {\n clause: `CNSA 2.0 — deprecate classical PKC after ${deprecateAfter}`,\n tier: \"deprecate\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: DEPRECATE_EFFECTIVE,\n note: \"Classical public-key cryptography deprecated; systems should use CNSA 2.0 PQC exclusively.\",\n },\n {\n clause: `CNSA 2.0 — disallow classical PKC after ${disallowAfter}`,\n tier: \"disallow\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: DISALLOW_EFFECTIVE,\n note: \"Classical public-key cryptography disallowed; the migration must be complete.\",\n },\n ],\n },\n \"nist-ir-8547\": {\n id: \"nist-ir-8547\",\n name: \"NIST IR 8547\",\n authority: \"NIST\",\n citation: \"NIST IR 8547 (Transition to Post-Quantum Cryptography Standards)\",\n asOf: \"2026-07\",\n rules: [\n {\n clause: `NIST IR 8547 — deprecate classical PKC after ${deprecateAfter}`,\n tier: \"deprecate\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: DEPRECATE_EFFECTIVE,\n note: `112-bit-security classical public-key algorithms deprecated after ${deprecateAfter}.`,\n },\n {\n clause: `NIST IR 8547 — disallow classical PKC after ${disallowAfter}`,\n tier: \"disallow\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: DISALLOW_EFFECTIVE,\n note: `Classical public-key algorithms disallowed after ${disallowAfter}.`,\n },\n ],\n },\n};\n\nexport const mandateIds = (): string[] => Object.keys(MANDATES);\nexport const getMandate = (id: string): Mandate | undefined => MANDATES[id];\n\n/**\n * Validate mandate ids loudly, matching `parseCryptoPolicy`'s fail-loud\n * convention: a mistyped id must never silently evaluate to an empty gate.\n * Throws an `Error` naming every unknown id and the known catalog.\n * `evaluateMandates` itself stays lenient (unknown ids are skipped) so callers\n * decide where to fail.\n */\nexport function assertKnownMandates(ids: readonly string[]): void {\n const unknown = ids.filter((id) => !MANDATES[id]);\n if (unknown.length > 0) {\n throw new Error(\n `unknown mandate id(s): ${unknown.join(\", \")}; known mandates: ${mandateIds().join(\", \")}`,\n );\n }\n}\n\n/**\n * A finding's status against a mandate, worst last:\n * - `conformant` — in scope (classical asymmetric) but prohibited by no selected\n * clause (e.g. X25519 as the presumed hybrid leg).\n * - `due` — prohibited, with every deadline still ahead.\n * - `deprecated` — the DEPRECATE deadline has passed; a warning, not a failure.\n * - `violation` — the DISALLOW deadline has passed; fails the default gate.\n */\nexport type MandateStatus = \"conformant\" | \"due\" | \"deprecated\" | \"violation\";\n\nexport interface MandateFindingVerdict {\n ruleId: string;\n algorithm: AlgorithmFamily | \"unknown\";\n file: string;\n line: number;\n /** Mandate id (e.g. \"cnsa-2.0\"). */\n mandate: string;\n /**\n * The governing clause: the next upcoming clause when `due`, the passed\n * DEPRECATE clause when `deprecated`, the passed DISALLOW clause on `violation`.\n */\n clause: string;\n /** ISO effective date of the governing clause. */\n effective: string;\n status: MandateStatus;\n /** Whole months from `now` to the governing deadline; negative once it has passed. */\n monthsUntil: number;\n /**\n * ISO effective date of this mandate's DISALLOW clause for the family, or null\n * when the mandate carries none. The gate's `leadMonths` measures against this.\n */\n disallowEffective: string | null;\n /** Whole months from `now` to `disallowEffective`; null when there is none. */\n monthsUntilDisallow: number | null;\n citation: string;\n}\n\nexport interface MandateEvaluation {\n /** The `now` the evaluation was computed against (ISO). */\n now: string;\n /** Mandate ids evaluated. */\n mandates: string[];\n /**\n * Counts of IN-SCOPE findings (classical asymmetric families) by their worst\n * status across the selected mandates. Out-of-scope findings are excluded so\n * the conformant count is an honest statement about asymmetric crypto only.\n */\n summary: Record<MandateStatus, number>;\n /**\n * Findings outside the mandate's scope (hashes, RNG, dependency, TLS-config…),\n * which a PQC-asymmetric mandate does not adjudicate.\n */\n notInScope: number;\n /** One row per (prohibited finding × applicable mandate). */\n findings: MandateFindingVerdict[];\n /** Earliest still-future deadline across non-violation rows, or null. */\n nextDeadline: string | null;\n /** True when at least one DISALLOW deadline has passed (a `violation` exists). */\n hasViolation: boolean;\n}\n\nconst MONTH_MS = 2_629_800_000; // average month\n\nfunction monthsBetween(fromMs: number, toMs: number): number {\n return Math.round((toMs - fromMs) / MONTH_MS);\n}\n\nconst STATUS_RANK: Record<MandateStatus, number> = {\n conformant: 0,\n due: 1,\n deprecated: 2,\n violation: 3,\n};\n\n/**\n * Evaluate findings against the selected mandates as of `now`. Unknown mandate\n * ids are ignored — callers validate up front with {@link assertKnownMandates}.\n * A finding outside the classical-asymmetric scope is counted in `notInScope`;\n * an in-scope finding no selected mandate prohibits is `conformant`. Neither\n * contributes a verdict row.\n */\nexport function evaluateMandates(\n findings: readonly Finding[],\n mandateIdList: readonly string[],\n now: Date,\n): MandateEvaluation {\n const nowMs = now.getTime();\n const selected = mandateIdList.map(getMandate).filter((m): m is Mandate => Boolean(m));\n\n const rows: MandateFindingVerdict[] = [];\n const perFindingWorst: MandateStatus[] = [];\n let notInScope = 0;\n let nextDeadlineMs: number | null = null;\n\n for (const f of findings) {\n const algo = f.algorithm ?? \"unknown\";\n if (!CLASSICAL_PUBLIC_KEY.includes(algo as AlgorithmFamily)) {\n notInScope++;\n continue;\n }\n let worst: MandateStatus = \"conformant\";\n for (const mandate of selected) {\n // The applicable clauses for this family, earliest deadline first.\n const applicable = mandate.rules\n .filter((r) => r.prohibits.includes(algo as AlgorithmFamily))\n .sort((a, b) => a.effective.localeCompare(b.effective));\n if (applicable.length === 0) continue;\n\n // Tier the clauses so both stay live: a passed DISALLOW clause is a\n // violation; a passed DEPRECATE clause (disallow still ahead) is the\n // deprecated warning tier; otherwise the finding is due against the next\n // upcoming clause.\n const passed = applicable.filter((r) => nowMs >= new Date(r.effective).getTime());\n const passedDisallow = passed.filter((r) => r.tier === \"disallow\");\n let status: MandateStatus;\n let governing: MandateRule;\n if (passedDisallow.length > 0) {\n status = \"violation\";\n governing = passedDisallow[0];\n } else if (passed.length > 0) {\n status = \"deprecated\";\n governing = passed[passed.length - 1];\n } else {\n status = \"due\";\n governing = applicable[0];\n }\n\n // The disallow clause (earliest, if several) anchors `leadMonths`.\n const disallowRule = applicable.find((r) => r.tier === \"disallow\") ?? null;\n const disallowMs = disallowRule ? new Date(disallowRule.effective).getTime() : null;\n\n if (status !== \"violation\") {\n for (const r of applicable) {\n const effMs = new Date(r.effective).getTime();\n if (effMs > nowMs && (nextDeadlineMs === null || effMs < nextDeadlineMs))\n nextDeadlineMs = effMs;\n }\n }\n if (STATUS_RANK[status] > STATUS_RANK[worst]) worst = status;\n rows.push({\n ruleId: f.ruleId,\n algorithm: algo,\n file: f.location.file,\n line: f.location.line,\n mandate: mandate.id,\n clause: governing.clause,\n effective: governing.effective,\n status,\n monthsUntil: monthsBetween(nowMs, new Date(governing.effective).getTime()),\n disallowEffective: disallowRule ? disallowRule.effective : null,\n monthsUntilDisallow: disallowMs !== null ? monthsBetween(nowMs, disallowMs) : null,\n citation: mandate.citation,\n });\n }\n perFindingWorst.push(worst);\n }\n\n const summary: Record<MandateStatus, number> = {\n conformant: 0,\n due: 0,\n deprecated: 0,\n violation: 0,\n };\n for (const s of perFindingWorst) summary[s]++;\n\n return {\n now: now.toISOString(),\n mandates: selected.map((m) => m.id),\n summary,\n notInScope,\n findings: rows,\n nextDeadline:\n nextDeadlineMs !== null ? new Date(nextDeadlineMs).toISOString().slice(0, 10) : null,\n hasViolation: summary.violation > 0,\n };\n}\n\nexport interface MandateGateOptions {\n /** Fail when a DISALLOW deadline is within this many months (early enforcement). */\n leadMonths?: number;\n /** Fail on any mandate-prohibited finding regardless of the deadlines. */\n failNow?: boolean;\n}\n\n/**\n * The gate decision under the \"deadline-aware\" default: fail only once a DISALLOW\n * deadline has passed (`violation`). A passed DEPRECATE date (`deprecated`) is a\n * warning and does not fail the build. `leadMonths` fails early when a disallow\n * deadline is within the window; `failNow` fails on any prohibited finding\n * immediately.\n */\nexport function mandateGateFails(ev: MandateEvaluation, opts: MandateGateOptions = {}): boolean {\n if (ev.hasViolation) return true;\n if (opts.failNow) return ev.findings.length > 0;\n if (opts.leadMonths !== undefined) {\n return ev.findings.some(\n (v) => v.monthsUntilDisallow !== null && v.monthsUntilDisallow <= opts.leadMonths!,\n );\n }\n return false;\n}\n"]}
{"version":3,"file":"mandates.js","sourceRoot":"","sources":["../src/mandates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Policy-as-code compliance mandates → dated, clause-named verdicts for findings.\n *\n * `CryptoPolicy` (policy.ts) classifies findings by algorithm family but is\n * date-blind. A mandate adds the missing dimension: named clauses with an effective\n * DATE (\"CNSA 2.0 disallows classical public-key crypto after 2033\"). The evaluator\n * compares each finding's algorithm against the selected mandates and today's date,\n * so a finding on a prohibited family reads as `due` (every deadline still ahead),\n * `deprecated` (the DEPRECATE deadline has passed — a warning), or `violation` (the\n * DISALLOW deadline has passed — a failure), always naming the governing clause,\n * deadline, and citation. This is what turns the inventory into an enforceable,\n * mandate-mapped gate rather than a neutral list.\n *\n * Pure and deterministic (the caller supplies `now`), so it is trivially testable.\n * qScan consumes it today for the `--mandate` gate; because it operates on the\n * shared `Finding[]`, qProbe or the GitHub Action can reuse it unchanged.\n *\n * Catalog scope: the two regimes that carry hard algorithm deadlines — CNSA 2.0 and\n * NIST IR 8547. DORA / NIS2 / PCI DSS require approved cryptography but set no\n * independent algorithm date; they inherit these timelines and are cited in docs.\n */\nimport type { AlgorithmFamily, Finding } from \"./types.js\";\nimport { PQC_STANDARDS } from \"./standards.js\";\nimport { verdictForAlgorithm } from \"./policy.js\";\nimport type { CryptoPolicy, PolicyVerdict } from \"./policy.js\";\n\n/**\n * All Shor-broken classical asymmetric families — the mandate's SCOPE. A finding\n * on one of these is adjudicated against the selected mandates; findings on\n * anything else (hashes, RNG, dependency, or TLS-configuration findings) are out\n * of scope for a PQC-asymmetric mandate and are tallied as `notInScope` instead\n * of inflating the conformant count.\n */\nconst CLASSICAL_PUBLIC_KEY: readonly AlgorithmFamily[] = [\n \"RSA\",\n \"ECDH\",\n \"ECDSA\",\n \"EdDSA\",\n \"DH\",\n \"DSA\",\n \"X25519\",\n \"X448\",\n \"ECIES\",\n];\n\n/**\n * The PROHIBITED subset the dated clauses apply to. X25519 and X448 are\n * deliberately excluded: they are the classical half of the recommended hybrid\n * key exchange (X25519MLKEM768 — permitted and recommended under the NIST\n * profile), and a static scan cannot distinguish a standalone exchange from the\n * hybrid's classical leg. Prohibiting them would false-positive exactly the orgs\n * that hybridized correctly, so they stay in scope but read `conformant`.\n */\nconst PROHIBITED_FAMILIES: readonly AlgorithmFamily[] = CLASSICAL_PUBLIC_KEY.filter(\n (family) => family !== \"X25519\" && family !== \"X448\",\n);\n\n/**\n * Effective dates derived from the standards source of truth, so a quarterly\n * standards update moves the mandate deadlines automatically (test/standards.test.ts\n * asserts they agree). Each regime uses its OWN dated timeline: NIST IR 8547\n * disallows after 2035, while CNSA 2.0 sets its general exclusive-use milestone\n * at 2033 (both deprecate after 2030) — so the two mandates carry different\n * disallow years rather than sharing one.\n *\n * Boundary choice: \"deprecate AFTER 2030\" leaves the whole stated year permitted,\n * so each clause takes effect on the LAST day of its year (`YYYY-12-31`) —\n * conservative by a single day, unlike `YYYY-01-01`, which would bite roughly a\n * year early.\n */\nconst IR8547 = PQC_STANDARDS.transitionTimeline; // 2030 deprecate / 2035 disallow\nconst CNSA = PQC_STANDARDS.cnsaTimeline; // 2030 deprecate / 2033 disallow\nconst NIST_DEPRECATE_EFFECTIVE = `${IR8547.deprecateAfter}-12-31`;\nconst NIST_DISALLOW_EFFECTIVE = `${IR8547.disallowAfter}-12-31`;\nconst CNSA_DEPRECATE_EFFECTIVE = `${CNSA.deprecateAfter}-12-31`;\nconst CNSA_DISALLOW_EFFECTIVE = `${CNSA.disallowAfter}-12-31`;\n\n/** Which enforcement tier a clause encodes: warn (`deprecate`) or fail (`disallow`). */\nexport type MandateRuleTier = \"deprecate\" | \"disallow\";\n\nexport interface MandateRule {\n /** The named clause this rule encodes (verbatim in the gate's failure message). */\n clause: string;\n /** Enforcement tier: a passed `deprecate` date warns; a passed `disallow` date fails. */\n tier: MandateRuleTier;\n /** Algorithm families prohibited from the effective date onward. */\n prohibits: AlgorithmFamily[];\n /** ISO date (YYYY-MM-DD) the prohibition takes effect. */\n effective: string;\n /** One-line human description of the clause. */\n note: string;\n}\n\nexport interface Mandate {\n id: string;\n name: string;\n authority: string;\n citation: string;\n /** When this catalog entry was last reviewed against the source. */\n asOf: string;\n rules: MandateRule[];\n}\n\n/** The bundled mandate catalog. Keyed by `--mandate <id>`. */\nexport const MANDATES: Record<string, Mandate> = {\n \"cnsa-2.0\": {\n id: \"cnsa-2.0\",\n name: \"CNSA 2.0\",\n authority: \"NSA\",\n citation: \"NSA Commercial National Security Algorithm Suite 2.0 (CNSA 2.0)\",\n asOf: \"2026-07\",\n rules: [\n {\n clause: `CNSA 2.0 — deprecate classical PKC after ${CNSA.deprecateAfter}`,\n tier: \"deprecate\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: CNSA_DEPRECATE_EFFECTIVE,\n note: `Classical public-key cryptography deprecated (${CNSA.deprecateAfter}: software/firmware signing exclusive-use); systems should use CNSA 2.0 PQC.`,\n },\n {\n clause: `CNSA 2.0 — disallow classical PKC after ${CNSA.disallowAfter}`,\n tier: \"disallow\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: CNSA_DISALLOW_EFFECTIVE,\n note: `Classical public-key cryptography disallowed (${CNSA.disallowAfter}: general NSS exclusive-use milestone); the migration must be complete.`,\n },\n ],\n },\n \"nist-ir-8547\": {\n id: \"nist-ir-8547\",\n name: \"NIST IR 8547\",\n authority: \"NIST\",\n citation: \"NIST IR 8547 (Transition to Post-Quantum Cryptography Standards)\",\n asOf: \"2026-07\",\n rules: [\n {\n clause: `NIST IR 8547 — deprecate classical PKC after ${IR8547.deprecateAfter}`,\n tier: \"deprecate\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: NIST_DEPRECATE_EFFECTIVE,\n note: `112-bit-security classical public-key algorithms deprecated after ${IR8547.deprecateAfter}.`,\n },\n {\n clause: `NIST IR 8547 — disallow classical PKC after ${IR8547.disallowAfter}`,\n tier: \"disallow\",\n prohibits: [...PROHIBITED_FAMILIES],\n effective: NIST_DISALLOW_EFFECTIVE,\n note: `Classical public-key algorithms disallowed after ${IR8547.disallowAfter}.`,\n },\n ],\n },\n};\n\nexport const mandateIds = (): string[] => Object.keys(MANDATES);\nexport const getMandate = (id: string): Mandate | undefined => MANDATES[id];\n\n/**\n * Validate mandate ids loudly, matching `parseCryptoPolicy`'s fail-loud\n * convention: a mistyped id must never silently evaluate to an empty gate.\n * Throws an `Error` naming every unknown id and the known catalog.\n * `evaluateMandates` itself stays lenient (unknown ids are skipped) so callers\n * decide where to fail.\n */\nexport function assertKnownMandates(ids: readonly string[]): void {\n const unknown = ids.filter((id) => !MANDATES[id]);\n if (unknown.length > 0) {\n throw new Error(\n `unknown mandate id(s): ${unknown.join(\", \")}; known mandates: ${mandateIds().join(\", \")}`,\n );\n }\n}\n\n/**\n * A finding's status against a mandate, worst last:\n * - `conformant` — in scope (classical asymmetric) but prohibited by no selected\n * clause (e.g. X25519 as the presumed hybrid leg).\n * - `due` — prohibited, with every deadline still ahead.\n * - `deprecated` — the DEPRECATE deadline has passed; a warning, not a failure.\n * - `violation` — the DISALLOW deadline has passed; fails the default gate.\n */\nexport type MandateStatus = \"conformant\" | \"due\" | \"deprecated\" | \"violation\";\n\nexport interface MandateFindingVerdict {\n ruleId: string;\n algorithm: AlgorithmFamily | \"unknown\";\n file: string;\n line: number;\n /** Mandate id (e.g. \"cnsa-2.0\"). */\n mandate: string;\n /**\n * The governing clause: the next upcoming clause when `due`, the passed\n * DEPRECATE clause when `deprecated`, the passed DISALLOW clause on `violation`.\n */\n clause: string;\n /** ISO effective date of the governing clause. */\n effective: string;\n status: MandateStatus;\n /** Whole months from `now` to the governing deadline; negative once it has passed. */\n monthsUntil: number;\n /**\n * ISO effective date of this mandate's DISALLOW clause for the family, or null\n * when the mandate carries none. The gate's `leadMonths` measures against this.\n */\n disallowEffective: string | null;\n /** Whole months from `now` to `disallowEffective`; null when there is none. */\n monthsUntilDisallow: number | null;\n citation: string;\n /**\n * The org cryptography policy's verdict on this algorithm family when a policy\n * was composed in via {@link evaluateMandates}' `policy` argument, else null.\n * Purely informational — it records the org's own stance next to the mandate's\n * dated clause so a machine-readable report shows both.\n */\n policyVerdict: PolicyVerdict | null;\n /**\n * True when the org policy EXPLICITLY permits or is transitioning this family —\n * an owned, tracked decision. Acknowledged findings are exempt from the EARLY\n * gates (`failNow` / `leadMonths`); a passed DISALLOW deadline (`violation`)\n * still fails regardless, because an org cannot self-exempt from a dated legal\n * disallow. `false` when no policy was supplied.\n */\n acknowledged: boolean;\n}\n\nexport interface MandateEvaluation {\n /** The `now` the evaluation was computed against (ISO). */\n now: string;\n /** Mandate ids evaluated. */\n mandates: string[];\n /**\n * Counts of IN-SCOPE findings (classical asymmetric families) by their worst\n * status across the selected mandates. Out-of-scope findings are excluded so\n * the conformant count is an honest statement about asymmetric crypto only.\n */\n summary: Record<MandateStatus, number>;\n /**\n * Findings outside the mandate's scope (hashes, RNG, dependency, TLS-config…),\n * which a PQC-asymmetric mandate does not adjudicate.\n */\n notInScope: number;\n /** One row per (prohibited finding × applicable mandate). */\n findings: MandateFindingVerdict[];\n /** Earliest still-future deadline across non-violation rows, or null. */\n nextDeadline: string | null;\n /** True when at least one DISALLOW deadline has passed (a `violation` exists). */\n hasViolation: boolean;\n /** Name of the org policy composed in via `policy`, or null when none was supplied. */\n policyName: string | null;\n /**\n * How many distinct prohibited FINDINGS the org policy explicitly acknowledged\n * (family listed as `permitted` or `inTransition`) — counted per finding, not\n * per verdict row, so a family prohibited by two mandates counts once, matching\n * the per-finding `summary`. 0 when no policy was supplied.\n */\n acknowledged: number;\n}\n\nconst MONTH_MS = 2_629_800_000; // average month\n\nfunction monthsBetween(fromMs: number, toMs: number): number {\n return Math.round((toMs - fromMs) / MONTH_MS);\n}\n\nconst STATUS_RANK: Record<MandateStatus, number> = {\n conformant: 0,\n due: 1,\n deprecated: 2,\n violation: 3,\n};\n\n/**\n * True when the org policy EXPLICITLY accepts a family — listed in `permitted`\n * (an owned exception) or `inTransition` (a tracked migration). A `prohibited`\n * family or one covered only by the policy's default fallback is NOT\n * acknowledged: silence is not consent, so an unnamed family never earns a gate\n * exemption.\n *\n * `prohibited` takes precedence, matching {@link verdictForAlgorithm}: a policy\n * that lists a family in BOTH `prohibited` and `permitted` (a plausible merge of\n * two policy fragments) resolves to `violation`, and must not then be silently\n * acknowledged away — that would produce a self-contradictory verdict (verdict\n * `violation`, yet exempt from the gate).\n */\nfunction policyAcknowledges(algo: AlgorithmFamily, policy: CryptoPolicy): boolean {\n if (policy.prohibited?.includes(algo)) return false;\n return Boolean(policy.permitted?.includes(algo) || policy.inTransition?.includes(algo));\n}\n\n/**\n * Evaluate findings against the selected mandates as of `now`. Unknown mandate\n * ids are ignored — callers validate up front with {@link assertKnownMandates}.\n * A finding outside the classical-asymmetric scope is counted in `notInScope`;\n * an in-scope finding no selected mandate prohibits is `conformant`. Neither\n * contributes a verdict row.\n *\n * When an org `policy` is supplied (the `--policy` composition), every verdict\n * row is annotated with the org's own `policyVerdict` and an `acknowledged` flag\n * (family explicitly permitted / in-transition). Acknowledgement is purely\n * additive here — it changes no status — but {@link mandateGateFails} honours it\n * to keep the early gates from double-flagging crypto the org is knowingly,\n * traceably managing. A passed DISALLOW deadline is never acknowledgeable away.\n */\nexport function evaluateMandates(\n findings: readonly Finding[],\n mandateIdList: readonly string[],\n now: Date,\n policy?: CryptoPolicy,\n): MandateEvaluation {\n // A compliance verdict is as-of a DAY: the clauses take effect on date\n // boundaries (YYYY-MM-DD), so the exact clock time carries no compliance\n // meaning. Pin `now` to UTC midnight of its date before any arithmetic — this\n // makes the whole evaluation (statuses AND the monthsUntil / monthsUntilDisallow\n // counters) identical for any two runs on the same day, which is what keeps the\n // attested evidence hash reproducible per commit per day. Truncating changes no\n // status: every `effective` date is itself UTC-midnight, so `nowMs >= effMs` has\n // the same truth value at midnight as at any other time that day.\n const nowMs = Date.parse(`${now.toISOString().slice(0, 10)}T00:00:00.000Z`);\n const nowIso = new Date(nowMs).toISOString();\n const selected = mandateIdList.map(getMandate).filter((m): m is Mandate => Boolean(m));\n\n const rows: MandateFindingVerdict[] = [];\n const perFindingWorst: MandateStatus[] = [];\n let notInScope = 0;\n let acknowledged = 0;\n let nextDeadlineMs: number | null = null;\n\n for (const f of findings) {\n const algo = f.algorithm ?? \"unknown\";\n if (!CLASSICAL_PUBLIC_KEY.includes(algo as AlgorithmFamily)) {\n notInScope++;\n continue;\n }\n let worst: MandateStatus = \"conformant\";\n // Acknowledgement is a property of the FAMILY (fixed for this finding), so it\n // is computed once here and stamped on every row. The tally counts distinct\n // acknowledged findings (not rows), so one family under two mandates is one\n // acknowledgement, matching the per-finding status counts in `summary`.\n const family = algo as AlgorithmFamily;\n const isAcknowledged = policy ? policyAcknowledges(family, policy) : false;\n let producedRow = false;\n for (const mandate of selected) {\n // The applicable clauses for this family, earliest deadline first.\n const applicable = mandate.rules\n .filter((r) => r.prohibits.includes(algo as AlgorithmFamily))\n .sort((a, b) => a.effective.localeCompare(b.effective));\n if (applicable.length === 0) continue;\n producedRow = true;\n\n // Tier the clauses so both stay live: a passed DISALLOW clause is a\n // violation; a passed DEPRECATE clause (disallow still ahead) is the\n // deprecated warning tier; otherwise the finding is due against the next\n // upcoming clause.\n const passed = applicable.filter((r) => nowMs >= new Date(r.effective).getTime());\n const passedDisallow = passed.filter((r) => r.tier === \"disallow\");\n let status: MandateStatus;\n let governing: MandateRule;\n if (passedDisallow.length > 0) {\n status = \"violation\";\n governing = passedDisallow[0];\n } else if (passed.length > 0) {\n status = \"deprecated\";\n governing = passed[passed.length - 1];\n } else {\n status = \"due\";\n governing = applicable[0];\n }\n\n // The disallow clause (earliest, if several) anchors `leadMonths`.\n const disallowRule = applicable.find((r) => r.tier === \"disallow\") ?? null;\n const disallowMs = disallowRule ? new Date(disallowRule.effective).getTime() : null;\n\n if (status !== \"violation\") {\n for (const r of applicable) {\n const effMs = new Date(r.effective).getTime();\n if (effMs > nowMs && (nextDeadlineMs === null || effMs < nextDeadlineMs))\n nextDeadlineMs = effMs;\n }\n }\n if (STATUS_RANK[status] > STATUS_RANK[worst]) worst = status;\n rows.push({\n ruleId: f.ruleId,\n algorithm: algo,\n file: f.location.file,\n line: f.location.line,\n mandate: mandate.id,\n clause: governing.clause,\n effective: governing.effective,\n status,\n monthsUntil: monthsBetween(nowMs, new Date(governing.effective).getTime()),\n disallowEffective: disallowRule ? disallowRule.effective : null,\n monthsUntilDisallow: disallowMs !== null ? monthsBetween(nowMs, disallowMs) : null,\n citation: mandate.citation,\n policyVerdict: policy ? verdictForAlgorithm(family, policy).verdict : null,\n acknowledged: isAcknowledged,\n });\n }\n // Count the acknowledged FINDING once (it produced at least one prohibited\n // row and the org policy owns/tracks its family), not once per mandate row.\n if (producedRow && isAcknowledged) acknowledged++;\n perFindingWorst.push(worst);\n }\n\n const summary: Record<MandateStatus, number> = {\n conformant: 0,\n due: 0,\n deprecated: 0,\n violation: 0,\n };\n for (const s of perFindingWorst) summary[s]++;\n\n return {\n now: nowIso,\n mandates: selected.map((m) => m.id),\n summary,\n notInScope,\n findings: rows,\n nextDeadline:\n nextDeadlineMs !== null ? new Date(nextDeadlineMs).toISOString().slice(0, 10) : null,\n hasViolation: summary.violation > 0,\n policyName: policy?.name ?? null,\n acknowledged,\n };\n}\n\nexport interface MandateGateOptions {\n /** Fail when a DISALLOW deadline is within this many months (early enforcement). */\n leadMonths?: number;\n /** Fail on any mandate-prohibited finding regardless of the deadlines. */\n failNow?: boolean;\n}\n\n/**\n * The gate decision under the \"deadline-aware\" default: fail only once a DISALLOW\n * deadline has passed (`violation`). A passed DEPRECATE date (`deprecated`) is a\n * warning and does not fail the build. `leadMonths` fails early when a disallow\n * deadline is within the window; `failNow` fails on any prohibited finding\n * immediately.\n *\n * Policy composition: when a finding was `acknowledged` by the org policy\n * (`--policy`), it is exempt from the EARLY gates (`failNow` / `leadMonths`) —\n * the org is knowingly, traceably managing that family, so its own early\n * enforcement should not re-flag it. A passed DISALLOW deadline (`violation`)\n * still fails regardless: a dated legal disallow is not something an org can\n * self-exempt from.\n */\nexport function mandateGateFails(ev: MandateEvaluation, opts: MandateGateOptions = {}): boolean {\n if (ev.hasViolation) return true;\n // Early gates skip policy-acknowledged findings; the hard `violation` above did not.\n const gated = ev.findings.filter((v) => !v.acknowledged);\n if (opts.failNow) return gated.length > 0;\n if (opts.leadMonths !== undefined) {\n return gated.some(\n (v) => v.monthsUntilDisallow !== null && v.monthsUntilDisallow <= opts.leadMonths!,\n );\n }\n return false;\n}\n"]}

@@ -10,2 +10,3 @@ /**

import type { HndlReport } from "./hndl.js";
import type { MandateEvaluation } from "./mandates.js";
/** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */

@@ -41,2 +42,10 @@ export interface SarifLog {

hndl?: HndlReport;
/**
* Optional compliance-mandate evaluation ({@link evaluateMandates}). When
* supplied, the JSON report carries a top-level `mandateMapping` block and the
* SARIF run carries the same under `run.properties.mandate` — the
* machine-readable half of the `--mandate` gate for CI consumption. Purely
* additive: it never changes finding identity, ordering, or exit codes.
*/
mandate?: MandateEvaluation;
}

@@ -43,0 +52,0 @@ /** Serialize a scan result as SARIF 2.1.0. */

@@ -1,1 +0,1 @@

{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAA4B,QAAQ,EAAE,UAAU,EAAY,MAAM,YAAY,CAAC;AAK3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,KAAK,EAAmB,UAAU,EAAE,MAAM,WAAW,CAAC;AAE7D,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AA+FD,8CAA8C;AAC9C,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,QAAQ,CAyI1E;AAkBD,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6DxF;AAwCD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,YAAY,CAAA;CAAE,GACjD,MAAM,CAyGR;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,IAAI,EAAE,YAAY,GACjB,MAAM,EAAE,CAmBV;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,OAAO,EAAE,gBAAgB,GACxB,MAAM,EAAE,CAsBV"}
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAA4B,QAAQ,EAAE,UAAU,EAAY,MAAM,YAAY,CAAC;AAK3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,KAAK,EAAmB,UAAU,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AA+FD,8CAA8C;AAC9C,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,QAAQ,CAgJ1E;AAkBD,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkExF;AAwCD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,YAAY,CAAA;CAAE,GACjD,MAAM,CAyGR;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,IAAI,EAAE,YAAY,GACjB,MAAM,EAAE,CAmBV;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,OAAO,EAAE,gBAAgB,GACxB,MAAM,EAAE,CAsBV"}

@@ -198,2 +198,10 @@ import { VERSION } from "./version.js";

: [];
// Run-level properties bag: the repo HNDL summary and/or the compliance-mandate
// evaluation, whichever were supplied. Both are additive metadata for SARIF
// consumers (our platform ingest, CI) and never affect result identity.
const runProperties = {};
if (opts?.hndl)
runProperties.hndl = hndlSummaryBlock(opts.hndl);
if (opts?.mandate)
runProperties.mandate = opts.mandate;
return {

@@ -213,3 +221,3 @@ $schema: SARIF_SCHEMA,

...(taxonomies.length > 0 ? { taxonomies } : {}),
...(opts?.hndl ? { properties: { hndl: hndlSummaryBlock(opts.hndl) } } : {}),
...(Object.keys(runProperties).length > 0 ? { properties: runProperties } : {}),
results,

@@ -255,2 +263,7 @@ },

...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),
// Compliance-mandate evaluation (`--mandate`): the machine-readable verdicts
// + summary, so a CI job can gate/report on them without re-parsing the human
// block. Carries the org `--policy` composition (policyVerdict / acknowledged)
// when one was supplied.
...(opts?.mandate ? { mandateMapping: opts.mandate } : {}),
findings: result.findings.map((f) => {

@@ -257,0 +270,0 @@ const exposure = exposureFor(f, hndl);

@@ -1,1 +0,1 @@

{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reporters: turn a {@link ScanResult} into SARIF 2.1.0, a clean JSON object,\n * or a human-readable text summary. No third-party dependencies — ANSI colour\n * is emitted with raw escape codes and is off by default.\n */\nimport type { AlgorithmFamily, Finding, RuleMeta, ScanResult, Severity } from \"./types.js\";\nimport { VERSION } from \"./version.js\";\nimport { SEVERITY_ORDER, sarifLevel } from \"./severity.js\";\nimport { ANALYZABLE_LANGUAGES_LABEL } from \"./detect-utils.js\";\nimport { remediationFor, remediationForTier, remediationForProfile } from \"./remediation.js\";\nimport type { SecurityTier } from \"./remediation.js\";\nimport type { StandardsProfile } from \"./standards-profiles.js\";\nimport { fingerprintFinding } from \"./baseline.js\";\nimport { findingFingerprint } from \"./hndl.js\";\nimport type { FindingExposure, HndlReport } from \"./hndl.js\";\n\n/** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */\nexport interface SarifLog {\n $schema: string;\n version: \"2.1.0\";\n runs: unknown[];\n}\n\n/** Options shared by the structured reporters ({@link toSarif} / {@link toJson}). */\nexport interface ReportOptions {\n /**\n * Omit `location.snippet` from every finding in the output. Defaults to false\n * (snippets are included). Snippets of `sensitive` findings (e.g. PEM key\n * blocks, SSH public keys) are ALWAYS omitted regardless of this flag — the\n * snippet there IS the sensitive value.\n */\n redactSnippets?: boolean;\n /**\n * Full rule catalog to advertise in SARIF `tool.driver.rules[]`, even for\n * rules that produced no finding in this run. Pass\n * `defaultRegistry.ruleCatalog()`. When omitted, only the rules that actually\n * fired are emitted (the historical behaviour). SARIF-only; ignored by\n * {@link toJson}.\n */\n catalog?: RuleMeta[];\n /**\n * Optional HNDL exposure analysis ({@link computeHndl}). When supplied, each\n * finding gains its `exposure` fields (score, bound data asset, rationale)\n * keyed by fingerprint, and the report carries the repo-level HNDL summary.\n * Purely additive: it never changes finding identity, ordering, or exit codes.\n */\n hndl?: HndlReport;\n}\n\n/** The per-finding exposure block emitted in JSON / SARIF, or undefined. */\nfunction exposureFor(f: Finding, hndl: HndlReport | undefined): FindingExposure | undefined {\n if (!hndl) return undefined;\n return hndl.byFingerprint.get(findingFingerprint(f));\n}\n\n/** The repo HNDL summary block shared by JSON output and the SARIF run. */\nfunction hndlSummaryBlock(hndl: HndlReport): Record<string, unknown> {\n return {\n modelVersion: hndl.modelVersion,\n horizon: hndl.horizon,\n summary: hndl.summary,\n assets: hndl.assets,\n };\n}\n\nconst SARIF_SCHEMA =\n \"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json\";\n\nconst INFORMATION_URI = \"https://github.com/quantakrypto/pqc-tools\";\n\n/**\n * Resolve the snippet to emit for a finding, honouring redaction. Sensitive\n * findings (key material) never expose their snippet; otherwise the snippet is\n * dropped only when `redactSnippets` is set.\n */\nfunction emittedSnippet(f: Finding, redactSnippets: boolean): string | undefined {\n if (redactSnippets || f.sensitive) return undefined;\n return f.location.snippet;\n}\n\n/** Map our severity to a SARIF rule-level default (used in rules[].defaultConfiguration). */\nfunction sarifRank(severity: Severity): number {\n switch (severity) {\n case \"critical\":\n return 100;\n case \"high\":\n return 80;\n case \"medium\":\n return 50;\n case \"low\":\n return 20;\n default:\n return 5;\n }\n}\n\n/** Build a SARIF `rules[]` entry from a rule's severity/title/message/etc. */\nfunction sarifRule(spec: {\n id: string;\n title: string;\n message: string;\n severity: Severity;\n category: string;\n algorithm?: string;\n hndl: boolean;\n cwe?: string;\n remediation?: string;\n}): Record<string, unknown> {\n return {\n id: spec.id,\n name: spec.id,\n shortDescription: { text: spec.title },\n fullDescription: { text: spec.message },\n defaultConfiguration: { level: sarifLevel(spec.severity), rank: sarifRank(spec.severity) },\n ...(spec.remediation ? { help: { text: `Remediation: ${spec.remediation}` } } : {}),\n properties: {\n category: spec.category,\n ...(spec.algorithm ? { algorithm: spec.algorithm } : {}),\n hndl: spec.hndl,\n ...(spec.cwe ? { cwe: spec.cwe, \"security-severity\": securitySeverity(spec.severity) } : {}),\n ...(spec.cwe ? { tags: [\"security\", spec.cwe] } : {}),\n },\n ...(spec.cwe\n ? {\n relationships: [\n { target: { id: spec.cwe, toolComponent: { name: \"CWE\" } }, kinds: [\"relevant\"] },\n ],\n }\n : {}),\n };\n}\n\n/** SARIF result.properties fragment for a finding's HNDL exposure, or empty. */\nfunction exposureProperties(exposure: FindingExposure | undefined): Record<string, unknown> {\n if (!exposure) return {};\n return {\n exposureScore: exposure.exposureScore,\n dataAsset: exposure.dataAsset,\n exposureRationale: exposure.rationale,\n };\n}\n\n/** Serialize a scan result as SARIF 2.1.0. */\nexport function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {\n const redactSnippets = opts?.redactSnippets ?? false;\n // Build the rule set and collect the CWE taxa referenced by any rule. When a\n // full catalog is supplied, advertise every rule (even ones that didn't fire);\n // otherwise emit one rule per ruleId encountered (the historical behaviour).\n const ruleIndex = new Map<string, number>();\n const rules: Array<Record<string, unknown>> = [];\n const cweTaxa = new Set<string>();\n\n for (const r of opts?.catalog ?? []) {\n if (ruleIndex.has(r.id)) continue;\n if (r.cwe) cweTaxa.add(r.cwe);\n ruleIndex.set(r.id, rules.length);\n rules.push(\n sarifRule({\n id: r.id,\n title: r.title,\n message: r.message,\n severity: r.severity,\n category: r.category,\n algorithm: r.algorithm,\n hndl: r.hndl,\n cwe: r.cwe,\n remediation: r.remediation,\n }),\n );\n }\n\n for (const f of result.findings) {\n if (f.cwe) cweTaxa.add(f.cwe);\n if (ruleIndex.has(f.ruleId)) continue;\n ruleIndex.set(f.ruleId, rules.length);\n rules.push(\n sarifRule({\n id: f.ruleId,\n title: f.title,\n message: f.message,\n severity: f.severity,\n category: f.category,\n algorithm: f.algorithm,\n hndl: f.hndl,\n cwe: f.cwe,\n remediation: f.remediation,\n }),\n );\n }\n\n const results = result.findings.map((f) => {\n const region: Record<string, number> = { startLine: f.location.line };\n if (typeof f.location.column === \"number\") region.startColumn = f.location.column;\n if (typeof f.location.endLine === \"number\") region.endLine = f.location.endLine;\n const snippet = emittedSnippet(f, redactSnippets);\n\n return {\n ruleId: f.ruleId,\n ruleIndex: ruleIndex.get(f.ruleId),\n level: sarifLevel(f.severity),\n message: { text: f.message },\n // Line-INSENSITIVE fingerprint (the same one the baseline uses:\n // sha256 of ruleId|file|normalizedSnippet). GitHub code scanning keys\n // alert identity + dedup off partialFingerprints, so a finding survives\n // line shifts and reformatting instead of re-alerting as \"new\" on every\n // edit above it. `quantakrypto/v1` names our scheme.\n partialFingerprints: { \"quantakrypto/v1\": fingerprintFinding(f) },\n properties: {\n // Same stable identity mirrored into properties so non-GitHub SARIF\n // consumers (our platform ingest) can read one uniform `fingerprint`\n // field across JSON and SARIF without reaching into partialFingerprints.\n fingerprint: fingerprintFinding(f),\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n hndl: f.hndl,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n ...(f.remediation ? { remediation: f.remediation } : {}),\n ...(f.cwe ? { cwe: f.cwe } : {}),\n ...exposureProperties(exposureFor(f, opts?.hndl)),\n },\n ...(f.cwe\n ? {\n taxa: [\n {\n target: { id: f.cwe, toolComponent: { name: \"CWE\" } },\n },\n ],\n }\n : {}),\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: f.location.file },\n region: {\n ...region,\n ...(snippet ? { snippet: { text: snippet } } : {}),\n },\n },\n },\n ],\n };\n });\n\n // CWE taxonomy component (SARIF taxonomies), referenced by rules + results.\n const taxonomies =\n cweTaxa.size > 0\n ? [\n {\n name: \"CWE\",\n informationUri: \"https://cwe.mitre.org/\",\n organization: \"MITRE\",\n shortDescription: { text: \"The MITRE Common Weakness Enumeration\" },\n taxa: [...cweTaxa].sort().map((id) => ({\n id,\n helpUri: `https://cwe.mitre.org/data/definitions/${id.replace(/^CWE-/, \"\")}.html`,\n })),\n },\n ]\n : [];\n\n return {\n $schema: SARIF_SCHEMA,\n version: \"2.1.0\",\n runs: [\n {\n tool: {\n driver: {\n name: \"qScan\",\n informationUri: INFORMATION_URI,\n version: result.toolVersion || VERSION,\n rules,\n },\n },\n ...(taxonomies.length > 0 ? { taxonomies } : {}),\n ...(opts?.hndl ? { properties: { hndl: hndlSummaryBlock(opts.hndl) } } : {}),\n results,\n },\n ],\n };\n}\n\n/** GitHub-code-scanning `security-severity` (0–10) derived from our severity. */\nfunction securitySeverity(severity: Severity): string {\n switch (severity) {\n case \"critical\":\n return \"9.5\";\n case \"high\":\n return \"8.0\";\n case \"medium\":\n return \"5.0\";\n case \"low\":\n return \"3.0\";\n default:\n return \"1.0\";\n }\n}\n\n/** Serialize a scan result as a plain JSON-friendly object. */\nexport function toJson(result: ScanResult, opts?: ReportOptions): Record<string, unknown> {\n const redactSnippets = opts?.redactSnippets ?? false;\n const hndl = opts?.hndl;\n return {\n toolVersion: result.toolVersion,\n root: result.root,\n startedAt: result.startedAt,\n finishedAt: result.finishedAt,\n filesScanned: result.filesScanned,\n ...(result.analyzedFiles !== undefined ? { analyzedFiles: result.analyzedFiles } : {}),\n ...(result.diagnostics ? { diagnostics: result.diagnostics } : {}),\n inventory: {\n readinessScore: result.inventory.readinessScore,\n hndlCount: result.inventory.hndlCount,\n bySeverity: result.inventory.bySeverity,\n byCategory: result.inventory.byCategory,\n byAlgorithm: result.inventory.byAlgorithm,\n },\n ...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),\n findings: result.findings.map((f) => {\n const exposure = exposureFor(f, hndl);\n return {\n // Stable, line-INSENSITIVE identity of the finding: sha256 of\n // ruleId | normalized-POSIX-repo-relative-path | normalized-snippet\n // (the SARIF partialFingerprints trick, line number deliberately\n // excluded). Reused verbatim from the baseline module so JSON identity,\n // SARIF partialFingerprints, and the baseline suppression set are one and\n // the same value. A line move does NOT change it; when no snippet context\n // exists it falls back to ruleId|path. This is the cross-run identity the\n // platform keys posture drift on.\n fingerprint: fingerprintFinding(f),\n ruleId: f.ruleId,\n title: f.title,\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n algorithm: f.algorithm,\n hndl: f.hndl,\n message: f.message,\n remediation: f.remediation,\n cwe: f.cwe,\n location: {\n file: f.location.file,\n line: f.location.line,\n column: f.location.column,\n endLine: f.location.endLine,\n snippet: emittedSnippet(f, redactSnippets),\n },\n ...(exposure\n ? {\n exposure: {\n fingerprint: exposure.fingerprint,\n exposureScore: exposure.exposureScore,\n dataAsset: exposure.dataAsset,\n rationale: exposure.rationale,\n },\n }\n : {}),\n };\n }),\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Human-readable summary */\n/* -------------------------------------------------------------------------- */\n\n/** Raw ANSI codes (no chalk). Disabled when colour is off. */\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n} as const;\n\nfunction severityColor(sev: Severity): string {\n switch (sev) {\n case \"critical\":\n return ANSI.magenta;\n case \"high\":\n return ANSI.red;\n case \"medium\":\n return ANSI.yellow;\n case \"low\":\n return ANSI.blue;\n default:\n return ANSI.dim;\n }\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return ANSI.green;\n if (score >= 50) return ANSI.yellow;\n return ANSI.red;\n}\n\n/**\n * Render a human-readable summary of a scan result. Colour is off by default;\n * pass `{ color: true }` to emit ANSI escape codes.\n */\nexport function formatSummary(\n result: ScanResult,\n options?: { color?: boolean; tier?: SecurityTier },\n): string {\n const color = options?.color ?? false;\n const c = (code: string, text: string): string => (color ? `${code}${text}${ANSI.reset}` : text);\n\n const lines: string[] = [];\n const inv = result.inventory;\n\n lines.push(c(ANSI.bold, \"qScan — post-quantum readiness report\"));\n lines.push(c(ANSI.dim, `tool v${result.toolVersion} · root: ${result.root}`));\n lines.push(\"\");\n\n // Readiness score banner.\n lines.push(\n `Readiness score: ${c(`${ANSI.bold}${scoreColor(inv.readinessScore)}`, `${inv.readinessScore}/100`)}`,\n );\n const analyzed =\n result.analyzedFiles !== undefined\n ? ` Analyzed (${ANALYZABLE_LANGUAGES_LABEL}): ${result.analyzedFiles}`\n : \"\";\n lines.push(\n `Files scanned: ${result.filesScanned}${analyzed} Findings: ${result.findings.length} HNDL-exposed: ${c(\n inv.hndlCount > 0 ? ANSI.red : ANSI.green,\n String(inv.hndlCount),\n )}`,\n );\n // Coverage honesty: a score over zero analyzable files is not a clean bill of health.\n if (result.analyzedFiles === 0 && result.filesScanned > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: 0 files were in a supported source language (${ANALYZABLE_LANGUAGES_LABEL}) — the readiness score does not reflect this codebase.`,\n ),\n );\n }\n // Coverage diagnostics: skipped files mean the finding count may be incomplete.\n const diag = result.diagnostics;\n if (diag && (diag.unreadable > 0 || diag.skippedMinified > 0)) {\n lines.push(\n c(\n ANSI.yellow,\n `Coverage: ${diag.unreadable} unreadable, ${diag.skippedMinified} skipped as minified — results may be incomplete.`,\n ),\n );\n }\n lines.push(\"\");\n\n // Severity breakdown.\n const sevParts = SEVERITY_ORDER.filter((s) => inv.bySeverity[s] > 0).map((s) =>\n c(severityColor(s), `${s}: ${inv.bySeverity[s]}`),\n );\n lines.push(`By severity: ${sevParts.length ? sevParts.join(\" \") : c(ANSI.green, \"none\")}`);\n\n // Algorithm breakdown.\n const algoParts = Object.entries(inv.byAlgorithm)\n .sort((a, b) => b[1] - a[1])\n .map(([k, v]) => `${k}: ${v}`);\n if (algoParts.length) lines.push(`By algorithm: ${algoParts.join(\" \")}`);\n lines.push(\"\");\n\n if (result.findings.length === 0) {\n lines.push(c(ANSI.green, \"No classical asymmetric cryptography detected. ✓\"));\n return lines.join(\"\\n\");\n }\n\n // Top findings, grouped by severity (most severe first), capped for readability.\n const sorted = [...result.findings].sort(\n (a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity),\n );\n const MAX_SHOWN = 25;\n lines.push(\n c(ANSI.bold, `Top findings (${Math.min(MAX_SHOWN, sorted.length)} of ${sorted.length}):`),\n );\n\n for (const f of sorted.slice(0, MAX_SHOWN)) {\n const loc = `${f.location.file}:${f.location.line}${\n f.location.column ? `:${f.location.column}` : \"\"\n }`;\n const tag = c(severityColor(f.severity), `[${f.severity}]`);\n const hndl = f.hndl ? c(ANSI.red, \" (HNDL)\") : \"\";\n lines.push(` ${tag} ${f.title}${hndl}`);\n lines.push(c(ANSI.dim, ` ${loc} — ${f.message}`));\n if (f.remediation) lines.push(c(ANSI.cyan, ` → ${f.remediation}`));\n }\n\n if (sorted.length > MAX_SHOWN) {\n lines.push(c(ANSI.dim, ` …and ${sorted.length - MAX_SHOWN} more.`));\n }\n\n lines.push(\"\");\n if (inv.hndlCount > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: ${inv.hndlCount} finding(s) are exposed to \"harvest now, decrypt later\" — ` +\n \"encrypted traffic captured today can be decrypted once a quantum computer exists. \" +\n \"Prioritise migrating key exchange / encryption to hybrid PQC (X25519MLKEM768).\",\n ),\n );\n }\n\n if (options?.tier) {\n lines.push(\"\", ...formatTierGuidance(inv.byAlgorithm, options.tier));\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Per-family migration targets for a CNSA security tier — surfaces the otherwise\n * library-only {@link remediationForTier} in human reports. Category 5 shows the\n * ML-KEM-1024 / ML-DSA-87 sets CNSA 2.0 mandates for national-security systems and\n * long-lived secrets. Returns plain (un-coloured) lines; the caller styles them.\n */\nexport function formatTierGuidance(\n byAlgorithm: Record<string, number>,\n tier: SecurityTier,\n): string[] {\n const label = tier === \"category-5\" ? \"CNSA 2.0 (Category 5)\" : \"Category 3 (commercial)\";\n const out: string[] = [`${label} migration targets:`];\n const seen = new Set<string>();\n for (const [k, n] of Object.entries(byAlgorithm)) {\n if (n <= 0) continue;\n const fam = k as AlgorithmFamily;\n if (fam === \"unknown\" || !remediationFor(fam)) continue; // skip unmapped families\n const rem = remediationForTier(fam, tier);\n if (seen.has(rem.recommendation)) continue;\n seen.add(rem.recommendation);\n out.push(` ${fam} → ${rem.recommendation}`);\n }\n if (tier === \"category-5\") {\n out.push(\n \" CNSA 2.0 mandates ML-KEM-1024 / ML-DSA-87 for national-security systems and long-lived secrets (2030/2033 milestones).\",\n );\n }\n return out;\n}\n\n/**\n * Per-family migration targets tailored to a selected {@link StandardsProfile}\n * (`--profile`). Unlike {@link formatTierGuidance} (CNSA-tier only), this surfaces the\n * regime's parameter sets AND its hybrid stance — required (ANSSI/BSI) vs recommended\n * (NIST/NCSC) vs optional (CNSA 2.0) — so guidance isn't regime-wrong. Returns plain\n * (un-coloured) lines; the caller styles them.\n */\nexport function formatProfileGuidance(\n byAlgorithm: Record<string, number>,\n profile: StandardsProfile,\n): string[] {\n const out: string[] = [`${profile.name} migration targets:`];\n const seen = new Set<string>();\n for (const [k, n] of Object.entries(byAlgorithm)) {\n if (n <= 0) continue;\n const fam = k as AlgorithmFamily;\n if (fam === \"unknown\" || !remediationFor(fam)) continue; // skip unmapped families\n const rem = remediationForProfile(fam, profile);\n if (seen.has(rem.recommendation)) continue;\n seen.add(rem.recommendation);\n out.push(` ${fam} → ${rem.recommendation}`);\n }\n const stance =\n profile.hybridStance === \"required\"\n ? \"requires classical+PQC hybridization\"\n : profile.hybridStance === \"recommended\"\n ? \"recommends hybridization\"\n : \"does not require hybridization\";\n out.push(\n ` ${profile.authority} ${stance}; classical public-key crypto disallowed after ${profile.disallowAfter} (${profile.citation}).`,\n );\n return out;\n}\n"]}
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AA6C/C,4EAA4E;AAC5E,SAAS,WAAW,CAAC,CAAU,EAAE,IAA4B;IAC3D,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,2EAA2E;AAC3E,SAAS,gBAAgB,CAAC,IAAgB;IACxC,OAAO;QACL,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAChB,gGAAgG,CAAC;AAEnG,MAAM,eAAe,GAAG,2CAA2C,CAAC;AAEpE;;;;GAIG;AACH,SAAS,cAAc,CAAC,CAAU,EAAE,cAAuB;IACzD,IAAI,cAAc,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;AAC5B,CAAC;AAED,6FAA6F;AAC7F,SAAS,SAAS,CAAC,QAAkB;IACnC,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,UAAU;YACb,OAAO,GAAG,CAAC;QACb,KAAK,MAAM;YACT,OAAO,EAAE,CAAC;QACZ,KAAK,QAAQ;YACX,OAAO,EAAE,CAAC;QACZ,KAAK,KAAK;YACR,OAAO,EAAE,CAAC;QACZ;YACE,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS,SAAS,CAAC,IAUlB;IACC,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,EAAE;QACb,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE;QACtC,eAAe,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE;QACvC,oBAAoB,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;QAC1F,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,gBAAgB,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,UAAU,EAAE;YACV,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,mBAAmB,EAAE,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5F,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtD;QACD,GAAG,CAAC,IAAI,CAAC,GAAG;YACV,CAAC,CAAC;gBACE,aAAa,EAAE;oBACb,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE;iBAClF;aACF;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,SAAS,kBAAkB,CAAC,QAAqC;IAC/D,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,OAAO;QACL,aAAa,EAAE,QAAQ,CAAC,aAAa;QACrC,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,iBAAiB,EAAE,QAAQ,CAAC,SAAS;KACtC,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,OAAO,CAAC,MAAkB,EAAE,IAAoB;IAC9D,MAAM,cAAc,GAAG,IAAI,EAAE,cAAc,IAAI,KAAK,CAAC;IACrD,6EAA6E;IAC7E,+EAA+E;IAC/E,6EAA6E;IAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,MAAM,KAAK,GAAmC,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;QACpC,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,SAAS;QAClC,IAAI,CAAC,CAAC,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC9B,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAClC,KAAK,CAAC,IAAI,CACR,SAAS,CAAC;YACR,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,WAAW,EAAE,CAAC,CAAC,WAAW;SAC3B,CAAC,CACH,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAChC,IAAI,CAAC,CAAC,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;YAAE,SAAS;QACtC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACtC,KAAK,CAAC,IAAI,CACR,SAAS,CAAC;YACR,EAAE,EAAE,CAAC,CAAC,MAAM;YACZ,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,WAAW,EAAE,CAAC,CAAC,WAAW;SAC3B,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACxC,MAAM,MAAM,GAA2B,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtE,IAAI,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,QAAQ;YAAE,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;QAClF,IAAI,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,KAAK,QAAQ;YAAE,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;QAChF,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;QAElD,OAAO;YACL,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;YAClC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC7B,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;YAC5B,gEAAgE;YAChE,sEAAsE;YACtE,wEAAwE;YACxE,wEAAwE;YACxE,qDAAqD;YACrD,mBAAmB,EAAE,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE;YACjE,UAAU,EAAE;gBACV,oEAAoE;gBACpE,qEAAqE;gBACrE,yEAAyE;gBACzE,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;gBAClC,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClD,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChC,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;aAClD;YACD,GAAG,CAAC,CAAC,CAAC,GAAG;gBACP,CAAC,CAAC;oBACE,IAAI,EAAE;wBACJ;4BACE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;yBACtD;qBACF;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,SAAS,EAAE;gBACT;oBACE,gBAAgB,EAAE;wBAChB,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE;wBAC1C,MAAM,EAAE;4BACN,GAAG,MAAM;4BACT,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBACnD;qBACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,4EAA4E;IAC5E,MAAM,UAAU,GACd,OAAO,CAAC,IAAI,GAAG,CAAC;QACd,CAAC,CAAC;YACE;gBACE,IAAI,EAAE,KAAK;gBACX,cAAc,EAAE,wBAAwB;gBACxC,YAAY,EAAE,OAAO;gBACrB,gBAAgB,EAAE,EAAE,IAAI,EAAE,uCAAuC,EAAE;gBACnE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;oBACrC,EAAE;oBACF,OAAO,EAAE,0CAA0C,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO;iBAClF,CAAC,CAAC;aACJ;SACF;QACH,CAAC,CAAC,EAAE,CAAC;IAET,gFAAgF;IAChF,4EAA4E;IAC5E,wEAAwE;IACxE,MAAM,aAAa,GAA4B,EAAE,CAAC;IAClD,IAAI,IAAI,EAAE,IAAI;QAAE,aAAa,CAAC,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,IAAI,EAAE,OAAO;QAAE,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAExD,OAAO;QACL,OAAO,EAAE,YAAY;QACrB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE;oBACJ,MAAM,EAAE;wBACN,IAAI,EAAE,OAAO;wBACb,cAAc,EAAE,eAAe;wBAC/B,OAAO,EAAE,MAAM,CAAC,WAAW,IAAI,OAAO;wBACtC,KAAK;qBACN;iBACF;gBACD,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/E,OAAO;aACR;SACF;KACF,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,SAAS,gBAAgB,CAAC,QAAkB;IAC1C,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,UAAU;YACb,OAAO,KAAK,CAAC;QACf,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,KAAK,CAAC;QACf,KAAK,KAAK;YACR,OAAO,KAAK,CAAC;QACf;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CAAC,MAAkB,EAAE,IAAoB;IAC7D,MAAM,cAAc,GAAG,IAAI,EAAE,cAAc,IAAI,KAAK,CAAC;IACrD,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC;IACxB,OAAO;QACL,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,GAAG,CAAC,MAAM,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,SAAS,EAAE;YACT,cAAc,EAAE,MAAM,CAAC,SAAS,CAAC,cAAc;YAC/C,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,SAAS;YACrC,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,UAAU;YACvC,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,UAAU;YACvC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW;SAC1C;QACD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,6EAA6E;QAC7E,8EAA8E;QAC9E,+EAA+E;QAC/E,yBAAyB;QACzB,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YACtC,OAAO;gBACL,8DAA8D;gBAC9D,oEAAoE;gBACpE,iEAAiE;gBACjE,wEAAwE;gBACxE,0EAA0E;gBAC1E,0EAA0E;gBAC1E,0EAA0E;gBAC1E,kCAAkC;gBAClC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;gBAClC,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,QAAQ,EAAE;oBACR,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI;oBACrB,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI;oBACrB,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM;oBACzB,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO;oBAC3B,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,cAAc,CAAC;iBAC3C;gBACD,GAAG,CAAC,QAAQ;oBACV,CAAC,CAAC;wBACE,QAAQ,EAAE;4BACR,WAAW,EAAE,QAAQ,CAAC,WAAW;4BACjC,aAAa,EAAE,QAAQ,CAAC,aAAa;4BACrC,SAAS,EAAE,QAAQ,CAAC,SAAS;4BAC7B,SAAS,EAAE,QAAQ,CAAC,SAAS;yBAC9B;qBACF;oBACH,CAAC,CAAC,EAAE,CAAC;aACR,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,iFAAiF;AACjF,gFAAgF;AAEhF,8DAA8D;AAC9D,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,UAAU;IACf,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,UAAU;IACnB,IAAI,EAAE,UAAU;CACR,CAAC;AAEX,SAAS,aAAa,CAAC,GAAa;IAClC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,UAAU;YACb,OAAO,IAAI,CAAC,OAAO,CAAC;QACtB,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,GAAG,CAAC;QAClB,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,KAAK,KAAK;YACR,OAAO,IAAI,CAAC,IAAI,CAAC;QACnB;YACE,OAAO,IAAI,CAAC,GAAG,CAAC;IACpB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC;IACnC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IACpC,OAAO,IAAI,CAAC,GAAG,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAkB,EAClB,OAAkD;IAElD,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,KAAK,CAAC;IACtC,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,IAAY,EAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEjG,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC;IAE7B,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,uCAAuC,CAAC,CAAC,CAAC;IAClE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC,WAAW,YAAY,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,0BAA0B;IAC1B,KAAK,CAAC,IAAI,CACR,oBAAoB,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,cAAc,MAAM,CAAC,EAAE,CACtG,CAAC;IACF,MAAM,QAAQ,GACZ,MAAM,CAAC,aAAa,KAAK,SAAS;QAChC,CAAC,CAAC,gBAAgB,0BAA0B,MAAM,MAAM,CAAC,aAAa,EAAE;QACxE,CAAC,CAAC,EAAE,CAAC;IACT,KAAK,CAAC,IAAI,CACR,oBAAoB,MAAM,CAAC,YAAY,GAAG,QAAQ,gBAAgB,MAAM,CAAC,QAAQ,CAAC,MAAM,oBAAoB,CAAC,CAC3G,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EACzC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CACtB,EAAE,CACJ,CAAC;IACF,sFAAsF;IACtF,IAAI,MAAM,CAAC,aAAa,KAAK,CAAC,IAAI,MAAM,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QAC1D,KAAK,CAAC,IAAI,CACR,CAAC,CACC,IAAI,CAAC,MAAM,EACX,sDAAsD,0BAA0B,yDAAyD,CAC1I,CACF,CAAC;IACJ,CAAC;IACD,gFAAgF;IAChF,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC;IAChC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9D,KAAK,CAAC,IAAI,CACR,CAAC,CACC,IAAI,CAAC,MAAM,EACX,aAAa,IAAI,CAAC,UAAU,gBAAgB,IAAI,CAAC,eAAe,mDAAmD,CACpH,CACF,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,sBAAsB;IACtB,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7E,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAClD,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,iBAAiB,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAE9F,uBAAuB;IACvB,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;SAC9C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjC,IAAI,SAAS,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,kDAAkD,CAAC,CAAC,CAAC;QAC9E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,iFAAiF;IACjF,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CACtC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAClF,CAAC;IACF,MAAM,SAAS,GAAG,EAAE,CAAC;IACrB,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAC1F,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,GAC/C,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAChD,EAAE,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,IAAI,EAAE,CAAC,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,CAAC,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,MAAM,CAAC,MAAM,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,IAAI,GAAG,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CACR,CAAC,CACC,IAAI,CAAC,MAAM,EACX,SAAS,GAAG,CAAC,SAAS,4DAA4D;YAChF,oFAAoF;YACpF,gFAAgF,CACnF,CACF,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAmC,EACnC,IAAkB;IAElB,MAAM,KAAK,GAAG,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,yBAAyB,CAAC;IAC1F,MAAM,GAAG,GAAa,CAAC,GAAG,KAAK,qBAAqB,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACrB,MAAM,GAAG,GAAG,CAAoB,CAAC;QACjC,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,yBAAyB;QAClF,MAAM,GAAG,GAAG,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;YAAE,SAAS;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC7B,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,GAAG,CAAC,IAAI,CACN,0HAA0H,CAC3H,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,WAAmC,EACnC,OAAyB;IAEzB,MAAM,GAAG,GAAa,CAAC,GAAG,OAAO,CAAC,IAAI,qBAAqB,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACrB,MAAM,GAAG,GAAG,CAAoB,CAAC;QACjC,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,yBAAyB;QAClF,MAAM,GAAG,GAAG,qBAAqB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;YAAE,SAAS;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC7B,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,MAAM,GACV,OAAO,CAAC,YAAY,KAAK,UAAU;QACjC,CAAC,CAAC,sCAAsC;QACxC,CAAC,CAAC,OAAO,CAAC,YAAY,KAAK,aAAa;YACtC,CAAC,CAAC,0BAA0B;YAC5B,CAAC,CAAC,gCAAgC,CAAC;IACzC,GAAG,CAAC,IAAI,CACN,KAAK,OAAO,CAAC,SAAS,IAAI,MAAM,kDAAkD,OAAO,CAAC,aAAa,KAAK,OAAO,CAAC,QAAQ,IAAI,CACjI,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * Reporters: turn a {@link ScanResult} into SARIF 2.1.0, a clean JSON object,\n * or a human-readable text summary. No third-party dependencies — ANSI colour\n * is emitted with raw escape codes and is off by default.\n */\nimport type { AlgorithmFamily, Finding, RuleMeta, ScanResult, Severity } from \"./types.js\";\nimport { VERSION } from \"./version.js\";\nimport { SEVERITY_ORDER, sarifLevel } from \"./severity.js\";\nimport { ANALYZABLE_LANGUAGES_LABEL } from \"./detect-utils.js\";\nimport { remediationFor, remediationForTier, remediationForProfile } from \"./remediation.js\";\nimport type { SecurityTier } from \"./remediation.js\";\nimport type { StandardsProfile } from \"./standards-profiles.js\";\nimport { fingerprintFinding } from \"./baseline.js\";\nimport { findingFingerprint } from \"./hndl.js\";\nimport type { FindingExposure, HndlReport } from \"./hndl.js\";\nimport type { MandateEvaluation } from \"./mandates.js\";\n\n/** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */\nexport interface SarifLog {\n $schema: string;\n version: \"2.1.0\";\n runs: unknown[];\n}\n\n/** Options shared by the structured reporters ({@link toSarif} / {@link toJson}). */\nexport interface ReportOptions {\n /**\n * Omit `location.snippet` from every finding in the output. Defaults to false\n * (snippets are included). Snippets of `sensitive` findings (e.g. PEM key\n * blocks, SSH public keys) are ALWAYS omitted regardless of this flag — the\n * snippet there IS the sensitive value.\n */\n redactSnippets?: boolean;\n /**\n * Full rule catalog to advertise in SARIF `tool.driver.rules[]`, even for\n * rules that produced no finding in this run. Pass\n * `defaultRegistry.ruleCatalog()`. When omitted, only the rules that actually\n * fired are emitted (the historical behaviour). SARIF-only; ignored by\n * {@link toJson}.\n */\n catalog?: RuleMeta[];\n /**\n * Optional HNDL exposure analysis ({@link computeHndl}). When supplied, each\n * finding gains its `exposure` fields (score, bound data asset, rationale)\n * keyed by fingerprint, and the report carries the repo-level HNDL summary.\n * Purely additive: it never changes finding identity, ordering, or exit codes.\n */\n hndl?: HndlReport;\n /**\n * Optional compliance-mandate evaluation ({@link evaluateMandates}). When\n * supplied, the JSON report carries a top-level `mandateMapping` block and the\n * SARIF run carries the same under `run.properties.mandate` — the\n * machine-readable half of the `--mandate` gate for CI consumption. Purely\n * additive: it never changes finding identity, ordering, or exit codes.\n */\n mandate?: MandateEvaluation;\n}\n\n/** The per-finding exposure block emitted in JSON / SARIF, or undefined. */\nfunction exposureFor(f: Finding, hndl: HndlReport | undefined): FindingExposure | undefined {\n if (!hndl) return undefined;\n return hndl.byFingerprint.get(findingFingerprint(f));\n}\n\n/** The repo HNDL summary block shared by JSON output and the SARIF run. */\nfunction hndlSummaryBlock(hndl: HndlReport): Record<string, unknown> {\n return {\n modelVersion: hndl.modelVersion,\n horizon: hndl.horizon,\n summary: hndl.summary,\n assets: hndl.assets,\n };\n}\n\nconst SARIF_SCHEMA =\n \"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json\";\n\nconst INFORMATION_URI = \"https://github.com/quantakrypto/pqc-tools\";\n\n/**\n * Resolve the snippet to emit for a finding, honouring redaction. Sensitive\n * findings (key material) never expose their snippet; otherwise the snippet is\n * dropped only when `redactSnippets` is set.\n */\nfunction emittedSnippet(f: Finding, redactSnippets: boolean): string | undefined {\n if (redactSnippets || f.sensitive) return undefined;\n return f.location.snippet;\n}\n\n/** Map our severity to a SARIF rule-level default (used in rules[].defaultConfiguration). */\nfunction sarifRank(severity: Severity): number {\n switch (severity) {\n case \"critical\":\n return 100;\n case \"high\":\n return 80;\n case \"medium\":\n return 50;\n case \"low\":\n return 20;\n default:\n return 5;\n }\n}\n\n/** Build a SARIF `rules[]` entry from a rule's severity/title/message/etc. */\nfunction sarifRule(spec: {\n id: string;\n title: string;\n message: string;\n severity: Severity;\n category: string;\n algorithm?: string;\n hndl: boolean;\n cwe?: string;\n remediation?: string;\n}): Record<string, unknown> {\n return {\n id: spec.id,\n name: spec.id,\n shortDescription: { text: spec.title },\n fullDescription: { text: spec.message },\n defaultConfiguration: { level: sarifLevel(spec.severity), rank: sarifRank(spec.severity) },\n ...(spec.remediation ? { help: { text: `Remediation: ${spec.remediation}` } } : {}),\n properties: {\n category: spec.category,\n ...(spec.algorithm ? { algorithm: spec.algorithm } : {}),\n hndl: spec.hndl,\n ...(spec.cwe ? { cwe: spec.cwe, \"security-severity\": securitySeverity(spec.severity) } : {}),\n ...(spec.cwe ? { tags: [\"security\", spec.cwe] } : {}),\n },\n ...(spec.cwe\n ? {\n relationships: [\n { target: { id: spec.cwe, toolComponent: { name: \"CWE\" } }, kinds: [\"relevant\"] },\n ],\n }\n : {}),\n };\n}\n\n/** SARIF result.properties fragment for a finding's HNDL exposure, or empty. */\nfunction exposureProperties(exposure: FindingExposure | undefined): Record<string, unknown> {\n if (!exposure) return {};\n return {\n exposureScore: exposure.exposureScore,\n dataAsset: exposure.dataAsset,\n exposureRationale: exposure.rationale,\n };\n}\n\n/** Serialize a scan result as SARIF 2.1.0. */\nexport function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {\n const redactSnippets = opts?.redactSnippets ?? false;\n // Build the rule set and collect the CWE taxa referenced by any rule. When a\n // full catalog is supplied, advertise every rule (even ones that didn't fire);\n // otherwise emit one rule per ruleId encountered (the historical behaviour).\n const ruleIndex = new Map<string, number>();\n const rules: Array<Record<string, unknown>> = [];\n const cweTaxa = new Set<string>();\n\n for (const r of opts?.catalog ?? []) {\n if (ruleIndex.has(r.id)) continue;\n if (r.cwe) cweTaxa.add(r.cwe);\n ruleIndex.set(r.id, rules.length);\n rules.push(\n sarifRule({\n id: r.id,\n title: r.title,\n message: r.message,\n severity: r.severity,\n category: r.category,\n algorithm: r.algorithm,\n hndl: r.hndl,\n cwe: r.cwe,\n remediation: r.remediation,\n }),\n );\n }\n\n for (const f of result.findings) {\n if (f.cwe) cweTaxa.add(f.cwe);\n if (ruleIndex.has(f.ruleId)) continue;\n ruleIndex.set(f.ruleId, rules.length);\n rules.push(\n sarifRule({\n id: f.ruleId,\n title: f.title,\n message: f.message,\n severity: f.severity,\n category: f.category,\n algorithm: f.algorithm,\n hndl: f.hndl,\n cwe: f.cwe,\n remediation: f.remediation,\n }),\n );\n }\n\n const results = result.findings.map((f) => {\n const region: Record<string, number> = { startLine: f.location.line };\n if (typeof f.location.column === \"number\") region.startColumn = f.location.column;\n if (typeof f.location.endLine === \"number\") region.endLine = f.location.endLine;\n const snippet = emittedSnippet(f, redactSnippets);\n\n return {\n ruleId: f.ruleId,\n ruleIndex: ruleIndex.get(f.ruleId),\n level: sarifLevel(f.severity),\n message: { text: f.message },\n // Line-INSENSITIVE fingerprint (the same one the baseline uses:\n // sha256 of ruleId|file|normalizedSnippet). GitHub code scanning keys\n // alert identity + dedup off partialFingerprints, so a finding survives\n // line shifts and reformatting instead of re-alerting as \"new\" on every\n // edit above it. `quantakrypto/v1` names our scheme.\n partialFingerprints: { \"quantakrypto/v1\": fingerprintFinding(f) },\n properties: {\n // Same stable identity mirrored into properties so non-GitHub SARIF\n // consumers (our platform ingest) can read one uniform `fingerprint`\n // field across JSON and SARIF without reaching into partialFingerprints.\n fingerprint: fingerprintFinding(f),\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n hndl: f.hndl,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n ...(f.remediation ? { remediation: f.remediation } : {}),\n ...(f.cwe ? { cwe: f.cwe } : {}),\n ...exposureProperties(exposureFor(f, opts?.hndl)),\n },\n ...(f.cwe\n ? {\n taxa: [\n {\n target: { id: f.cwe, toolComponent: { name: \"CWE\" } },\n },\n ],\n }\n : {}),\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: f.location.file },\n region: {\n ...region,\n ...(snippet ? { snippet: { text: snippet } } : {}),\n },\n },\n },\n ],\n };\n });\n\n // CWE taxonomy component (SARIF taxonomies), referenced by rules + results.\n const taxonomies =\n cweTaxa.size > 0\n ? [\n {\n name: \"CWE\",\n informationUri: \"https://cwe.mitre.org/\",\n organization: \"MITRE\",\n shortDescription: { text: \"The MITRE Common Weakness Enumeration\" },\n taxa: [...cweTaxa].sort().map((id) => ({\n id,\n helpUri: `https://cwe.mitre.org/data/definitions/${id.replace(/^CWE-/, \"\")}.html`,\n })),\n },\n ]\n : [];\n\n // Run-level properties bag: the repo HNDL summary and/or the compliance-mandate\n // evaluation, whichever were supplied. Both are additive metadata for SARIF\n // consumers (our platform ingest, CI) and never affect result identity.\n const runProperties: Record<string, unknown> = {};\n if (opts?.hndl) runProperties.hndl = hndlSummaryBlock(opts.hndl);\n if (opts?.mandate) runProperties.mandate = opts.mandate;\n\n return {\n $schema: SARIF_SCHEMA,\n version: \"2.1.0\",\n runs: [\n {\n tool: {\n driver: {\n name: \"qScan\",\n informationUri: INFORMATION_URI,\n version: result.toolVersion || VERSION,\n rules,\n },\n },\n ...(taxonomies.length > 0 ? { taxonomies } : {}),\n ...(Object.keys(runProperties).length > 0 ? { properties: runProperties } : {}),\n results,\n },\n ],\n };\n}\n\n/** GitHub-code-scanning `security-severity` (0–10) derived from our severity. */\nfunction securitySeverity(severity: Severity): string {\n switch (severity) {\n case \"critical\":\n return \"9.5\";\n case \"high\":\n return \"8.0\";\n case \"medium\":\n return \"5.0\";\n case \"low\":\n return \"3.0\";\n default:\n return \"1.0\";\n }\n}\n\n/** Serialize a scan result as a plain JSON-friendly object. */\nexport function toJson(result: ScanResult, opts?: ReportOptions): Record<string, unknown> {\n const redactSnippets = opts?.redactSnippets ?? false;\n const hndl = opts?.hndl;\n return {\n toolVersion: result.toolVersion,\n root: result.root,\n startedAt: result.startedAt,\n finishedAt: result.finishedAt,\n filesScanned: result.filesScanned,\n ...(result.analyzedFiles !== undefined ? { analyzedFiles: result.analyzedFiles } : {}),\n ...(result.diagnostics ? { diagnostics: result.diagnostics } : {}),\n inventory: {\n readinessScore: result.inventory.readinessScore,\n hndlCount: result.inventory.hndlCount,\n bySeverity: result.inventory.bySeverity,\n byCategory: result.inventory.byCategory,\n byAlgorithm: result.inventory.byAlgorithm,\n },\n ...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),\n // Compliance-mandate evaluation (`--mandate`): the machine-readable verdicts\n // + summary, so a CI job can gate/report on them without re-parsing the human\n // block. Carries the org `--policy` composition (policyVerdict / acknowledged)\n // when one was supplied.\n ...(opts?.mandate ? { mandateMapping: opts.mandate } : {}),\n findings: result.findings.map((f) => {\n const exposure = exposureFor(f, hndl);\n return {\n // Stable, line-INSENSITIVE identity of the finding: sha256 of\n // ruleId | normalized-POSIX-repo-relative-path | normalized-snippet\n // (the SARIF partialFingerprints trick, line number deliberately\n // excluded). Reused verbatim from the baseline module so JSON identity,\n // SARIF partialFingerprints, and the baseline suppression set are one and\n // the same value. A line move does NOT change it; when no snippet context\n // exists it falls back to ruleId|path. This is the cross-run identity the\n // platform keys posture drift on.\n fingerprint: fingerprintFinding(f),\n ruleId: f.ruleId,\n title: f.title,\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n algorithm: f.algorithm,\n hndl: f.hndl,\n message: f.message,\n remediation: f.remediation,\n cwe: f.cwe,\n location: {\n file: f.location.file,\n line: f.location.line,\n column: f.location.column,\n endLine: f.location.endLine,\n snippet: emittedSnippet(f, redactSnippets),\n },\n ...(exposure\n ? {\n exposure: {\n fingerprint: exposure.fingerprint,\n exposureScore: exposure.exposureScore,\n dataAsset: exposure.dataAsset,\n rationale: exposure.rationale,\n },\n }\n : {}),\n };\n }),\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Human-readable summary */\n/* -------------------------------------------------------------------------- */\n\n/** Raw ANSI codes (no chalk). Disabled when colour is off. */\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n} as const;\n\nfunction severityColor(sev: Severity): string {\n switch (sev) {\n case \"critical\":\n return ANSI.magenta;\n case \"high\":\n return ANSI.red;\n case \"medium\":\n return ANSI.yellow;\n case \"low\":\n return ANSI.blue;\n default:\n return ANSI.dim;\n }\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return ANSI.green;\n if (score >= 50) return ANSI.yellow;\n return ANSI.red;\n}\n\n/**\n * Render a human-readable summary of a scan result. Colour is off by default;\n * pass `{ color: true }` to emit ANSI escape codes.\n */\nexport function formatSummary(\n result: ScanResult,\n options?: { color?: boolean; tier?: SecurityTier },\n): string {\n const color = options?.color ?? false;\n const c = (code: string, text: string): string => (color ? `${code}${text}${ANSI.reset}` : text);\n\n const lines: string[] = [];\n const inv = result.inventory;\n\n lines.push(c(ANSI.bold, \"qScan — post-quantum readiness report\"));\n lines.push(c(ANSI.dim, `tool v${result.toolVersion} · root: ${result.root}`));\n lines.push(\"\");\n\n // Readiness score banner.\n lines.push(\n `Readiness score: ${c(`${ANSI.bold}${scoreColor(inv.readinessScore)}`, `${inv.readinessScore}/100`)}`,\n );\n const analyzed =\n result.analyzedFiles !== undefined\n ? ` Analyzed (${ANALYZABLE_LANGUAGES_LABEL}): ${result.analyzedFiles}`\n : \"\";\n lines.push(\n `Files scanned: ${result.filesScanned}${analyzed} Findings: ${result.findings.length} HNDL-exposed: ${c(\n inv.hndlCount > 0 ? ANSI.red : ANSI.green,\n String(inv.hndlCount),\n )}`,\n );\n // Coverage honesty: a score over zero analyzable files is not a clean bill of health.\n if (result.analyzedFiles === 0 && result.filesScanned > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: 0 files were in a supported source language (${ANALYZABLE_LANGUAGES_LABEL}) — the readiness score does not reflect this codebase.`,\n ),\n );\n }\n // Coverage diagnostics: skipped files mean the finding count may be incomplete.\n const diag = result.diagnostics;\n if (diag && (diag.unreadable > 0 || diag.skippedMinified > 0)) {\n lines.push(\n c(\n ANSI.yellow,\n `Coverage: ${diag.unreadable} unreadable, ${diag.skippedMinified} skipped as minified — results may be incomplete.`,\n ),\n );\n }\n lines.push(\"\");\n\n // Severity breakdown.\n const sevParts = SEVERITY_ORDER.filter((s) => inv.bySeverity[s] > 0).map((s) =>\n c(severityColor(s), `${s}: ${inv.bySeverity[s]}`),\n );\n lines.push(`By severity: ${sevParts.length ? sevParts.join(\" \") : c(ANSI.green, \"none\")}`);\n\n // Algorithm breakdown.\n const algoParts = Object.entries(inv.byAlgorithm)\n .sort((a, b) => b[1] - a[1])\n .map(([k, v]) => `${k}: ${v}`);\n if (algoParts.length) lines.push(`By algorithm: ${algoParts.join(\" \")}`);\n lines.push(\"\");\n\n if (result.findings.length === 0) {\n lines.push(c(ANSI.green, \"No classical asymmetric cryptography detected. ✓\"));\n return lines.join(\"\\n\");\n }\n\n // Top findings, grouped by severity (most severe first), capped for readability.\n const sorted = [...result.findings].sort(\n (a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity),\n );\n const MAX_SHOWN = 25;\n lines.push(\n c(ANSI.bold, `Top findings (${Math.min(MAX_SHOWN, sorted.length)} of ${sorted.length}):`),\n );\n\n for (const f of sorted.slice(0, MAX_SHOWN)) {\n const loc = `${f.location.file}:${f.location.line}${\n f.location.column ? `:${f.location.column}` : \"\"\n }`;\n const tag = c(severityColor(f.severity), `[${f.severity}]`);\n const hndl = f.hndl ? c(ANSI.red, \" (HNDL)\") : \"\";\n lines.push(` ${tag} ${f.title}${hndl}`);\n lines.push(c(ANSI.dim, ` ${loc} — ${f.message}`));\n if (f.remediation) lines.push(c(ANSI.cyan, ` → ${f.remediation}`));\n }\n\n if (sorted.length > MAX_SHOWN) {\n lines.push(c(ANSI.dim, ` …and ${sorted.length - MAX_SHOWN} more.`));\n }\n\n lines.push(\"\");\n if (inv.hndlCount > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: ${inv.hndlCount} finding(s) are exposed to \"harvest now, decrypt later\" — ` +\n \"encrypted traffic captured today can be decrypted once a quantum computer exists. \" +\n \"Prioritise migrating key exchange / encryption to hybrid PQC (X25519MLKEM768).\",\n ),\n );\n }\n\n if (options?.tier) {\n lines.push(\"\", ...formatTierGuidance(inv.byAlgorithm, options.tier));\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Per-family migration targets for a CNSA security tier — surfaces the otherwise\n * library-only {@link remediationForTier} in human reports. Category 5 shows the\n * ML-KEM-1024 / ML-DSA-87 sets CNSA 2.0 mandates for national-security systems and\n * long-lived secrets. Returns plain (un-coloured) lines; the caller styles them.\n */\nexport function formatTierGuidance(\n byAlgorithm: Record<string, number>,\n tier: SecurityTier,\n): string[] {\n const label = tier === \"category-5\" ? \"CNSA 2.0 (Category 5)\" : \"Category 3 (commercial)\";\n const out: string[] = [`${label} migration targets:`];\n const seen = new Set<string>();\n for (const [k, n] of Object.entries(byAlgorithm)) {\n if (n <= 0) continue;\n const fam = k as AlgorithmFamily;\n if (fam === \"unknown\" || !remediationFor(fam)) continue; // skip unmapped families\n const rem = remediationForTier(fam, tier);\n if (seen.has(rem.recommendation)) continue;\n seen.add(rem.recommendation);\n out.push(` ${fam} → ${rem.recommendation}`);\n }\n if (tier === \"category-5\") {\n out.push(\n \" CNSA 2.0 mandates ML-KEM-1024 / ML-DSA-87 for national-security systems and long-lived secrets (2030/2033 milestones).\",\n );\n }\n return out;\n}\n\n/**\n * Per-family migration targets tailored to a selected {@link StandardsProfile}\n * (`--profile`). Unlike {@link formatTierGuidance} (CNSA-tier only), this surfaces the\n * regime's parameter sets AND its hybrid stance — required (ANSSI/BSI) vs recommended\n * (NIST/NCSC) vs optional (CNSA 2.0) — so guidance isn't regime-wrong. Returns plain\n * (un-coloured) lines; the caller styles them.\n */\nexport function formatProfileGuidance(\n byAlgorithm: Record<string, number>,\n profile: StandardsProfile,\n): string[] {\n const out: string[] = [`${profile.name} migration targets:`];\n const seen = new Set<string>();\n for (const [k, n] of Object.entries(byAlgorithm)) {\n if (n <= 0) continue;\n const fam = k as AlgorithmFamily;\n if (fam === \"unknown\" || !remediationFor(fam)) continue; // skip unmapped families\n const rem = remediationForProfile(fam, profile);\n if (seen.has(rem.recommendation)) continue;\n seen.add(rem.recommendation);\n out.push(` ${fam} → ${rem.recommendation}`);\n }\n const stance =\n profile.hybridStance === \"required\"\n ? \"requires classical+PQC hybridization\"\n : profile.hybridStance === \"recommended\"\n ? \"recommends hybridization\"\n : \"does not require hybridization\";\n out.push(\n ` ${profile.authority} ${stance}; classical public-key crypto disallowed after ${profile.disallowAfter} (${profile.citation}).`,\n );\n return out;\n}\n"]}

@@ -1,1 +0,1 @@

{"version":3,"file":"standards-profiles.d.ts","sourceRoot":"","sources":["../src/standards-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;AAEnE,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzE,oFAAoF;IACpF,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,wEAAwE;IACxE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,oFAAoF;IACpF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,yCAAyC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,SAAS,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAkEzE,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAK9C;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE5E;AAED,kDAAkD;AAClD,wBAAgB,uBAAuB,IAAI,gBAAgB,CAE1D"}
{"version":3,"file":"standards-profiles.d.ts","sourceRoot":"","sources":["../src/standards-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;AAEnE,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzE,oFAAoF;IACpF,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,wEAAwE;IACxE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,oFAAoF;IACpF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,yCAAyC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,SAAS,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAuEzE,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAK9C;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE5E;AAED,kDAAkD;AAClD,wBAAgB,uBAAuB,IAAI,gBAAgB,CAE1D"}

@@ -16,2 +16,3 @@ /**

*/
import { PQC_STANDARDS } from "./standards.js";
/** The default profile id when `--profile` is not given. */

@@ -32,4 +33,6 @@ export const DEFAULT_PROFILE_ID = "nist";

hybridGuidance: "Hybrid key establishment (e.g. X25519MLKEM768) is permitted and recommended during the transition; pure ML-KEM is also acceptable (SP 800-227 / IR 8547).",
deprecateAfter: 2030,
disallowAfter: 2035,
// Derived from the single source of truth so the profile can never drift from
// the `nist-ir-8547` mandate gate (the standards drift test asserts this).
deprecateAfter: PQC_STANDARDS.transitionTimeline.deprecateAfter,
disallowAfter: PQC_STANDARDS.transitionTimeline.disallowAfter,
citation: "NIST IR 8547 + FIPS 203/204/205",

@@ -45,5 +48,8 @@ asOf: "2026-07",

hybridGuidance: "CNSA 2.0 targets pure PQC and does not require hybrids; if a hybrid TLS group is used, it must be SecP384r1MLKEM1024 — X25519MLKEM768's ML-KEM-768 component is sub-CNSA.",
deprecateAfter: 2030,
disallowAfter: 2035,
citation: "NSA CNSA 2.0 (2030/2033/2035 migration milestones)",
// CNSA 2.0 carries its OWN exclusive-use milestones (2030 software/firmware
// signing, 2033 general NSS) — NOT IR 8547's 2035. Derived from the single
// source so `--profile cnsa-2.0` and `--mandate cnsa-2.0` always agree.
deprecateAfter: PQC_STANDARDS.cnsaTimeline.deprecateAfter,
disallowAfter: PQC_STANDARDS.cnsaTimeline.disallowAfter,
citation: "NSA CNSA 2.0 (2030 deprecate / 2033 disallow exclusive-use milestones)",
asOf: "2026-07",

@@ -50,0 +56,0 @@ },

@@ -1,1 +0,1 @@

{"version":3,"file":"standards-profiles.js","sourceRoot":"","sources":["../src/standards-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA6BH,4DAA4D;AAC5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA+C;IAC5E,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM;QACV,IAAI,EAAE,6BAA6B;QACnC,SAAS,EAAE,MAAM;QACjB,SAAS,EAAE,EAAE,GAAG,EAAE,uBAAuB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC9E,YAAY,EAAE,aAAa;QAC3B,cAAc,EACZ,2JAA2J;QAC7J,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,QAAQ,EAAE,iCAAiC;QAC3C,IAAI,EAAE,SAAS;KAChB;IACD,UAAU,EAAE;QACV,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,0CAA0C;QAChD,SAAS,EAAE,KAAK;QAChB,SAAS,EAAE,EAAE,GAAG,EAAE,wBAAwB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC/E,YAAY,EAAE,UAAU;QACxB,cAAc,EACZ,2KAA2K;QAC7K,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,QAAQ,EAAE,oDAAoD;QAC9D,IAAI,EAAE,SAAS;KAChB;IACD,cAAc,EAAE;QACd,EAAE,EAAE,cAAc;QAClB,IAAI,EAAE,wBAAwB;QAC9B,SAAS,EAAE,KAAK;QAChB,SAAS,EAAE,EAAE,GAAG,EAAE,uBAAuB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC9E,YAAY,EAAE,UAAU;QACxB,cAAc,EACZ,+NAA+N;QACjO,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,QAAQ,EAAE,6CAA6C;QACvD,IAAI,EAAE,SAAS;KAChB;IACD,KAAK,EAAE;QACL,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,gBAAgB;QACtB,SAAS,EAAE,OAAO;QAClB,SAAS,EAAE,EAAE,GAAG,EAAE,wBAAwB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC/E,YAAY,EAAE,UAAU;QACxB,cAAc,EACZ,iLAAiL;QACnL,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,QAAQ,EAAE,wCAAwC;QAClD,IAAI,EAAE,SAAS;KAChB;IACD,SAAS,EAAE;QACT,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,MAAM;QACjB,SAAS,EAAE,EAAE,GAAG,EAAE,uBAAuB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC9E,YAAY,EAAE,aAAa;QAC3B,cAAc,EACZ,yNAAyN;QAC3N,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,QAAQ,EAAE,yEAAyE;QACnF,IAAI,EAAE,SAAS;KAChB;CACF,CAAC;AAEF,mEAAmE;AACnE,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,kBAAkB;QAClB,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,kBAAkB,CAAC;KAC7E,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,CAAC,EAAU;IAC5C,OAAO,kBAAkB,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,uBAAuB;IACrC,OAAO,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;AAChD,CAAC","sourcesContent":["/**\n * Selectable STANDARDS PROFILES — the regime a scan's remediation, deadlines, and\n * hybrid guidance are tailored to. Different national/regional authorities agree on\n * the PQC primitives (ML-KEM / ML-DSA) but diverge on two things this tool must not\n * hardcode: (a) the required PARAMETER SETS (a commercial ML-KEM-768 vs a\n * national-security ML-KEM-1024), and (b) the HYBRID STANCE — whether classical+PQC\n * hybridization is required, recommended, or optional during the transition. Baking\n * in NIST/CNSA's \"hybrids optional\" is wrong for an ANSSI or BSI audience, where\n * hybrid is required. `--profile <id>` selects the regime; `--policy` composes an\n * org's own exceptions on top.\n *\n * Like {@link PqcStandards}, every profile carries a citation + `asOf` date and is\n * re-verified on the quarterly standards cadence. These are guidance summaries, not\n * legal advice — consult the cited source of record.\n */\n\n/** Whether classical+PQC hybridization is required during the transition, per regime. */\nexport type HybridStance = \"required\" | \"recommended\" | \"optional\";\n\n/** A regime's PQC guidance profile. */\nexport interface StandardsProfile {\n /** Stable id used by `--profile` (kebab-case). */\n readonly id: string;\n /** Human-readable name. */\n readonly name: string;\n /** The authority / document of record. */\n readonly authority: string;\n /** The KEM / signature parameter sets this regime calls for. */\n readonly paramSets: { readonly kem: string; readonly signature: string };\n /** Whether hybridization is required / recommended / optional under this regime. */\n readonly hybridStance: HybridStance;\n /** One-line regime-specific hybrid guidance surfaced in remediation. */\n readonly hybridGuidance: string;\n /** Year after which classical public-key crypto is deprecated under this regime. */\n readonly deprecateAfter: number;\n /** Year after which it is disallowed. */\n readonly disallowAfter: number;\n /** Spec identifier / publication of record. */\n readonly citation: string;\n /** `YYYY-MM` — when this profile was last verified against its source. */\n readonly asOf: string;\n}\n\n/** The default profile id when `--profile` is not given. */\nexport const DEFAULT_PROFILE_ID = \"nist\";\n\n/**\n * Built-in regime profiles. Facts reflect each authority's published PQC-transition\n * position as of the `asOf` date; verify against the cited source before relying on a\n * deadline or a hybrid mandate for a compliance decision.\n */\nexport const STANDARDS_PROFILES: Readonly<Record<string, StandardsProfile>> = {\n nist: {\n id: \"nist\",\n name: \"NIST (general / commercial)\",\n authority: \"NIST\",\n paramSets: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n hybridStance: \"recommended\",\n hybridGuidance:\n \"Hybrid key establishment (e.g. X25519MLKEM768) is permitted and recommended during the transition; pure ML-KEM is also acceptable (SP 800-227 / IR 8547).\",\n deprecateAfter: 2030,\n disallowAfter: 2035,\n citation: \"NIST IR 8547 + FIPS 203/204/205\",\n asOf: \"2026-07\",\n },\n \"cnsa-2.0\": {\n id: \"cnsa-2.0\",\n name: \"NSA CNSA 2.0 (national-security systems)\",\n authority: \"NSA\",\n paramSets: { kem: \"ML-KEM-1024 (FIPS 203)\", signature: \"ML-DSA-87 (FIPS 204)\" },\n hybridStance: \"optional\",\n hybridGuidance:\n \"CNSA 2.0 targets pure PQC and does not require hybrids; if a hybrid TLS group is used, it must be SecP384r1MLKEM1024 — X25519MLKEM768's ML-KEM-768 component is sub-CNSA.\",\n deprecateAfter: 2030,\n disallowAfter: 2035,\n citation: \"NSA CNSA 2.0 (2030/2033/2035 migration milestones)\",\n asOf: \"2026-07\",\n },\n \"bsi-tr-02102\": {\n id: \"bsi-tr-02102\",\n name: \"BSI TR-02102 (Germany)\",\n authority: \"BSI\",\n paramSets: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n hybridStance: \"required\",\n hybridGuidance:\n \"BSI requires PQC be deployed in HYBRID with an established classical scheme during the transition (defense-in-depth); FrodoKEM is the conservative KEM alternative to ML-KEM, and XMSS/LMS are approved for firmware signing.\",\n deprecateAfter: 2030,\n disallowAfter: 2035,\n citation: \"BSI TR-02102-1 (Kryptographische Verfahren)\",\n asOf: \"2026-07\",\n },\n anssi: {\n id: \"anssi\",\n name: \"ANSSI (France)\",\n authority: \"ANSSI\",\n paramSets: { kem: \"ML-KEM-1024 (FIPS 203)\", signature: \"ML-DSA-87 (FIPS 204)\" },\n hybridStance: \"required\",\n hybridGuidance:\n \"ANSSI requires HYBRIDIZATION (classical + PQC) throughout the transition phase and does not endorse pure PQC alone yet; use the highest parameter set for long-lived assurance.\",\n deprecateAfter: 2030,\n disallowAfter: 2035,\n citation: \"ANSSI — PQC transition position papers\",\n asOf: \"2026-07\",\n },\n \"uk-ncsc\": {\n id: \"uk-ncsc\",\n name: \"UK NCSC\",\n authority: \"NCSC\",\n paramSets: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n hybridStance: \"recommended\",\n hybridGuidance:\n \"NCSC recommends ML-KEM / ML-DSA and is broadly agnostic on hybridization (recommended, not mandated); its migration milestones are earlier — discovery/plan by 2028, high-priority migration by 2031, complete by 2035.\",\n deprecateAfter: 2031,\n disallowAfter: 2035,\n citation: \"NCSC — Preparing for quantum-safe cryptography / PQC migration timeline\",\n asOf: \"2026-07\",\n },\n};\n\n/** All built-in profile ids, in a stable order (default first). */\nexport function standardsProfileIds(): string[] {\n return [\n DEFAULT_PROFILE_ID,\n ...Object.keys(STANDARDS_PROFILES).filter((id) => id !== DEFAULT_PROFILE_ID),\n ];\n}\n\n/** Look up a built-in profile by id, or `undefined` when unknown. */\nexport function getStandardsProfile(id: string): StandardsProfile | undefined {\n return STANDARDS_PROFILES[id];\n}\n\n/** The default profile (NIST). Always defined. */\nexport function defaultStandardsProfile(): StandardsProfile {\n return STANDARDS_PROFILES[DEFAULT_PROFILE_ID];\n}\n"]}
{"version":3,"file":"standards-profiles.js","sourceRoot":"","sources":["../src/standards-profiles.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Selectable STANDARDS PROFILES — the regime a scan's remediation, deadlines, and\n * hybrid guidance are tailored to. Different national/regional authorities agree on\n * the PQC primitives (ML-KEM / ML-DSA) but diverge on two things this tool must not\n * hardcode: (a) the required PARAMETER SETS (a commercial ML-KEM-768 vs a\n * national-security ML-KEM-1024), and (b) the HYBRID STANCE — whether classical+PQC\n * hybridization is required, recommended, or optional during the transition. Baking\n * in NIST/CNSA's \"hybrids optional\" is wrong for an ANSSI or BSI audience, where\n * hybrid is required. `--profile <id>` selects the regime; `--policy` composes an\n * org's own exceptions on top.\n *\n * Like {@link PqcStandards}, every profile carries a citation + `asOf` date and is\n * re-verified on the quarterly standards cadence. These are guidance summaries, not\n * legal advice — consult the cited source of record.\n */\n\nimport { PQC_STANDARDS } from \"./standards.js\";\n\n/** Whether classical+PQC hybridization is required during the transition, per regime. */\nexport type HybridStance = \"required\" | \"recommended\" | \"optional\";\n\n/** A regime's PQC guidance profile. */\nexport interface StandardsProfile {\n /** Stable id used by `--profile` (kebab-case). */\n readonly id: string;\n /** Human-readable name. */\n readonly name: string;\n /** The authority / document of record. */\n readonly authority: string;\n /** The KEM / signature parameter sets this regime calls for. */\n readonly paramSets: { readonly kem: string; readonly signature: string };\n /** Whether hybridization is required / recommended / optional under this regime. */\n readonly hybridStance: HybridStance;\n /** One-line regime-specific hybrid guidance surfaced in remediation. */\n readonly hybridGuidance: string;\n /** Year after which classical public-key crypto is deprecated under this regime. */\n readonly deprecateAfter: number;\n /** Year after which it is disallowed. */\n readonly disallowAfter: number;\n /** Spec identifier / publication of record. */\n readonly citation: string;\n /** `YYYY-MM` — when this profile was last verified against its source. */\n readonly asOf: string;\n}\n\n/** The default profile id when `--profile` is not given. */\nexport const DEFAULT_PROFILE_ID = \"nist\";\n\n/**\n * Built-in regime profiles. Facts reflect each authority's published PQC-transition\n * position as of the `asOf` date; verify against the cited source before relying on a\n * deadline or a hybrid mandate for a compliance decision.\n */\nexport const STANDARDS_PROFILES: Readonly<Record<string, StandardsProfile>> = {\n nist: {\n id: \"nist\",\n name: \"NIST (general / commercial)\",\n authority: \"NIST\",\n paramSets: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n hybridStance: \"recommended\",\n hybridGuidance:\n \"Hybrid key establishment (e.g. X25519MLKEM768) is permitted and recommended during the transition; pure ML-KEM is also acceptable (SP 800-227 / IR 8547).\",\n // Derived from the single source of truth so the profile can never drift from\n // the `nist-ir-8547` mandate gate (the standards drift test asserts this).\n deprecateAfter: PQC_STANDARDS.transitionTimeline.deprecateAfter,\n disallowAfter: PQC_STANDARDS.transitionTimeline.disallowAfter,\n citation: \"NIST IR 8547 + FIPS 203/204/205\",\n asOf: \"2026-07\",\n },\n \"cnsa-2.0\": {\n id: \"cnsa-2.0\",\n name: \"NSA CNSA 2.0 (national-security systems)\",\n authority: \"NSA\",\n paramSets: { kem: \"ML-KEM-1024 (FIPS 203)\", signature: \"ML-DSA-87 (FIPS 204)\" },\n hybridStance: \"optional\",\n hybridGuidance:\n \"CNSA 2.0 targets pure PQC and does not require hybrids; if a hybrid TLS group is used, it must be SecP384r1MLKEM1024 — X25519MLKEM768's ML-KEM-768 component is sub-CNSA.\",\n // CNSA 2.0 carries its OWN exclusive-use milestones (2030 software/firmware\n // signing, 2033 general NSS) — NOT IR 8547's 2035. Derived from the single\n // source so `--profile cnsa-2.0` and `--mandate cnsa-2.0` always agree.\n deprecateAfter: PQC_STANDARDS.cnsaTimeline.deprecateAfter,\n disallowAfter: PQC_STANDARDS.cnsaTimeline.disallowAfter,\n citation: \"NSA CNSA 2.0 (2030 deprecate / 2033 disallow exclusive-use milestones)\",\n asOf: \"2026-07\",\n },\n \"bsi-tr-02102\": {\n id: \"bsi-tr-02102\",\n name: \"BSI TR-02102 (Germany)\",\n authority: \"BSI\",\n paramSets: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n hybridStance: \"required\",\n hybridGuidance:\n \"BSI requires PQC be deployed in HYBRID with an established classical scheme during the transition (defense-in-depth); FrodoKEM is the conservative KEM alternative to ML-KEM, and XMSS/LMS are approved for firmware signing.\",\n deprecateAfter: 2030,\n disallowAfter: 2035,\n citation: \"BSI TR-02102-1 (Kryptographische Verfahren)\",\n asOf: \"2026-07\",\n },\n anssi: {\n id: \"anssi\",\n name: \"ANSSI (France)\",\n authority: \"ANSSI\",\n paramSets: { kem: \"ML-KEM-1024 (FIPS 203)\", signature: \"ML-DSA-87 (FIPS 204)\" },\n hybridStance: \"required\",\n hybridGuidance:\n \"ANSSI requires HYBRIDIZATION (classical + PQC) throughout the transition phase and does not endorse pure PQC alone yet; use the highest parameter set for long-lived assurance.\",\n deprecateAfter: 2030,\n disallowAfter: 2035,\n citation: \"ANSSI — PQC transition position papers\",\n asOf: \"2026-07\",\n },\n \"uk-ncsc\": {\n id: \"uk-ncsc\",\n name: \"UK NCSC\",\n authority: \"NCSC\",\n paramSets: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n hybridStance: \"recommended\",\n hybridGuidance:\n \"NCSC recommends ML-KEM / ML-DSA and is broadly agnostic on hybridization (recommended, not mandated); its migration milestones are earlier — discovery/plan by 2028, high-priority migration by 2031, complete by 2035.\",\n deprecateAfter: 2031,\n disallowAfter: 2035,\n citation: \"NCSC — Preparing for quantum-safe cryptography / PQC migration timeline\",\n asOf: \"2026-07\",\n },\n};\n\n/** All built-in profile ids, in a stable order (default first). */\nexport function standardsProfileIds(): string[] {\n return [\n DEFAULT_PROFILE_ID,\n ...Object.keys(STANDARDS_PROFILES).filter((id) => id !== DEFAULT_PROFILE_ID),\n ];\n}\n\n/** Look up a built-in profile by id, or `undefined` when unknown. */\nexport function getStandardsProfile(id: string): StandardsProfile | undefined {\n return STANDARDS_PROFILES[id];\n}\n\n/** The default profile (NIST). Always defined. */\nexport function defaultStandardsProfile(): StandardsProfile {\n return STANDARDS_PROFILES[DEFAULT_PROFILE_ID];\n}\n"]}

@@ -62,3 +62,3 @@ /**

readonly statefulHbs: StandardsCitation;
/** The migration deadline the transition note surfaces. */
/** The NIST IR 8547 migration deadline the transition note surfaces. */
readonly transitionTimeline: {

@@ -72,2 +72,17 @@ /** Year after which classical public-key crypto is deprecated. */

};
/**
* CNSA 2.0's OWN migration milestones — distinct from the IR 8547 timeline
* above. CNSA 2.0 sets exclusive-use dates per system class; the `cnsa-2.0`
* mandate encodes the earliest hard milestone as `deprecate` and the general
* exclusive-use milestone as `disallow`. Kept separate so the two mandates
* ({@link MANDATES}) each derive from their own dated source.
*/
readonly cnsaTimeline: {
/** Year after which classical PKC is deprecated (2030: software/firmware signing exclusive). */
readonly deprecateAfter: number;
/** Year after which it is disallowed (2033: general NSS exclusive use). */
readonly disallowAfter: number;
readonly source: string;
readonly asOf: string;
};
/** Emerging / backup standards worth tracking beyond the current FIPS. */

@@ -74,0 +89,0 @@ readonly emerging: readonly StandardsCitation[];

@@ -1,1 +0,1 @@

{"version":3,"file":"standards.d.ts","sourceRoot":"","sources":["../src/standards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IAEtC,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAClC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAClC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;KACpC,CAAC;IAEF;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,SAAS,EAAE;YAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QACzE,QAAQ,CAAC,SAAS,EAAE;YAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IAExC,2DAA2D;IAC3D,QAAQ,CAAC,kBAAkB,EAAE;QAC3B,kEAAkE;QAClE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,yCAAyC;QACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAEhD,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,YA6E3B,CAAC;AAEF,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,EACT,SAAS,GAAE,YAA4B,GACtC,qBAAqB,CAMvB"}
{"version":3,"file":"standards.d.ts","sourceRoot":"","sources":["../src/standards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IAEtC,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAClC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAClC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;KACpC,CAAC;IAEF;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,SAAS,EAAE;YAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QACzE,QAAQ,CAAC,SAAS,EAAE;YAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IAExC,wEAAwE;IACxE,QAAQ,CAAC,kBAAkB,EAAE;QAC3B,kEAAkE;QAClE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,yCAAyC;QACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,EAAE;QACrB,gGAAgG;QAChG,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,2EAA2E;QAC3E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAEhD,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,YAqF3B,CAAC;AAEF,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,EACT,SAAS,GAAE,YAA4B,GACtC,qBAAqB,CAMvB"}

@@ -64,2 +64,8 @@ /**

},
cnsaTimeline: {
deprecateAfter: 2030,
disallowAfter: 2033,
source: "NSA CNSA 2.0 (exclusive-use milestones: 2030 software/firmware signing, 2033 general NSS)",
asOf: "2026-07",
},
emerging: [

@@ -66,0 +72,0 @@ {

@@ -1,1 +0,1 @@

{"version":3,"file":"standards.js","sourceRoot":"","sources":["../src/standards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA2DH;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,YAAY,EAAE,YAAY;IAC1B,UAAU,EAAE,YAAY;IACxB,oBAAoB,EAAE,CAAC;IAEvB,IAAI,EAAE;QACJ,KAAK,EAAE;YACL,OAAO,EAAE,2DAA2D;YACpE,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,SAAS;SAChB;QACD,KAAK,EAAE;YACL,OAAO,EAAE,gEAAgE;YACzE,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,SAAS;SAChB;QACD,MAAM,EAAE;YACN,OAAO,EAAE,6EAA6E;YACtF,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,SAAS;SAChB;KACF;IAED,IAAI,EAAE;QACJ,SAAS,EAAE,EAAE,GAAG,EAAE,uBAAuB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC9E,SAAS,EAAE,EAAE,GAAG,EAAE,wBAAwB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC/E,MAAM,EAAE,0EAA0E;QAClF,IAAI,EAAE,SAAS;KAChB;IAED,WAAW,EAAE;QACX,OAAO,EACL,iFAAiF;YACjF,mFAAmF;QACrF,MAAM,EAAE,iBAAiB;QACzB,IAAI,EAAE,SAAS;KAChB;IAED,kBAAkB,EAAE;QAClB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,kEAAkE;QAC1E,IAAI,EAAE,SAAS;KAChB;IAED,QAAQ,EAAE;QACR;YACE,OAAO,EACL,yFAAyF;gBACzF,sDAAsD;YACxD,MAAM,EAAE,0BAA0B;YAClC,IAAI,EAAE,SAAS;SAChB;QACD;YACE,OAAO,EAAE,+CAA+C;YACxD,MAAM,EAAE,qBAAqB;YAC7B,IAAI,EAAE,SAAS;SAChB;QACD;YACE,OAAO,EAAE,oEAAoE;YAC7E,MAAM,EAAE,oCAAoC;YAC5C,IAAI,EAAE,SAAS;SAChB;KACF;IAED,OAAO,EAAE;QACP;YACE,OAAO,EAAE,iEAAiE;YAC1E,MAAM,EAAE,iCAAiC;YACzC,IAAI,EAAE,SAAS;SAChB;QACD;YACE,OAAO,EAAE,uEAAuE;YAChF,MAAM,EAAE,iCAAiC;YACzC,IAAI,EAAE,SAAS;SAChB;KACF;CACF,CAAC;AAYF;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAS,EACT,YAA0B,aAAa;IAEvC,MAAM,UAAU,GAAG,UAAU,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,UAAU,YAAY,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IAClF,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,UAAU,CAAC,CAAC;IAC1D,OAAO,EAAE,GAAG,EAAE,SAAS,IAAI,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC;AAC9E,CAAC","sourcesContent":["/**\n * Single source of truth for the post-quantum standards this tool depends on.\n *\n * The scanner's credibility rests on its recommendations tracking the current\n * NIST / CNSA / IETF state. That tracking used to be ad-hoc — facts were spread\n * across `remediation.ts` with no dates, no citations, and nothing to catch code\n * drifting from the published standards. This module makes the standards facts\n * explicit, dated, and cited, and the companion drift test\n * (`test/standards.test.ts`) fails the build if the runtime constants\n * (`TIER_PARAMS`, `PQC_TRANSITION_NOTE`, `STATEFUL_HBS_NOTE`) fall out of sync\n * with what is recorded here.\n *\n * ## Cadence\n *\n * Re-verify every quarter (see `docs/standards/pqc-standards.md` for the runbook).\n * On each review: check the sources below for changes, update the facts + their\n * `asOf`, and roll `lastReviewed` / `nextReview` forward. `scripts/standards-check.mjs`\n * (advisory, runs in CI) flags when `nextReview` has passed so a review can't be\n * silently skipped. `standardsReviewStatus(now)` is the pure predicate behind it.\n */\n\n/** A single standards fact with its citation and when it was last verified. */\nexport interface StandardsCitation {\n /** One-line statement of the fact. */\n readonly summary: string;\n /** Spec identifier / publication (and URL where stable). */\n readonly source: string;\n /** `YYYY-MM` — when this fact was last verified against its source. */\n readonly asOf: string;\n}\n\n/** The full post-quantum standards snapshot the tool tracks. */\nexport interface PqcStandards {\n /** `YYYY-MM-DD` — when the whole snapshot was last reviewed. */\n readonly lastReviewed: string;\n /** `YYYY-MM-DD` — when the next review is due. */\n readonly nextReview: string;\n /** Cadence, in months, between reviews. */\n readonly reviewIntervalMonths: number;\n\n /** NIST's finalized PQC FIPS (the recommendation targets). */\n readonly fips: {\n readonly mlKem: StandardsCitation; // FIPS 203\n readonly mlDsa: StandardsCitation; // FIPS 204\n readonly slhDsa: StandardsCitation; // FIPS 205\n };\n\n /**\n * CNSA 2.0 security tiers → the KEM / signature parameter sets. These MUST\n * mirror `remediation.TIER_PARAMS`; the drift test asserts they stay identical.\n */\n readonly cnsa: {\n readonly category3: { readonly kem: string; readonly signature: string };\n readonly category5: { readonly kem: string; readonly signature: string };\n readonly source: string;\n readonly asOf: string;\n };\n\n /** Stateful hash-based signatures (firmware / boot signing). */\n readonly statefulHbs: StandardsCitation; // SP 800-208\n\n /** The migration deadline the transition note surfaces. */\n readonly transitionTimeline: {\n /** Year after which classical public-key crypto is deprecated. */\n readonly deprecateAfter: number;\n /** Year after which it is disallowed. */\n readonly disallowAfter: number;\n readonly source: string;\n readonly asOf: string;\n };\n\n /** Emerging / backup standards worth tracking beyond the current FIPS. */\n readonly emerging: readonly StandardsCitation[];\n\n /** Recommended hybrid key-exchange groups. */\n readonly hybrids: readonly StandardsCitation[];\n}\n\n/**\n * The current snapshot. Update on each quarterly review; the drift test keeps the\n * runtime remediation constants aligned with it.\n */\nexport const PQC_STANDARDS: PqcStandards = {\n lastReviewed: \"2026-07-19\",\n nextReview: \"2026-10-19\",\n reviewIntervalMonths: 3,\n\n fips: {\n mlKem: {\n summary: \"ML-KEM (Kyber) key encapsulation — finalized August 2024.\",\n source: \"NIST FIPS 203\",\n asOf: \"2026-07\",\n },\n mlDsa: {\n summary: \"ML-DSA (Dilithium) lattice signatures — finalized August 2024.\",\n source: \"NIST FIPS 204\",\n asOf: \"2026-07\",\n },\n slhDsa: {\n summary: \"SLH-DSA (SPHINCS+) stateless hash-based signatures — finalized August 2024.\",\n source: \"NIST FIPS 205\",\n asOf: \"2026-07\",\n },\n },\n\n cnsa: {\n category3: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n category5: { kem: \"ML-KEM-1024 (FIPS 203)\", signature: \"ML-DSA-87 (FIPS 204)\" },\n source: \"NSA CNSA 2.0 (national-security systems; 2030/2033 migration milestones)\",\n asOf: \"2026-07\",\n },\n\n statefulHbs: {\n summary:\n \"LMS/HSS and XMSS/XMSSMT stateful hash-based signatures (incl. the SHAKE256 and \" +\n \"192-bit parameter sets) are approved for firmware/boot signing, but are STATEFUL.\",\n source: \"NIST SP 800-208\",\n asOf: \"2026-07\",\n },\n\n transitionTimeline: {\n deprecateAfter: 2030,\n disallowAfter: 2035,\n source: \"NIST IR 8547 (transition to post-quantum cryptography standards)\",\n asOf: \"2026-07\",\n },\n\n emerging: [\n {\n summary:\n \"HQC — NIST's code-based backup KEM (selected March 2025; draft FIPS expected ~2026), a \" +\n \"diversity hedge against ML-KEM's lattice assumption.\",\n source: \"NIST PQC (HQC selection)\",\n asOf: \"2026-07\",\n },\n {\n summary: \"FN-DSA / Falcon — compact lattice signatures.\",\n source: \"NIST draft FIPS 206\",\n asOf: \"2026-07\",\n },\n {\n summary: \"X-Wing — X25519 + ML-KEM-768 hybrid KEM for HPKE-style encryption.\",\n source: \"IETF draft-connolly-cfrg-xwing-kem\",\n asOf: \"2026-07\",\n },\n ],\n\n hybrids: [\n {\n summary: \"X25519MLKEM768 — the default TLS 1.3 hybrid key-exchange group.\",\n source: \"IETF draft-ietf-tls-ecdhe-mlkem\",\n asOf: \"2026-07\",\n },\n {\n summary: \"SecP384r1MLKEM1024 — the Category-5 / CNSA hybrid key-exchange group.\",\n source: \"IETF draft-ietf-tls-ecdhe-mlkem\",\n asOf: \"2026-07\",\n },\n ],\n};\n\n/** Result of {@link standardsReviewStatus}. */\nexport interface StandardsReviewStatus {\n /** True when `now` is on or after `nextReview` — a review is due. */\n readonly due: boolean;\n /** The `nextReview` date being compared against (`YYYY-MM-DD`). */\n readonly nextReview: string;\n /** Whole days from `now` until `nextReview` (negative when overdue). */\n readonly daysUntil: number;\n}\n\n/**\n * Whether a standards review is due as of `now`. Pure (takes `now` explicitly) so\n * it is deterministic in tests; the CI script passes the real clock. Compares on\n * whole UTC days so a same-day run is not spuriously \"overdue\".\n */\nexport function standardsReviewStatus(\n now: Date,\n standards: PqcStandards = PQC_STANDARDS,\n): StandardsReviewStatus {\n const MS_PER_DAY = 86_400_000;\n const next = Date.parse(`${standards.nextReview}T00:00:00Z`);\n const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());\n const daysUntil = Math.round((next - today) / MS_PER_DAY);\n return { due: daysUntil <= 0, nextReview: standards.nextReview, daysUntil };\n}\n"]}
{"version":3,"file":"standards.js","sourceRoot":"","sources":["../src/standards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA2EH;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,YAAY,EAAE,YAAY;IAC1B,UAAU,EAAE,YAAY;IACxB,oBAAoB,EAAE,CAAC;IAEvB,IAAI,EAAE;QACJ,KAAK,EAAE;YACL,OAAO,EAAE,2DAA2D;YACpE,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,SAAS;SAChB;QACD,KAAK,EAAE;YACL,OAAO,EAAE,gEAAgE;YACzE,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,SAAS;SAChB;QACD,MAAM,EAAE;YACN,OAAO,EAAE,6EAA6E;YACtF,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE,SAAS;SAChB;KACF;IAED,IAAI,EAAE;QACJ,SAAS,EAAE,EAAE,GAAG,EAAE,uBAAuB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC9E,SAAS,EAAE,EAAE,GAAG,EAAE,wBAAwB,EAAE,SAAS,EAAE,sBAAsB,EAAE;QAC/E,MAAM,EAAE,0EAA0E;QAClF,IAAI,EAAE,SAAS;KAChB;IAED,WAAW,EAAE;QACX,OAAO,EACL,iFAAiF;YACjF,mFAAmF;QACrF,MAAM,EAAE,iBAAiB;QACzB,IAAI,EAAE,SAAS;KAChB;IAED,kBAAkB,EAAE;QAClB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,kEAAkE;QAC1E,IAAI,EAAE,SAAS;KAChB;IAED,YAAY,EAAE;QACZ,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,MAAM,EACJ,2FAA2F;QAC7F,IAAI,EAAE,SAAS;KAChB;IAED,QAAQ,EAAE;QACR;YACE,OAAO,EACL,yFAAyF;gBACzF,sDAAsD;YACxD,MAAM,EAAE,0BAA0B;YAClC,IAAI,EAAE,SAAS;SAChB;QACD;YACE,OAAO,EAAE,+CAA+C;YACxD,MAAM,EAAE,qBAAqB;YAC7B,IAAI,EAAE,SAAS;SAChB;QACD;YACE,OAAO,EAAE,oEAAoE;YAC7E,MAAM,EAAE,oCAAoC;YAC5C,IAAI,EAAE,SAAS;SAChB;KACF;IAED,OAAO,EAAE;QACP;YACE,OAAO,EAAE,iEAAiE;YAC1E,MAAM,EAAE,iCAAiC;YACzC,IAAI,EAAE,SAAS;SAChB;QACD;YACE,OAAO,EAAE,uEAAuE;YAChF,MAAM,EAAE,iCAAiC;YACzC,IAAI,EAAE,SAAS;SAChB;KACF;CACF,CAAC;AAYF;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAS,EACT,YAA0B,aAAa;IAEvC,MAAM,UAAU,GAAG,UAAU,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,UAAU,YAAY,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IAClF,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,UAAU,CAAC,CAAC;IAC1D,OAAO,EAAE,GAAG,EAAE,SAAS,IAAI,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC;AAC9E,CAAC","sourcesContent":["/**\n * Single source of truth for the post-quantum standards this tool depends on.\n *\n * The scanner's credibility rests on its recommendations tracking the current\n * NIST / CNSA / IETF state. That tracking used to be ad-hoc — facts were spread\n * across `remediation.ts` with no dates, no citations, and nothing to catch code\n * drifting from the published standards. This module makes the standards facts\n * explicit, dated, and cited, and the companion drift test\n * (`test/standards.test.ts`) fails the build if the runtime constants\n * (`TIER_PARAMS`, `PQC_TRANSITION_NOTE`, `STATEFUL_HBS_NOTE`) fall out of sync\n * with what is recorded here.\n *\n * ## Cadence\n *\n * Re-verify every quarter (see `docs/standards/pqc-standards.md` for the runbook).\n * On each review: check the sources below for changes, update the facts + their\n * `asOf`, and roll `lastReviewed` / `nextReview` forward. `scripts/standards-check.mjs`\n * (advisory, runs in CI) flags when `nextReview` has passed so a review can't be\n * silently skipped. `standardsReviewStatus(now)` is the pure predicate behind it.\n */\n\n/** A single standards fact with its citation and when it was last verified. */\nexport interface StandardsCitation {\n /** One-line statement of the fact. */\n readonly summary: string;\n /** Spec identifier / publication (and URL where stable). */\n readonly source: string;\n /** `YYYY-MM` — when this fact was last verified against its source. */\n readonly asOf: string;\n}\n\n/** The full post-quantum standards snapshot the tool tracks. */\nexport interface PqcStandards {\n /** `YYYY-MM-DD` — when the whole snapshot was last reviewed. */\n readonly lastReviewed: string;\n /** `YYYY-MM-DD` — when the next review is due. */\n readonly nextReview: string;\n /** Cadence, in months, between reviews. */\n readonly reviewIntervalMonths: number;\n\n /** NIST's finalized PQC FIPS (the recommendation targets). */\n readonly fips: {\n readonly mlKem: StandardsCitation; // FIPS 203\n readonly mlDsa: StandardsCitation; // FIPS 204\n readonly slhDsa: StandardsCitation; // FIPS 205\n };\n\n /**\n * CNSA 2.0 security tiers → the KEM / signature parameter sets. These MUST\n * mirror `remediation.TIER_PARAMS`; the drift test asserts they stay identical.\n */\n readonly cnsa: {\n readonly category3: { readonly kem: string; readonly signature: string };\n readonly category5: { readonly kem: string; readonly signature: string };\n readonly source: string;\n readonly asOf: string;\n };\n\n /** Stateful hash-based signatures (firmware / boot signing). */\n readonly statefulHbs: StandardsCitation; // SP 800-208\n\n /** The NIST IR 8547 migration deadline the transition note surfaces. */\n readonly transitionTimeline: {\n /** Year after which classical public-key crypto is deprecated. */\n readonly deprecateAfter: number;\n /** Year after which it is disallowed. */\n readonly disallowAfter: number;\n readonly source: string;\n readonly asOf: string;\n };\n\n /**\n * CNSA 2.0's OWN migration milestones — distinct from the IR 8547 timeline\n * above. CNSA 2.0 sets exclusive-use dates per system class; the `cnsa-2.0`\n * mandate encodes the earliest hard milestone as `deprecate` and the general\n * exclusive-use milestone as `disallow`. Kept separate so the two mandates\n * ({@link MANDATES}) each derive from their own dated source.\n */\n readonly cnsaTimeline: {\n /** Year after which classical PKC is deprecated (2030: software/firmware signing exclusive). */\n readonly deprecateAfter: number;\n /** Year after which it is disallowed (2033: general NSS exclusive use). */\n readonly disallowAfter: number;\n readonly source: string;\n readonly asOf: string;\n };\n\n /** Emerging / backup standards worth tracking beyond the current FIPS. */\n readonly emerging: readonly StandardsCitation[];\n\n /** Recommended hybrid key-exchange groups. */\n readonly hybrids: readonly StandardsCitation[];\n}\n\n/**\n * The current snapshot. Update on each quarterly review; the drift test keeps the\n * runtime remediation constants aligned with it.\n */\nexport const PQC_STANDARDS: PqcStandards = {\n lastReviewed: \"2026-07-19\",\n nextReview: \"2026-10-19\",\n reviewIntervalMonths: 3,\n\n fips: {\n mlKem: {\n summary: \"ML-KEM (Kyber) key encapsulation — finalized August 2024.\",\n source: \"NIST FIPS 203\",\n asOf: \"2026-07\",\n },\n mlDsa: {\n summary: \"ML-DSA (Dilithium) lattice signatures — finalized August 2024.\",\n source: \"NIST FIPS 204\",\n asOf: \"2026-07\",\n },\n slhDsa: {\n summary: \"SLH-DSA (SPHINCS+) stateless hash-based signatures — finalized August 2024.\",\n source: \"NIST FIPS 205\",\n asOf: \"2026-07\",\n },\n },\n\n cnsa: {\n category3: { kem: \"ML-KEM-768 (FIPS 203)\", signature: \"ML-DSA-65 (FIPS 204)\" },\n category5: { kem: \"ML-KEM-1024 (FIPS 203)\", signature: \"ML-DSA-87 (FIPS 204)\" },\n source: \"NSA CNSA 2.0 (national-security systems; 2030/2033 migration milestones)\",\n asOf: \"2026-07\",\n },\n\n statefulHbs: {\n summary:\n \"LMS/HSS and XMSS/XMSSMT stateful hash-based signatures (incl. the SHAKE256 and \" +\n \"192-bit parameter sets) are approved for firmware/boot signing, but are STATEFUL.\",\n source: \"NIST SP 800-208\",\n asOf: \"2026-07\",\n },\n\n transitionTimeline: {\n deprecateAfter: 2030,\n disallowAfter: 2035,\n source: \"NIST IR 8547 (transition to post-quantum cryptography standards)\",\n asOf: \"2026-07\",\n },\n\n cnsaTimeline: {\n deprecateAfter: 2030,\n disallowAfter: 2033,\n source:\n \"NSA CNSA 2.0 (exclusive-use milestones: 2030 software/firmware signing, 2033 general NSS)\",\n asOf: \"2026-07\",\n },\n\n emerging: [\n {\n summary:\n \"HQC — NIST's code-based backup KEM (selected March 2025; draft FIPS expected ~2026), a \" +\n \"diversity hedge against ML-KEM's lattice assumption.\",\n source: \"NIST PQC (HQC selection)\",\n asOf: \"2026-07\",\n },\n {\n summary: \"FN-DSA / Falcon — compact lattice signatures.\",\n source: \"NIST draft FIPS 206\",\n asOf: \"2026-07\",\n },\n {\n summary: \"X-Wing — X25519 + ML-KEM-768 hybrid KEM for HPKE-style encryption.\",\n source: \"IETF draft-connolly-cfrg-xwing-kem\",\n asOf: \"2026-07\",\n },\n ],\n\n hybrids: [\n {\n summary: \"X25519MLKEM768 — the default TLS 1.3 hybrid key-exchange group.\",\n source: \"IETF draft-ietf-tls-ecdhe-mlkem\",\n asOf: \"2026-07\",\n },\n {\n summary: \"SecP384r1MLKEM1024 — the Category-5 / CNSA hybrid key-exchange group.\",\n source: \"IETF draft-ietf-tls-ecdhe-mlkem\",\n asOf: \"2026-07\",\n },\n ],\n};\n\n/** Result of {@link standardsReviewStatus}. */\nexport interface StandardsReviewStatus {\n /** True when `now` is on or after `nextReview` — a review is due. */\n readonly due: boolean;\n /** The `nextReview` date being compared against (`YYYY-MM-DD`). */\n readonly nextReview: string;\n /** Whole days from `now` until `nextReview` (negative when overdue). */\n readonly daysUntil: number;\n}\n\n/**\n * Whether a standards review is due as of `now`. Pure (takes `now` explicitly) so\n * it is deterministic in tests; the CI script passes the real clock. Compares on\n * whole UTC days so a same-day run is not spuriously \"overdue\".\n */\nexport function standardsReviewStatus(\n now: Date,\n standards: PqcStandards = PQC_STANDARDS,\n): StandardsReviewStatus {\n const MS_PER_DAY = 86_400_000;\n const next = Date.parse(`${standards.nextReview}T00:00:00Z`);\n const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());\n const daysUntil = Math.round((next - today) / MS_PER_DAY);\n return { due: daysUntil <= 0, nextReview: standards.nextReview, daysUntil };\n}\n"]}

@@ -6,3 +6,3 @@ /**

*/
export declare const VERSION = "0.8.0";
export declare const VERSION = "0.9.0";
//# sourceMappingURL=version.d.ts.map

@@ -6,3 +6,3 @@ /**

*/
export const VERSION = "0.8.0";
export const VERSION = "0.9.0";
//# sourceMappingURL=version.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC","sourcesContent":["/**\n * The tool version surfaced in reports. Kept in its own module so reporters and\n * the scan orchestrator can import it without creating a cycle through index.ts.\n * Keep in sync with packages/core/package.json.\n */\nexport const VERSION = \"0.8.0\";\n"]}
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC","sourcesContent":["/**\n * The tool version surfaced in reports. Kept in its own module so reporters and\n * the scan orchestrator can import it without creating a cycle through index.ts.\n * Keep in sync with packages/core/package.json.\n */\nexport const VERSION = \"0.9.0\";\n"]}
{
"name": "@quantakrypto/core",
"version": "0.8.0",
"version": "0.9.0",
"description": "Shared post-quantum readiness library: crypto detectors, vulnerable-dependency database, inventory + SARIF reporting. Zero runtime dependencies.",

@@ -5,0 +5,0 @@ "license": "Apache-2.0",

@@ -26,2 +26,3 @@ /**

import type { CryptoPolicy, PolicyMapping } from "./policy.js";
import type { MandateEvaluation } from "./mandates.js";

@@ -52,2 +53,10 @@ /** Stable per-finding record for the evidence body (deterministic per commit). */

policyMapping?: PolicyMapping;
/**
* Compliance-mandate verdicts (`--mandate`), present only when mandates were
* evaluated. DATE-PINNED for reproducibility: its `now` is stored as a plain
* `YYYY-MM-DD` (not the volatile scan timestamp) so the same scan of the same
* commit ON THE SAME DAY yields the same attestation hash, while a genuinely
* different compliance date (a passed deadline) correctly changes it.
*/
mandateMapping?: MandateEvaluation;
cbom: unknown;

@@ -95,2 +104,8 @@ attestation: {

policy?: CryptoPolicy;
/**
* Optional compliance-mandate evaluation ({@link evaluateMandates}) — adds the
* `mandateMapping` block. Date-pinned into the hashed body (see
* {@link ReadinessReport.mandateMapping}).
*/
mandate?: MandateEvaluation;
}

@@ -124,2 +139,12 @@

// Compliance mandates: attest the dated verdicts too. The evaluation is a pure
// function of (findings, date), and the findings are already hashed — so we
// DATE-PIN its `now` to a plain `YYYY-MM-DD` (dropping the volatile scan
// timestamp) and hash that. Two runs on the same commit ON THE SAME DAY then
// reproduce; a run after a deadline has passed correctly attests a different
// status (and a different hash).
const mandateMapping = opts.mandate
? { ...opts.mandate, now: opts.mandate.now.slice(0, 10) }
: undefined;
const hashableBody = {

@@ -137,2 +162,3 @@ reportType: "quantakrypto-readiness",

...(policyMapping ? { policyMapping } : {}),
...(mandateMapping ? { mandateMapping } : {}),
};

@@ -195,2 +221,3 @@ const contentHash =

...(report.policyMapping ? { policyMapping: report.policyMapping } : {}),
...(report.mandateMapping ? { mandateMapping: report.mandateMapping } : {}),
};

@@ -197,0 +224,0 @@ const computedHash =

@@ -6,3 +6,3 @@ /**

* date-blind. A mandate adds the missing dimension: named clauses with an effective
* DATE ("CNSA 2.0 disallows classical public-key crypto after 2035"). The evaluator
* DATE ("CNSA 2.0 disallows classical public-key crypto after 2033"). The evaluator
* compares each finding's algorithm against the selected mandates and today's date,

@@ -25,2 +25,4 @@ * so a finding on a prohibited family reads as `due` (every deadline still ahead),

import { PQC_STANDARDS } from "./standards.js";
import { verdictForAlgorithm } from "./policy.js";
import type { CryptoPolicy, PolicyVerdict } from "./policy.js";

@@ -59,5 +61,8 @@ /**

/**
* Effective dates derived from the standards source of truth
* (`PQC_STANDARDS.transitionTimeline`), so a quarterly standards update moves the
* mandate deadlines automatically (test/standards.test.ts asserts they agree).
* Effective dates derived from the standards source of truth, so a quarterly
* standards update moves the mandate deadlines automatically (test/standards.test.ts
* asserts they agree). Each regime uses its OWN dated timeline: NIST IR 8547
* disallows after 2035, while CNSA 2.0 sets its general exclusive-use milestone
* at 2033 (both deprecate after 2030) — so the two mandates carry different
* disallow years rather than sharing one.
*

@@ -69,5 +74,8 @@ * Boundary choice: "deprecate AFTER 2030" leaves the whole stated year permitted,

*/
const { deprecateAfter, disallowAfter } = PQC_STANDARDS.transitionTimeline;
const DEPRECATE_EFFECTIVE = `${deprecateAfter}-12-31`;
const DISALLOW_EFFECTIVE = `${disallowAfter}-12-31`;
const IR8547 = PQC_STANDARDS.transitionTimeline; // 2030 deprecate / 2035 disallow
const CNSA = PQC_STANDARDS.cnsaTimeline; // 2030 deprecate / 2033 disallow
const NIST_DEPRECATE_EFFECTIVE = `${IR8547.deprecateAfter}-12-31`;
const NIST_DISALLOW_EFFECTIVE = `${IR8547.disallowAfter}-12-31`;
const CNSA_DEPRECATE_EFFECTIVE = `${CNSA.deprecateAfter}-12-31`;
const CNSA_DISALLOW_EFFECTIVE = `${CNSA.disallowAfter}-12-31`;

@@ -110,14 +118,14 @@ /** Which enforcement tier a clause encodes: warn (`deprecate`) or fail (`disallow`). */

{
clause: `CNSA 2.0 — deprecate classical PKC after ${deprecateAfter}`,
clause: `CNSA 2.0 — deprecate classical PKC after ${CNSA.deprecateAfter}`,
tier: "deprecate",
prohibits: [...PROHIBITED_FAMILIES],
effective: DEPRECATE_EFFECTIVE,
note: "Classical public-key cryptography deprecated; systems should use CNSA 2.0 PQC exclusively.",
effective: CNSA_DEPRECATE_EFFECTIVE,
note: `Classical public-key cryptography deprecated (${CNSA.deprecateAfter}: software/firmware signing exclusive-use); systems should use CNSA 2.0 PQC.`,
},
{
clause: `CNSA 2.0 — disallow classical PKC after ${disallowAfter}`,
clause: `CNSA 2.0 — disallow classical PKC after ${CNSA.disallowAfter}`,
tier: "disallow",
prohibits: [...PROHIBITED_FAMILIES],
effective: DISALLOW_EFFECTIVE,
note: "Classical public-key cryptography disallowed; the migration must be complete.",
effective: CNSA_DISALLOW_EFFECTIVE,
note: `Classical public-key cryptography disallowed (${CNSA.disallowAfter}: general NSS exclusive-use milestone); the migration must be complete.`,
},

@@ -134,14 +142,14 @@ ],

{
clause: `NIST IR 8547 — deprecate classical PKC after ${deprecateAfter}`,
clause: `NIST IR 8547 — deprecate classical PKC after ${IR8547.deprecateAfter}`,
tier: "deprecate",
prohibits: [...PROHIBITED_FAMILIES],
effective: DEPRECATE_EFFECTIVE,
note: `112-bit-security classical public-key algorithms deprecated after ${deprecateAfter}.`,
effective: NIST_DEPRECATE_EFFECTIVE,
note: `112-bit-security classical public-key algorithms deprecated after ${IR8547.deprecateAfter}.`,
},
{
clause: `NIST IR 8547 — disallow classical PKC after ${disallowAfter}`,
clause: `NIST IR 8547 — disallow classical PKC after ${IR8547.disallowAfter}`,
tier: "disallow",
prohibits: [...PROHIBITED_FAMILIES],
effective: DISALLOW_EFFECTIVE,
note: `Classical public-key algorithms disallowed after ${disallowAfter}.`,
effective: NIST_DISALLOW_EFFECTIVE,
note: `Classical public-key algorithms disallowed after ${IR8547.disallowAfter}.`,
},

@@ -206,2 +214,17 @@ ],

citation: string;
/**
* The org cryptography policy's verdict on this algorithm family when a policy
* was composed in via {@link evaluateMandates}' `policy` argument, else null.
* Purely informational — it records the org's own stance next to the mandate's
* dated clause so a machine-readable report shows both.
*/
policyVerdict: PolicyVerdict | null;
/**
* True when the org policy EXPLICITLY permits or is transitioning this family —
* an owned, tracked decision. Acknowledged findings are exempt from the EARLY
* gates (`failNow` / `leadMonths`); a passed DISALLOW deadline (`violation`)
* still fails regardless, because an org cannot self-exempt from a dated legal
* disallow. `false` when no policy was supplied.
*/
acknowledged: boolean;
}

@@ -231,2 +254,11 @@

hasViolation: boolean;
/** Name of the org policy composed in via `policy`, or null when none was supplied. */
policyName: string | null;
/**
* How many distinct prohibited FINDINGS the org policy explicitly acknowledged
* (family listed as `permitted` or `inTransition`) — counted per finding, not
* per verdict row, so a family prohibited by two mandates counts once, matching
* the per-finding `summary`. 0 when no policy was supplied.
*/
acknowledged: number;
}

@@ -248,2 +280,20 @@

/**
* True when the org policy EXPLICITLY accepts a family — listed in `permitted`
* (an owned exception) or `inTransition` (a tracked migration). A `prohibited`
* family or one covered only by the policy's default fallback is NOT
* acknowledged: silence is not consent, so an unnamed family never earns a gate
* exemption.
*
* `prohibited` takes precedence, matching {@link verdictForAlgorithm}: a policy
* that lists a family in BOTH `prohibited` and `permitted` (a plausible merge of
* two policy fragments) resolves to `violation`, and must not then be silently
* acknowledged away — that would produce a self-contradictory verdict (verdict
* `violation`, yet exempt from the gate).
*/
function policyAcknowledges(algo: AlgorithmFamily, policy: CryptoPolicy): boolean {
if (policy.prohibited?.includes(algo)) return false;
return Boolean(policy.permitted?.includes(algo) || policy.inTransition?.includes(algo));
}
/**
* Evaluate findings against the selected mandates as of `now`. Unknown mandate

@@ -254,2 +304,9 @@ * ids are ignored — callers validate up front with {@link assertKnownMandates}.

* contributes a verdict row.
*
* When an org `policy` is supplied (the `--policy` composition), every verdict
* row is annotated with the org's own `policyVerdict` and an `acknowledged` flag
* (family explicitly permitted / in-transition). Acknowledgement is purely
* additive here — it changes no status — but {@link mandateGateFails} honours it
* to keep the early gates from double-flagging crypto the org is knowingly,
* traceably managing. A passed DISALLOW deadline is never acknowledgeable away.
*/

@@ -260,4 +317,14 @@ export function evaluateMandates(

now: Date,
policy?: CryptoPolicy,
): MandateEvaluation {
const nowMs = now.getTime();
// A compliance verdict is as-of a DAY: the clauses take effect on date
// boundaries (YYYY-MM-DD), so the exact clock time carries no compliance
// meaning. Pin `now` to UTC midnight of its date before any arithmetic — this
// makes the whole evaluation (statuses AND the monthsUntil / monthsUntilDisallow
// counters) identical for any two runs on the same day, which is what keeps the
// attested evidence hash reproducible per commit per day. Truncating changes no
// status: every `effective` date is itself UTC-midnight, so `nowMs >= effMs` has
// the same truth value at midnight as at any other time that day.
const nowMs = Date.parse(`${now.toISOString().slice(0, 10)}T00:00:00.000Z`);
const nowIso = new Date(nowMs).toISOString();
const selected = mandateIdList.map(getMandate).filter((m): m is Mandate => Boolean(m));

@@ -268,2 +335,3 @@

let notInScope = 0;
let acknowledged = 0;
let nextDeadlineMs: number | null = null;

@@ -278,2 +346,9 @@

let worst: MandateStatus = "conformant";
// Acknowledgement is a property of the FAMILY (fixed for this finding), so it
// is computed once here and stamped on every row. The tally counts distinct
// acknowledged findings (not rows), so one family under two mandates is one
// acknowledgement, matching the per-finding status counts in `summary`.
const family = algo as AlgorithmFamily;
const isAcknowledged = policy ? policyAcknowledges(family, policy) : false;
let producedRow = false;
for (const mandate of selected) {

@@ -285,2 +360,3 @@ // The applicable clauses for this family, earliest deadline first.

if (applicable.length === 0) continue;
producedRow = true;

@@ -331,4 +407,9 @@ // Tier the clauses so both stay live: a passed DISALLOW clause is a

citation: mandate.citation,
policyVerdict: policy ? verdictForAlgorithm(family, policy).verdict : null,
acknowledged: isAcknowledged,
});
}
// Count the acknowledged FINDING once (it produced at least one prohibited
// row and the org policy owns/tracks its family), not once per mandate row.
if (producedRow && isAcknowledged) acknowledged++;
perFindingWorst.push(worst);

@@ -346,3 +427,3 @@ }

return {
now: now.toISOString(),
now: nowIso,
mandates: selected.map((m) => m.id),

@@ -355,2 +436,4 @@ summary,

hasViolation: summary.violation > 0,
policyName: policy?.name ?? null,
acknowledged,
};

@@ -372,8 +455,17 @@ }

* immediately.
*
* Policy composition: when a finding was `acknowledged` by the org policy
* (`--policy`), it is exempt from the EARLY gates (`failNow` / `leadMonths`) —
* the org is knowingly, traceably managing that family, so its own early
* enforcement should not re-flag it. A passed DISALLOW deadline (`violation`)
* still fails regardless: a dated legal disallow is not something an org can
* self-exempt from.
*/
export function mandateGateFails(ev: MandateEvaluation, opts: MandateGateOptions = {}): boolean {
if (ev.hasViolation) return true;
if (opts.failNow) return ev.findings.length > 0;
// Early gates skip policy-acknowledged findings; the hard `violation` above did not.
const gated = ev.findings.filter((v) => !v.acknowledged);
if (opts.failNow) return gated.length > 0;
if (opts.leadMonths !== undefined) {
return ev.findings.some(
return gated.some(
(v) => v.monthsUntilDisallow !== null && v.monthsUntilDisallow <= opts.leadMonths!,

@@ -380,0 +472,0 @@ );

@@ -16,2 +16,3 @@ /**

import type { FindingExposure, HndlReport } from "./hndl.js";
import type { MandateEvaluation } from "./mandates.js";

@@ -49,2 +50,10 @@ /** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */

hndl?: HndlReport;
/**
* Optional compliance-mandate evaluation ({@link evaluateMandates}). When
* supplied, the JSON report carries a top-level `mandateMapping` block and the
* SARIF run carries the same under `run.properties.mandate` — the
* machine-readable half of the `--mandate` gate for CI consumption. Purely
* additive: it never changes finding identity, ordering, or exit codes.
*/
mandate?: MandateEvaluation;
}

@@ -264,2 +273,9 @@

// Run-level properties bag: the repo HNDL summary and/or the compliance-mandate
// evaluation, whichever were supplied. Both are additive metadata for SARIF
// consumers (our platform ingest, CI) and never affect result identity.
const runProperties: Record<string, unknown> = {};
if (opts?.hndl) runProperties.hndl = hndlSummaryBlock(opts.hndl);
if (opts?.mandate) runProperties.mandate = opts.mandate;
return {

@@ -279,3 +295,3 @@ $schema: SARIF_SCHEMA,

...(taxonomies.length > 0 ? { taxonomies } : {}),
...(opts?.hndl ? { properties: { hndl: hndlSummaryBlock(opts.hndl) } } : {}),
...(Object.keys(runProperties).length > 0 ? { properties: runProperties } : {}),
results,

@@ -323,2 +339,7 @@ },

...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),
// Compliance-mandate evaluation (`--mandate`): the machine-readable verdicts
// + summary, so a CI job can gate/report on them without re-parsing the human
// block. Carries the org `--policy` composition (policyVerdict / acknowledged)
// when one was supplied.
...(opts?.mandate ? { mandateMapping: opts.mandate } : {}),
findings: result.findings.map((f) => {

@@ -325,0 +346,0 @@ const exposure = exposureFor(f, hndl);

@@ -17,2 +17,4 @@ /**

import { PQC_STANDARDS } from "./standards.js";
/** Whether classical+PQC hybridization is required during the transition, per regime. */

@@ -62,4 +64,6 @@ export type HybridStance = "required" | "recommended" | "optional";

"Hybrid key establishment (e.g. X25519MLKEM768) is permitted and recommended during the transition; pure ML-KEM is also acceptable (SP 800-227 / IR 8547).",
deprecateAfter: 2030,
disallowAfter: 2035,
// Derived from the single source of truth so the profile can never drift from
// the `nist-ir-8547` mandate gate (the standards drift test asserts this).
deprecateAfter: PQC_STANDARDS.transitionTimeline.deprecateAfter,
disallowAfter: PQC_STANDARDS.transitionTimeline.disallowAfter,
citation: "NIST IR 8547 + FIPS 203/204/205",

@@ -76,5 +80,8 @@ asOf: "2026-07",

"CNSA 2.0 targets pure PQC and does not require hybrids; if a hybrid TLS group is used, it must be SecP384r1MLKEM1024 — X25519MLKEM768's ML-KEM-768 component is sub-CNSA.",
deprecateAfter: 2030,
disallowAfter: 2035,
citation: "NSA CNSA 2.0 (2030/2033/2035 migration milestones)",
// CNSA 2.0 carries its OWN exclusive-use milestones (2030 software/firmware
// signing, 2033 general NSS) — NOT IR 8547's 2035. Derived from the single
// source so `--profile cnsa-2.0` and `--mandate cnsa-2.0` always agree.
deprecateAfter: PQC_STANDARDS.cnsaTimeline.deprecateAfter,
disallowAfter: PQC_STANDARDS.cnsaTimeline.disallowAfter,
citation: "NSA CNSA 2.0 (2030 deprecate / 2033 disallow exclusive-use milestones)",
asOf: "2026-07",

@@ -81,0 +88,0 @@ },

@@ -62,3 +62,3 @@ /**

/** The migration deadline the transition note surfaces. */
/** The NIST IR 8547 migration deadline the transition note surfaces. */
readonly transitionTimeline: {

@@ -73,2 +73,18 @@ /** Year after which classical public-key crypto is deprecated. */

/**
* CNSA 2.0's OWN migration milestones — distinct from the IR 8547 timeline
* above. CNSA 2.0 sets exclusive-use dates per system class; the `cnsa-2.0`
* mandate encodes the earliest hard milestone as `deprecate` and the general
* exclusive-use milestone as `disallow`. Kept separate so the two mandates
* ({@link MANDATES}) each derive from their own dated source.
*/
readonly cnsaTimeline: {
/** Year after which classical PKC is deprecated (2030: software/firmware signing exclusive). */
readonly deprecateAfter: number;
/** Year after which it is disallowed (2033: general NSS exclusive use). */
readonly disallowAfter: number;
readonly source: string;
readonly asOf: string;
};
/** Emerging / backup standards worth tracking beyond the current FIPS. */

@@ -130,2 +146,10 @@ readonly emerging: readonly StandardsCitation[];

cnsaTimeline: {
deprecateAfter: 2030,
disallowAfter: 2033,
source:
"NSA CNSA 2.0 (exclusive-use milestones: 2030 software/firmware signing, 2033 general NSS)",
asOf: "2026-07",
},
emerging: [

@@ -132,0 +156,0 @@ {

@@ -6,2 +6,2 @@ /**

*/
export const VERSION = "0.8.0";
export const VERSION = "0.9.0";