@quantakrypto/core 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -27
- package/dist/cbom-merge.d.ts +9 -0
- package/dist/cbom-merge.d.ts.map +1 -0
- package/dist/cbom-merge.js +121 -0
- package/dist/cbom-merge.js.map +1 -0
- package/dist/cbom.d.ts +5 -0
- package/dist/cbom.d.ts.map +1 -1
- package/dist/cbom.js +132 -37
- package/dist/cbom.js.map +1 -1
- package/dist/comments.d.ts +6 -26
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +77 -38
- package/dist/comments.js.map +1 -1
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +1 -1
- package/dist/dependencies.js.map +1 -1
- package/dist/detect-utils.d.ts +59 -21
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +143 -8
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/age.d.ts +16 -0
- package/dist/detectors/age.d.ts.map +1 -0
- package/dist/detectors/age.js +40 -0
- package/dist/detectors/age.js.map +1 -0
- package/dist/detectors/ansible.d.ts +13 -0
- package/dist/detectors/ansible.d.ts.map +1 -0
- package/dist/detectors/ansible.js +108 -0
- package/dist/detectors/ansible.js.map +1 -0
- package/dist/detectors/bicep.d.ts +20 -0
- package/dist/detectors/bicep.d.ts.map +1 -0
- package/dist/detectors/bicep.js +74 -0
- package/dist/detectors/bicep.js.map +1 -0
- package/dist/detectors/c.d.ts +4 -2
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +222 -4
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts +27 -0
- package/dist/detectors/cicd.d.ts.map +1 -0
- package/dist/detectors/cicd.js +129 -0
- package/dist/detectors/cicd.js.map +1 -0
- package/dist/detectors/cloud-kms.d.ts +24 -0
- package/dist/detectors/cloud-kms.d.ts.map +1 -0
- package/dist/detectors/cloud-kms.js +99 -0
- package/dist/detectors/cloud-kms.js.map +1 -0
- package/dist/detectors/cloudformation.d.ts +38 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -0
- package/dist/detectors/cloudformation.js +201 -0
- package/dist/detectors/cloudformation.js.map +1 -0
- package/dist/detectors/codesign.d.ts +50 -0
- package/dist/detectors/codesign.d.ts.map +1 -0
- package/dist/detectors/codesign.js +143 -0
- package/dist/detectors/codesign.js.map +1 -0
- package/dist/detectors/csharp.d.ts.map +1 -1
- package/dist/detectors/csharp.js +13 -3
- package/dist/detectors/csharp.js.map +1 -1
- package/dist/detectors/dart.d.ts +35 -0
- package/dist/detectors/dart.d.ts.map +1 -0
- package/dist/detectors/dart.js +177 -0
- package/dist/detectors/dart.js.map +1 -0
- package/dist/detectors/database.d.ts +19 -0
- package/dist/detectors/database.d.ts.map +1 -0
- package/dist/detectors/database.js +97 -0
- package/dist/detectors/database.js.map +1 -0
- package/dist/detectors/dkim.d.ts +54 -0
- package/dist/detectors/dkim.d.ts.map +1 -0
- package/dist/detectors/dkim.js +92 -0
- package/dist/detectors/dkim.js.map +1 -0
- package/dist/detectors/dnssec.d.ts +55 -0
- package/dist/detectors/dnssec.d.ts.map +1 -0
- package/dist/detectors/dnssec.js +116 -0
- package/dist/detectors/dnssec.js.map +1 -0
- package/dist/detectors/elixir.d.ts +20 -0
- package/dist/detectors/elixir.d.ts.map +1 -0
- package/dist/detectors/elixir.js +227 -0
- package/dist/detectors/elixir.js.map +1 -0
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +34 -4
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts +20 -0
- package/dist/detectors/jose.d.ts.map +1 -0
- package/dist/detectors/jose.js +72 -0
- package/dist/detectors/jose.js.map +1 -0
- package/dist/detectors/jwk.d.ts +25 -0
- package/dist/detectors/jwk.d.ts.map +1 -0
- package/dist/detectors/jwk.js +131 -0
- package/dist/detectors/jwk.js.map +1 -0
- package/dist/detectors/k8s.d.ts +17 -0
- package/dist/detectors/k8s.d.ts.map +1 -0
- package/dist/detectors/k8s.js +88 -0
- package/dist/detectors/k8s.js.map +1 -0
- package/dist/detectors/keystore.d.ts +19 -0
- package/dist/detectors/keystore.d.ts.map +1 -0
- package/dist/detectors/keystore.js +76 -0
- package/dist/detectors/keystore.js.map +1 -0
- package/dist/detectors/mesh.d.ts +34 -0
- package/dist/detectors/mesh.d.ts.map +1 -0
- package/dist/detectors/mesh.js +91 -0
- package/dist/detectors/mesh.js.map +1 -0
- package/dist/detectors/messaging.d.ts +16 -0
- package/dist/detectors/messaging.d.ts.map +1 -0
- package/dist/detectors/messaging.js +79 -0
- package/dist/detectors/messaging.js.map +1 -0
- package/dist/detectors/objc.d.ts +41 -0
- package/dist/detectors/objc.d.ts.map +1 -0
- package/dist/detectors/objc.js +146 -0
- package/dist/detectors/objc.js.map +1 -0
- package/dist/detectors/openpgp.d.ts +20 -0
- package/dist/detectors/openpgp.d.ts.map +1 -0
- package/dist/detectors/openpgp.js +151 -0
- package/dist/detectors/openpgp.js.map +1 -0
- package/dist/detectors/pem.d.ts.map +1 -1
- package/dist/detectors/pem.js +33 -1
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +25 -0
- package/dist/detectors/php.d.ts.map +1 -0
- package/dist/detectors/php.js +171 -0
- package/dist/detectors/php.js.map +1 -0
- package/dist/detectors/pkcs11.d.ts +17 -0
- package/dist/detectors/pkcs11.d.ts.map +1 -0
- package/dist/detectors/pkcs11.js +87 -0
- package/dist/detectors/pkcs11.js.map +1 -0
- package/dist/detectors/proxy.d.ts +52 -0
- package/dist/detectors/proxy.d.ts.map +1 -0
- package/dist/detectors/proxy.js +157 -0
- package/dist/detectors/proxy.js.map +1 -0
- package/dist/detectors/pulumi.d.ts +19 -0
- package/dist/detectors/pulumi.d.ts.map +1 -0
- package/dist/detectors/pulumi.js +80 -0
- package/dist/detectors/pulumi.js.map +1 -0
- package/dist/detectors/python.d.ts.map +1 -1
- package/dist/detectors/python.js +70 -0
- package/dist/detectors/python.js.map +1 -1
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +73 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +24 -0
- package/dist/detectors/secrets.d.ts.map +1 -0
- package/dist/detectors/secrets.js +63 -0
- package/dist/detectors/secrets.js.map +1 -0
- package/dist/detectors/solidity.d.ts +50 -0
- package/dist/detectors/solidity.d.ts.map +1 -0
- package/dist/detectors/solidity.js +108 -0
- package/dist/detectors/solidity.js.map +1 -0
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +243 -53
- package/dist/detectors/source.js.map +1 -1
- package/dist/detectors/spire.d.ts +49 -0
- package/dist/detectors/spire.d.ts.map +1 -0
- package/dist/detectors/spire.js +77 -0
- package/dist/detectors/spire.js.map +1 -0
- package/dist/detectors/ssh-ca.d.ts +72 -0
- package/dist/detectors/ssh-ca.d.ts.map +1 -0
- package/dist/detectors/ssh-ca.js +119 -0
- package/dist/detectors/ssh-ca.js.map +1 -0
- package/dist/detectors/stateful-hbs.d.ts.map +1 -1
- package/dist/detectors/stateful-hbs.js +9 -6
- package/dist/detectors/stateful-hbs.js.map +1 -1
- package/dist/detectors/supply-chain.d.ts +17 -0
- package/dist/detectors/supply-chain.d.ts.map +1 -0
- package/dist/detectors/supply-chain.js +89 -0
- package/dist/detectors/supply-chain.js.map +1 -0
- package/dist/detectors/swift.d.ts +22 -0
- package/dist/detectors/swift.d.ts.map +1 -0
- package/dist/detectors/swift.js +120 -0
- package/dist/detectors/swift.js.map +1 -0
- package/dist/detectors/terraform.d.ts +21 -0
- package/dist/detectors/terraform.d.ts.map +1 -0
- package/dist/detectors/terraform.js +150 -0
- package/dist/detectors/terraform.js.map +1 -0
- package/dist/detectors/vault.d.ts +15 -0
- package/dist/detectors/vault.d.ts.map +1 -0
- package/dist/detectors/vault.js +97 -0
- package/dist/detectors/vault.js.map +1 -0
- package/dist/detectors/vpn.d.ts +22 -0
- package/dist/detectors/vpn.d.ts.map +1 -0
- package/dist/detectors/vpn.js +73 -0
- package/dist/detectors/vpn.js.map +1 -0
- package/dist/detectors/weak-hash.d.ts +71 -0
- package/dist/detectors/weak-hash.d.ts.map +1 -0
- package/dist/detectors/weak-hash.js +116 -0
- package/dist/detectors/weak-hash.js.map +1 -0
- package/dist/detectors/webauthn.d.ts +61 -0
- package/dist/detectors/webauthn.d.ts.map +1 -0
- package/dist/detectors/webauthn.js +109 -0
- package/dist/detectors/webauthn.js.map +1 -0
- package/dist/detectors/xmldsig.d.ts +23 -0
- package/dist/detectors/xmldsig.d.ts.map +1 -0
- package/dist/detectors/xmldsig.js +84 -0
- package/dist/detectors/xmldsig.js.map +1 -0
- package/dist/evidence.d.ts +79 -4
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +76 -0
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +17 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -9
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +0 -2
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +15 -6
- package/dist/inventory.js.map +1 -1
- package/dist/policy.d.ts +67 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/registry.d.ts +9 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +81 -4
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts +0 -3
- package/dist/remediate-pipeline.d.ts.map +1 -1
- package/dist/remediate-pipeline.js +23 -2
- package/dist/remediate-pipeline.js.map +1 -1
- package/dist/remediation.d.ts +10 -0
- package/dist/remediation.d.ts.map +1 -1
- package/dist/remediation.js +32 -0
- package/dist/remediation.js.map +1 -1
- package/dist/report.d.ts +9 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +38 -1
- package/dist/report.js.map +1 -1
- package/dist/scan-worker.js +5 -3
- package/dist/scan-worker.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +35 -6
- package/dist/scan.js.map +1 -1
- package/dist/standards-profiles.d.ts +58 -0
- package/dist/standards-profiles.d.ts.map +1 -0
- package/dist/standards-profiles.js +100 -0
- package/dist/standards-profiles.js.map +1 -0
- package/dist/standards.d.ts +96 -0
- package/dist/standards.d.ts.map +1 -0
- package/dist/standards.js +107 -0
- package/dist/standards.js.map +1 -0
- package/dist/types.d.ts +6 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/vex.d.ts +39 -0
- package/dist/vex.d.ts.map +1 -0
- package/dist/vex.js +87 -0
- package/dist/vex.js.map +1 -0
- package/dist/walk.d.ts +8 -3
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +32 -1
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/cbom-merge.ts +146 -0
- package/src/cbom.ts +153 -39
- package/src/comments.ts +84 -39
- package/src/dependencies.ts +2 -1
- package/src/detect-utils.ts +146 -12
- package/src/detectors/age.ts +60 -0
- package/src/detectors/ansible.ts +140 -0
- package/src/detectors/bicep.ts +107 -0
- package/src/detectors/c.ts +240 -6
- package/src/detectors/cicd.ts +171 -0
- package/src/detectors/cloud-kms.ts +141 -0
- package/src/detectors/cloudformation.ts +250 -0
- package/src/detectors/codesign.ts +214 -0
- package/src/detectors/csharp.ts +13 -3
- package/src/detectors/dart.ts +237 -0
- package/src/detectors/database.ts +141 -0
- package/src/detectors/dkim.ts +172 -0
- package/src/detectors/dnssec.ts +195 -0
- package/src/detectors/elixir.ts +268 -0
- package/src/detectors/java.ts +39 -4
- package/src/detectors/jose.ts +110 -0
- package/src/detectors/jwk.ts +170 -0
- package/src/detectors/k8s.ts +115 -0
- package/src/detectors/keystore.ts +109 -0
- package/src/detectors/mesh.ts +130 -0
- package/src/detectors/messaging.ts +106 -0
- package/src/detectors/objc.ts +209 -0
- package/src/detectors/openpgp.ts +194 -0
- package/src/detectors/pem.ts +31 -1
- package/src/detectors/php.ts +230 -0
- package/src/detectors/pkcs11.ts +113 -0
- package/src/detectors/proxy.ts +236 -0
- package/src/detectors/pulumi.ts +106 -0
- package/src/detectors/python.ts +71 -0
- package/src/detectors/rust.ts +76 -0
- package/src/detectors/secrets.ts +103 -0
- package/src/detectors/solidity.ts +176 -0
- package/src/detectors/source.ts +283 -73
- package/src/detectors/spire.ts +143 -0
- package/src/detectors/ssh-ca.ts +215 -0
- package/src/detectors/stateful-hbs.ts +9 -6
- package/src/detectors/supply-chain.ts +117 -0
- package/src/detectors/swift.ts +149 -0
- package/src/detectors/terraform.ts +188 -0
- package/src/detectors/vault.ts +120 -0
- package/src/detectors/vpn.ts +103 -0
- package/src/detectors/weak-hash.ts +217 -0
- package/src/detectors/webauthn.ts +205 -0
- package/src/detectors/xmldsig.ts +115 -0
- package/src/evidence.ts +138 -4
- package/src/index.ts +50 -12
- package/src/inventory.ts +15 -7
- package/src/policy.ts +180 -0
- package/src/registry.ts +81 -4
- package/src/remediate-pipeline.ts +26 -3
- package/src/remediation.ts +39 -0
- package/src/report.ts +43 -1
- package/src/scan-worker.ts +5 -3
- package/src/scan.ts +41 -6
- package/src/standards-profiles.ts +136 -0
- package/src/standards.ts +186 -0
- package/src/types.ts +11 -4
- package/src/version.ts +1 -1
- package/src/vex.ts +122 -0
- package/src/walk.ts +33 -2
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selectable STANDARDS PROFILES — the regime a scan's remediation, deadlines, and
|
|
3
|
+
* hybrid guidance are tailored to. Different national/regional authorities agree on
|
|
4
|
+
* the PQC primitives (ML-KEM / ML-DSA) but diverge on two things this tool must not
|
|
5
|
+
* hardcode: (a) the required PARAMETER SETS (a commercial ML-KEM-768 vs a
|
|
6
|
+
* national-security ML-KEM-1024), and (b) the HYBRID STANCE — whether classical+PQC
|
|
7
|
+
* hybridization is required, recommended, or optional during the transition. Baking
|
|
8
|
+
* in NIST/CNSA's "hybrids optional" is wrong for an ANSSI or BSI audience, where
|
|
9
|
+
* hybrid is required. `--profile <id>` selects the regime; `--policy` composes an
|
|
10
|
+
* org's own exceptions on top.
|
|
11
|
+
*
|
|
12
|
+
* Like {@link PqcStandards}, every profile carries a citation + `asOf` date and is
|
|
13
|
+
* re-verified on the quarterly standards cadence. These are guidance summaries, not
|
|
14
|
+
* legal advice — consult the cited source of record.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Whether classical+PQC hybridization is required during the transition, per regime. */
|
|
18
|
+
export type HybridStance = "required" | "recommended" | "optional";
|
|
19
|
+
|
|
20
|
+
/** A regime's PQC guidance profile. */
|
|
21
|
+
export interface StandardsProfile {
|
|
22
|
+
/** Stable id used by `--profile` (kebab-case). */
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** Human-readable name. */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/** The authority / document of record. */
|
|
27
|
+
readonly authority: string;
|
|
28
|
+
/** The KEM / signature parameter sets this regime calls for. */
|
|
29
|
+
readonly paramSets: { readonly kem: string; readonly signature: string };
|
|
30
|
+
/** Whether hybridization is required / recommended / optional under this regime. */
|
|
31
|
+
readonly hybridStance: HybridStance;
|
|
32
|
+
/** One-line regime-specific hybrid guidance surfaced in remediation. */
|
|
33
|
+
readonly hybridGuidance: string;
|
|
34
|
+
/** Year after which classical public-key crypto is deprecated under this regime. */
|
|
35
|
+
readonly deprecateAfter: number;
|
|
36
|
+
/** Year after which it is disallowed. */
|
|
37
|
+
readonly disallowAfter: number;
|
|
38
|
+
/** Spec identifier / publication of record. */
|
|
39
|
+
readonly citation: string;
|
|
40
|
+
/** `YYYY-MM` — when this profile was last verified against its source. */
|
|
41
|
+
readonly asOf: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The default profile id when `--profile` is not given. */
|
|
45
|
+
export const DEFAULT_PROFILE_ID = "nist";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Built-in regime profiles. Facts reflect each authority's published PQC-transition
|
|
49
|
+
* position as of the `asOf` date; verify against the cited source before relying on a
|
|
50
|
+
* deadline or a hybrid mandate for a compliance decision.
|
|
51
|
+
*/
|
|
52
|
+
export const STANDARDS_PROFILES: Readonly<Record<string, StandardsProfile>> = {
|
|
53
|
+
nist: {
|
|
54
|
+
id: "nist",
|
|
55
|
+
name: "NIST (general / commercial)",
|
|
56
|
+
authority: "NIST",
|
|
57
|
+
paramSets: { kem: "ML-KEM-768 (FIPS 203)", signature: "ML-DSA-65 (FIPS 204)" },
|
|
58
|
+
hybridStance: "recommended",
|
|
59
|
+
hybridGuidance:
|
|
60
|
+
"Hybrid key establishment (e.g. X25519MLKEM768) is permitted and recommended during the transition; pure ML-KEM is also acceptable (SP 800-227 / IR 8547).",
|
|
61
|
+
deprecateAfter: 2030,
|
|
62
|
+
disallowAfter: 2035,
|
|
63
|
+
citation: "NIST IR 8547 + FIPS 203/204/205",
|
|
64
|
+
asOf: "2026-07",
|
|
65
|
+
},
|
|
66
|
+
"cnsa-2.0": {
|
|
67
|
+
id: "cnsa-2.0",
|
|
68
|
+
name: "NSA CNSA 2.0 (national-security systems)",
|
|
69
|
+
authority: "NSA",
|
|
70
|
+
paramSets: { kem: "ML-KEM-1024 (FIPS 203)", signature: "ML-DSA-87 (FIPS 204)" },
|
|
71
|
+
hybridStance: "optional",
|
|
72
|
+
hybridGuidance:
|
|
73
|
+
"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.",
|
|
74
|
+
deprecateAfter: 2030,
|
|
75
|
+
disallowAfter: 2035,
|
|
76
|
+
citation: "NSA CNSA 2.0 (2030/2033/2035 migration milestones)",
|
|
77
|
+
asOf: "2026-07",
|
|
78
|
+
},
|
|
79
|
+
"bsi-tr-02102": {
|
|
80
|
+
id: "bsi-tr-02102",
|
|
81
|
+
name: "BSI TR-02102 (Germany)",
|
|
82
|
+
authority: "BSI",
|
|
83
|
+
paramSets: { kem: "ML-KEM-768 (FIPS 203)", signature: "ML-DSA-65 (FIPS 204)" },
|
|
84
|
+
hybridStance: "required",
|
|
85
|
+
hybridGuidance:
|
|
86
|
+
"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.",
|
|
87
|
+
deprecateAfter: 2030,
|
|
88
|
+
disallowAfter: 2035,
|
|
89
|
+
citation: "BSI TR-02102-1 (Kryptographische Verfahren)",
|
|
90
|
+
asOf: "2026-07",
|
|
91
|
+
},
|
|
92
|
+
anssi: {
|
|
93
|
+
id: "anssi",
|
|
94
|
+
name: "ANSSI (France)",
|
|
95
|
+
authority: "ANSSI",
|
|
96
|
+
paramSets: { kem: "ML-KEM-1024 (FIPS 203)", signature: "ML-DSA-87 (FIPS 204)" },
|
|
97
|
+
hybridStance: "required",
|
|
98
|
+
hybridGuidance:
|
|
99
|
+
"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.",
|
|
100
|
+
deprecateAfter: 2030,
|
|
101
|
+
disallowAfter: 2035,
|
|
102
|
+
citation: "ANSSI — PQC transition position papers",
|
|
103
|
+
asOf: "2026-07",
|
|
104
|
+
},
|
|
105
|
+
"uk-ncsc": {
|
|
106
|
+
id: "uk-ncsc",
|
|
107
|
+
name: "UK NCSC",
|
|
108
|
+
authority: "NCSC",
|
|
109
|
+
paramSets: { kem: "ML-KEM-768 (FIPS 203)", signature: "ML-DSA-65 (FIPS 204)" },
|
|
110
|
+
hybridStance: "recommended",
|
|
111
|
+
hybridGuidance:
|
|
112
|
+
"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.",
|
|
113
|
+
deprecateAfter: 2031,
|
|
114
|
+
disallowAfter: 2035,
|
|
115
|
+
citation: "NCSC — Preparing for quantum-safe cryptography / PQC migration timeline",
|
|
116
|
+
asOf: "2026-07",
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** All built-in profile ids, in a stable order (default first). */
|
|
121
|
+
export function standardsProfileIds(): string[] {
|
|
122
|
+
return [
|
|
123
|
+
DEFAULT_PROFILE_ID,
|
|
124
|
+
...Object.keys(STANDARDS_PROFILES).filter((id) => id !== DEFAULT_PROFILE_ID),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Look up a built-in profile by id, or `undefined` when unknown. */
|
|
129
|
+
export function getStandardsProfile(id: string): StandardsProfile | undefined {
|
|
130
|
+
return STANDARDS_PROFILES[id];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The default profile (NIST). Always defined. */
|
|
134
|
+
export function defaultStandardsProfile(): StandardsProfile {
|
|
135
|
+
return STANDARDS_PROFILES[DEFAULT_PROFILE_ID];
|
|
136
|
+
}
|
package/src/standards.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the post-quantum standards this tool depends on.
|
|
3
|
+
*
|
|
4
|
+
* The scanner's credibility rests on its recommendations tracking the current
|
|
5
|
+
* NIST / CNSA / IETF state. That tracking used to be ad-hoc — facts were spread
|
|
6
|
+
* across `remediation.ts` with no dates, no citations, and nothing to catch code
|
|
7
|
+
* drifting from the published standards. This module makes the standards facts
|
|
8
|
+
* explicit, dated, and cited, and the companion drift test
|
|
9
|
+
* (`test/standards.test.ts`) fails the build if the runtime constants
|
|
10
|
+
* (`TIER_PARAMS`, `PQC_TRANSITION_NOTE`, `STATEFUL_HBS_NOTE`) fall out of sync
|
|
11
|
+
* with what is recorded here.
|
|
12
|
+
*
|
|
13
|
+
* ## Cadence
|
|
14
|
+
*
|
|
15
|
+
* Re-verify every quarter (see `docs/standards/pqc-standards.md` for the runbook).
|
|
16
|
+
* On each review: check the sources below for changes, update the facts + their
|
|
17
|
+
* `asOf`, and roll `lastReviewed` / `nextReview` forward. `scripts/standards-check.mjs`
|
|
18
|
+
* (advisory, runs in CI) flags when `nextReview` has passed so a review can't be
|
|
19
|
+
* silently skipped. `standardsReviewStatus(now)` is the pure predicate behind it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** A single standards fact with its citation and when it was last verified. */
|
|
23
|
+
export interface StandardsCitation {
|
|
24
|
+
/** One-line statement of the fact. */
|
|
25
|
+
readonly summary: string;
|
|
26
|
+
/** Spec identifier / publication (and URL where stable). */
|
|
27
|
+
readonly source: string;
|
|
28
|
+
/** `YYYY-MM` — when this fact was last verified against its source. */
|
|
29
|
+
readonly asOf: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The full post-quantum standards snapshot the tool tracks. */
|
|
33
|
+
export interface PqcStandards {
|
|
34
|
+
/** `YYYY-MM-DD` — when the whole snapshot was last reviewed. */
|
|
35
|
+
readonly lastReviewed: string;
|
|
36
|
+
/** `YYYY-MM-DD` — when the next review is due. */
|
|
37
|
+
readonly nextReview: string;
|
|
38
|
+
/** Cadence, in months, between reviews. */
|
|
39
|
+
readonly reviewIntervalMonths: number;
|
|
40
|
+
|
|
41
|
+
/** NIST's finalized PQC FIPS (the recommendation targets). */
|
|
42
|
+
readonly fips: {
|
|
43
|
+
readonly mlKem: StandardsCitation; // FIPS 203
|
|
44
|
+
readonly mlDsa: StandardsCitation; // FIPS 204
|
|
45
|
+
readonly slhDsa: StandardsCitation; // FIPS 205
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* CNSA 2.0 security tiers → the KEM / signature parameter sets. These MUST
|
|
50
|
+
* mirror `remediation.TIER_PARAMS`; the drift test asserts they stay identical.
|
|
51
|
+
*/
|
|
52
|
+
readonly cnsa: {
|
|
53
|
+
readonly category3: { readonly kem: string; readonly signature: string };
|
|
54
|
+
readonly category5: { readonly kem: string; readonly signature: string };
|
|
55
|
+
readonly source: string;
|
|
56
|
+
readonly asOf: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Stateful hash-based signatures (firmware / boot signing). */
|
|
60
|
+
readonly statefulHbs: StandardsCitation; // SP 800-208
|
|
61
|
+
|
|
62
|
+
/** The migration deadline the transition note surfaces. */
|
|
63
|
+
readonly transitionTimeline: {
|
|
64
|
+
/** Year after which classical public-key crypto is deprecated. */
|
|
65
|
+
readonly deprecateAfter: number;
|
|
66
|
+
/** Year after which it is disallowed. */
|
|
67
|
+
readonly disallowAfter: number;
|
|
68
|
+
readonly source: string;
|
|
69
|
+
readonly asOf: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Emerging / backup standards worth tracking beyond the current FIPS. */
|
|
73
|
+
readonly emerging: readonly StandardsCitation[];
|
|
74
|
+
|
|
75
|
+
/** Recommended hybrid key-exchange groups. */
|
|
76
|
+
readonly hybrids: readonly StandardsCitation[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The current snapshot. Update on each quarterly review; the drift test keeps the
|
|
81
|
+
* runtime remediation constants aligned with it.
|
|
82
|
+
*/
|
|
83
|
+
export const PQC_STANDARDS: PqcStandards = {
|
|
84
|
+
lastReviewed: "2026-07-19",
|
|
85
|
+
nextReview: "2026-10-19",
|
|
86
|
+
reviewIntervalMonths: 3,
|
|
87
|
+
|
|
88
|
+
fips: {
|
|
89
|
+
mlKem: {
|
|
90
|
+
summary: "ML-KEM (Kyber) key encapsulation — finalized August 2024.",
|
|
91
|
+
source: "NIST FIPS 203",
|
|
92
|
+
asOf: "2026-07",
|
|
93
|
+
},
|
|
94
|
+
mlDsa: {
|
|
95
|
+
summary: "ML-DSA (Dilithium) lattice signatures — finalized August 2024.",
|
|
96
|
+
source: "NIST FIPS 204",
|
|
97
|
+
asOf: "2026-07",
|
|
98
|
+
},
|
|
99
|
+
slhDsa: {
|
|
100
|
+
summary: "SLH-DSA (SPHINCS+) stateless hash-based signatures — finalized August 2024.",
|
|
101
|
+
source: "NIST FIPS 205",
|
|
102
|
+
asOf: "2026-07",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
cnsa: {
|
|
107
|
+
category3: { kem: "ML-KEM-768 (FIPS 203)", signature: "ML-DSA-65 (FIPS 204)" },
|
|
108
|
+
category5: { kem: "ML-KEM-1024 (FIPS 203)", signature: "ML-DSA-87 (FIPS 204)" },
|
|
109
|
+
source: "NSA CNSA 2.0 (national-security systems; 2030/2033 migration milestones)",
|
|
110
|
+
asOf: "2026-07",
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
statefulHbs: {
|
|
114
|
+
summary:
|
|
115
|
+
"LMS/HSS and XMSS/XMSSMT stateful hash-based signatures (incl. the SHAKE256 and " +
|
|
116
|
+
"192-bit parameter sets) are approved for firmware/boot signing, but are STATEFUL.",
|
|
117
|
+
source: "NIST SP 800-208",
|
|
118
|
+
asOf: "2026-07",
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
transitionTimeline: {
|
|
122
|
+
deprecateAfter: 2030,
|
|
123
|
+
disallowAfter: 2035,
|
|
124
|
+
source: "NIST IR 8547 (transition to post-quantum cryptography standards)",
|
|
125
|
+
asOf: "2026-07",
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
emerging: [
|
|
129
|
+
{
|
|
130
|
+
summary:
|
|
131
|
+
"HQC — NIST's code-based backup KEM (selected March 2025; draft FIPS expected ~2026), a " +
|
|
132
|
+
"diversity hedge against ML-KEM's lattice assumption.",
|
|
133
|
+
source: "NIST PQC (HQC selection)",
|
|
134
|
+
asOf: "2026-07",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
summary: "FN-DSA / Falcon — compact lattice signatures.",
|
|
138
|
+
source: "NIST draft FIPS 206",
|
|
139
|
+
asOf: "2026-07",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
summary: "X-Wing — X25519 + ML-KEM-768 hybrid KEM for HPKE-style encryption.",
|
|
143
|
+
source: "IETF draft-connolly-cfrg-xwing-kem",
|
|
144
|
+
asOf: "2026-07",
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
|
|
148
|
+
hybrids: [
|
|
149
|
+
{
|
|
150
|
+
summary: "X25519MLKEM768 — the default TLS 1.3 hybrid key-exchange group.",
|
|
151
|
+
source: "IETF draft-ietf-tls-ecdhe-mlkem",
|
|
152
|
+
asOf: "2026-07",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
summary: "SecP384r1MLKEM1024 — the Category-5 / CNSA hybrid key-exchange group.",
|
|
156
|
+
source: "IETF draft-ietf-tls-ecdhe-mlkem",
|
|
157
|
+
asOf: "2026-07",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** Result of {@link standardsReviewStatus}. */
|
|
163
|
+
export interface StandardsReviewStatus {
|
|
164
|
+
/** True when `now` is on or after `nextReview` — a review is due. */
|
|
165
|
+
readonly due: boolean;
|
|
166
|
+
/** The `nextReview` date being compared against (`YYYY-MM-DD`). */
|
|
167
|
+
readonly nextReview: string;
|
|
168
|
+
/** Whole days from `now` until `nextReview` (negative when overdue). */
|
|
169
|
+
readonly daysUntil: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Whether a standards review is due as of `now`. Pure (takes `now` explicitly) so
|
|
174
|
+
* it is deterministic in tests; the CI script passes the real clock. Compares on
|
|
175
|
+
* whole UTC days so a same-day run is not spuriously "overdue".
|
|
176
|
+
*/
|
|
177
|
+
export function standardsReviewStatus(
|
|
178
|
+
now: Date,
|
|
179
|
+
standards: PqcStandards = PQC_STANDARDS,
|
|
180
|
+
): StandardsReviewStatus {
|
|
181
|
+
const MS_PER_DAY = 86_400_000;
|
|
182
|
+
const next = Date.parse(`${standards.nextReview}T00:00:00Z`);
|
|
183
|
+
const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
184
|
+
const daysUntil = Math.round((next - today) / MS_PER_DAY);
|
|
185
|
+
return { due: daysUntil <= 0, nextReview: standards.nextReview, daysUntil };
|
|
186
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -134,7 +134,13 @@ export type DetectorLanguage =
|
|
|
134
134
|
| "csharp"
|
|
135
135
|
| "rust"
|
|
136
136
|
| "ruby"
|
|
137
|
+
| "php"
|
|
138
|
+
| "elixir"
|
|
137
139
|
| "c"
|
|
140
|
+
| "swift"
|
|
141
|
+
| "objc"
|
|
142
|
+
| "dart"
|
|
143
|
+
| "solidity"
|
|
138
144
|
| "any";
|
|
139
145
|
|
|
140
146
|
/**
|
|
@@ -342,10 +348,11 @@ export interface ScanResult {
|
|
|
342
348
|
*/
|
|
343
349
|
diagnostics?: ScanDiagnostics;
|
|
344
350
|
/**
|
|
345
|
-
* Of `filesScanned`, how many were in a source language the scanner can
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
351
|
+
* Of `filesScanned`, how many were in a source language the scanner can actually
|
|
352
|
+
* analyze for inline crypto (the 13 packs: JS/TS, Python, Go, Java/Kotlin/Scala,
|
|
353
|
+
* C#, Rust, Ruby, PHP, Elixir, C/C++, Swift, Objective-C, Dart). When this is 0
|
|
354
|
+
* but `filesScanned` > 0, the readiness score reflects NO analyzable code — the
|
|
355
|
+
* crypto likely lives in an unsupported language (Lua, Perl, …) — and
|
|
349
356
|
* reporters surface that so a bare 100/100 can't read as "safe". Optional for
|
|
350
357
|
* backward compatibility with hand-built results.
|
|
351
358
|
*/
|
package/src/version.ts
CHANGED
package/src/vex.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenVEX export — turn a {@link ScanResult} into an OpenVEX 0.2.0 document so
|
|
3
|
+
* the quantum-readiness posture flows into the SAME supply-chain pipeline that
|
|
4
|
+
* already ingests CVE-based VEX (Vulnerability Exploitability eXchange).
|
|
5
|
+
*
|
|
6
|
+
* Reference: OpenVEX spec (https://openvex.dev/, `@context` v0.2.0).
|
|
7
|
+
*
|
|
8
|
+
* Modeling choice (documented, honest): PQC findings have no CVE, so each qScan
|
|
9
|
+
* rule becomes a synthetic vulnerability `QK-<ruleId>`. Every finding is reported
|
|
10
|
+
* `status: "affected"` — the classical, Shor-breakable primitive IS present in
|
|
11
|
+
* the product by construction — with the rule's remediation as the VEX
|
|
12
|
+
* `action_statement`. A `--triage` exposure verdict, when present, is surfaced in
|
|
13
|
+
* `status_notes` (score / priority / rationale). qScan never downgrades a finding
|
|
14
|
+
* to `not_affected`: only the operator can attest a mitigation, so that judgment
|
|
15
|
+
* is deliberately left to them (they can post-process this document).
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
|
|
19
|
+
import type { Finding, ScanResult } from "./types.js";
|
|
20
|
+
|
|
21
|
+
/** A single OpenVEX statement: one synthetic vulnerability over its affected products. */
|
|
22
|
+
export interface OpenVexStatement {
|
|
23
|
+
vulnerability: { name: string; description?: string };
|
|
24
|
+
products: { "@id": string }[];
|
|
25
|
+
status: "not_affected" | "affected" | "fixed" | "under_investigation";
|
|
26
|
+
/** Recommended action for an `affected` product (the rule's remediation). */
|
|
27
|
+
action_statement?: string;
|
|
28
|
+
/** Free-form status detail — carries the `--triage` exposure verdict when present. */
|
|
29
|
+
status_notes?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** An OpenVEX 0.2.0 document. */
|
|
33
|
+
export interface OpenVexDocument {
|
|
34
|
+
"@context": "https://openvex.dev/ns/v0.2.0";
|
|
35
|
+
"@id": string;
|
|
36
|
+
author: string;
|
|
37
|
+
timestamp: string;
|
|
38
|
+
version: number;
|
|
39
|
+
statements: OpenVexStatement[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Options for {@link toOpenVex}. */
|
|
43
|
+
export interface OpenVexOptions {
|
|
44
|
+
/** Document author (VEX issuer). Default: `"qScan"`. */
|
|
45
|
+
author?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A stable product identifier for a finding as an IRI (OpenVEX requires product
|
|
50
|
+
* `@id` to be an IRI): a `file:` URI with the line in the fragment, e.g.
|
|
51
|
+
* `file:src/a.ts#L3`.
|
|
52
|
+
*/
|
|
53
|
+
function productId(f: Finding): string {
|
|
54
|
+
return `file:${f.location.file}#L${f.location.line}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build an OpenVEX 0.2.0 document from a scan result. One statement per distinct
|
|
59
|
+
* rule id, listing every affected `file:line` product. Output is deterministic
|
|
60
|
+
* (statements sorted by vulnerability name, products sorted and de-duplicated;
|
|
61
|
+
* the `@id` and `timestamp` derive from the result), so re-exporting the same
|
|
62
|
+
* scan yields byte-identical VEX.
|
|
63
|
+
*/
|
|
64
|
+
export function toOpenVex(result: ScanResult, opts: OpenVexOptions = {}): OpenVexDocument {
|
|
65
|
+
// Group findings by rule id → one VEX statement per rule.
|
|
66
|
+
const byRule = new Map<string, Finding[]>();
|
|
67
|
+
for (const f of result.findings) {
|
|
68
|
+
const list = byRule.get(f.ruleId);
|
|
69
|
+
if (list) list.push(f);
|
|
70
|
+
else byRule.set(f.ruleId, [f]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const statements: OpenVexStatement[] = [...byRule.entries()]
|
|
74
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
75
|
+
.map(([ruleId, findings]) => {
|
|
76
|
+
const first = findings[0] as Finding;
|
|
77
|
+
// De-duplicate + sort the affected products (a rule can fire many times).
|
|
78
|
+
const products = [...new Set(findings.map(productId))].sort().map((id) => ({ "@id": id }));
|
|
79
|
+
|
|
80
|
+
// Surface a triage verdict (if any finding for this rule carries one). Findings
|
|
81
|
+
// of the same rule share a rule; the most-exposed verdict is the informative one.
|
|
82
|
+
const triaged = findings
|
|
83
|
+
.filter((f) => f.triage)
|
|
84
|
+
.sort((a, b) => (b.triage?.exposureScore ?? 0) - (a.triage?.exposureScore ?? 0))[0]?.triage;
|
|
85
|
+
|
|
86
|
+
const statement: OpenVexStatement = {
|
|
87
|
+
vulnerability: {
|
|
88
|
+
name: `QK-${ruleId}`,
|
|
89
|
+
description: first.message,
|
|
90
|
+
},
|
|
91
|
+
products,
|
|
92
|
+
status: "affected",
|
|
93
|
+
action_statement: first.remediation ?? "Migrate to a NIST PQC standard (FIPS 203/204/205).",
|
|
94
|
+
};
|
|
95
|
+
if (triaged) {
|
|
96
|
+
statement.status_notes =
|
|
97
|
+
`qScan triage — exposure ${triaged.exposureScore}/100, priority ${triaged.priority}: ` +
|
|
98
|
+
triaged.rationale;
|
|
99
|
+
}
|
|
100
|
+
return statement;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Deterministic doc id + timestamp so the same scan yields identical VEX. The
|
|
104
|
+
// triage `status_notes` are part of the identity: a triaged and an untriaged
|
|
105
|
+
// export of the same scan are DIFFERENT documents and must not share an @id.
|
|
106
|
+
const digest = createHash("sha256").update(`${result.root}|${result.toolVersion}`, "utf8");
|
|
107
|
+
for (const s of statements) {
|
|
108
|
+
digest.update(
|
|
109
|
+
`\n${s.vulnerability.name}|${s.products.map((p) => p["@id"]).join(",")}|${s.status_notes ?? ""}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const id = `https://quantakrypto.com/vex/${digest.digest("hex").slice(0, 16)}`;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"@context": "https://openvex.dev/ns/v0.2.0",
|
|
116
|
+
"@id": id,
|
|
117
|
+
author: opts.author ?? "qScan",
|
|
118
|
+
timestamp: result.finishedAt,
|
|
119
|
+
version: 1,
|
|
120
|
+
statements,
|
|
121
|
+
};
|
|
122
|
+
}
|
package/src/walk.ts
CHANGED
|
@@ -11,7 +11,7 @@ import * as path from "node:path";
|
|
|
11
11
|
import { isManifestFile } from "./dependencies.js";
|
|
12
12
|
|
|
13
13
|
/** Directories ignored by default (can be disabled with noDefaultIgnores). */
|
|
14
|
-
|
|
14
|
+
const DEFAULT_IGNORES: readonly string[] = [
|
|
15
15
|
"node_modules",
|
|
16
16
|
".git",
|
|
17
17
|
"dist",
|
|
@@ -99,7 +99,7 @@ const BINARY_EXTENSIONS = new Set<string>([
|
|
|
99
99
|
]);
|
|
100
100
|
|
|
101
101
|
/** Options accepted by {@link walkFiles}. */
|
|
102
|
-
|
|
102
|
+
interface WalkOptions {
|
|
103
103
|
/**
|
|
104
104
|
* Restrict to paths matching one of these include patterns (substring or
|
|
105
105
|
* relative-path-prefix match). When omitted/empty, all files pass.
|
|
@@ -222,6 +222,37 @@ export function isBinaryPath(rel: string): boolean {
|
|
|
222
222
|
return BINARY_EXTENSIONS.has(ext);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Binary cryptographic key containers we DO want to scan — Java/PKCS#12 keystores
|
|
227
|
+
* and binary OpenPGP keyrings/messages. Unlike the images/archives above these are
|
|
228
|
+
* read byte-preserving (latin1) and exempted from the minified skip, so the
|
|
229
|
+
* keystore + openpgp detectors can identify them by magic number / packet tag.
|
|
230
|
+
* Kept separate from {@link BINARY_EXTENSIONS} so those detectors can opt in.
|
|
231
|
+
*/
|
|
232
|
+
const KEYSTORE_EXTENSIONS = new Set<string>([
|
|
233
|
+
".jks",
|
|
234
|
+
".keystore",
|
|
235
|
+
".jceks",
|
|
236
|
+
".bks",
|
|
237
|
+
".p12",
|
|
238
|
+
".pfx",
|
|
239
|
+
// Binary OpenPGP keyrings / messages (armored .asc is text; handled elsewhere).
|
|
240
|
+
".gpg",
|
|
241
|
+
".pgp",
|
|
242
|
+
".kbx",
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* True if the path is a binary cryptographic key container we scan byte-preserving.
|
|
247
|
+
* Uses a suffix match (not `extname`) so it agrees with the detectors' `hasExtension`
|
|
248
|
+
* even for a bare dotfile literally named e.g. `.gpg` (where `extname` returns "").
|
|
249
|
+
*/
|
|
250
|
+
export function isKeystorePath(rel: string): boolean {
|
|
251
|
+
const lower = rel.toLowerCase();
|
|
252
|
+
for (const ext of KEYSTORE_EXTENSIONS) if (lower.endsWith(ext)) return true;
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
225
256
|
/**
|
|
226
257
|
* Compound / pattern extensions that mark generated or bundled output we skip
|
|
227
258
|
* by default (beyond `.min.js` / `.map`, which {@link isBinaryPath} handles).
|