@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,129 @@
|
|
|
1
|
+
import { eachMatch, findingFromRule, maskCommentLines } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
/** True for the CI/CD pipeline definition files this detector inspects. */
|
|
4
|
+
function isCiPipelineFile(filePath) {
|
|
5
|
+
const lower = filePath.toLowerCase();
|
|
6
|
+
const base = lower.split("/").pop() ?? lower;
|
|
7
|
+
return ((lower.includes(".github/workflows/") && (lower.endsWith(".yml") || lower.endsWith(".yaml"))) ||
|
|
8
|
+
base === ".gitlab-ci.yml" ||
|
|
9
|
+
lower.endsWith(".gitlab-ci.yml") ||
|
|
10
|
+
base === "jenkinsfile" ||
|
|
11
|
+
lower.endsWith(".jenkinsfile") ||
|
|
12
|
+
base === "azure-pipelines.yml" ||
|
|
13
|
+
base === "azure-pipelines.yaml" ||
|
|
14
|
+
(lower.includes(".circleci/") && (lower.endsWith(".yml") || lower.endsWith(".yaml"))));
|
|
15
|
+
}
|
|
16
|
+
const CI_RULES = [
|
|
17
|
+
{
|
|
18
|
+
// `sign-blob` precedes `sign` so the longer subcommand wins — otherwise `sign`
|
|
19
|
+
// matches first and the trailing `\b` succeeds at the `-`, never reaching `sign-blob`.
|
|
20
|
+
re: /\bcosign\s+(?:sign-blob|sign|attest|generate-key-pair)\b/g,
|
|
21
|
+
meta: {
|
|
22
|
+
id: "ci-cosign-ecdsa",
|
|
23
|
+
title: "cosign artifact signing (ECDSA)",
|
|
24
|
+
description: "sigstore/cosign signing in a CI pipeline",
|
|
25
|
+
category: "signature",
|
|
26
|
+
severity: "medium",
|
|
27
|
+
confidence: "high",
|
|
28
|
+
algorithm: "ECDSA",
|
|
29
|
+
hndl: false,
|
|
30
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
31
|
+
message: "CI pipeline signs artifacts with cosign (ECDSA P-256, key-based or keyless/Fulcio); classical signatures are forgeable once a CRQC exists.",
|
|
32
|
+
remediation: "Track sigstore's post-quantum signing roadmap (ML-DSA); plan hybrid signing for long-lived release artifacts.",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
// Bound the span to the gpg invocation ([^\n&|;] stops it crossing `&&`/`|`/`;`
|
|
37
|
+
// into another command's flag), and `(?![\w-])` stops `--sign` matching the
|
|
38
|
+
// `--sign` prefix of an unrelated flag like `--sign-artifacts`. The short forms
|
|
39
|
+
// `-s` (sign) / `-b` (detach-sign) are safe inside the bounded gpg span.
|
|
40
|
+
re: /\bgpg\b[^\n&|;]{0,120}?\s(?:-[sb]\b|--(?:detach-sign|clearsign|sign)(?![\w-]))/g,
|
|
41
|
+
meta: {
|
|
42
|
+
id: "ci-gpg-sign",
|
|
43
|
+
title: "GPG signing (RSA)",
|
|
44
|
+
description: "GnuPG detached/clear signing in a CI pipeline",
|
|
45
|
+
category: "signature",
|
|
46
|
+
severity: "medium",
|
|
47
|
+
confidence: "high",
|
|
48
|
+
algorithm: "RSA",
|
|
49
|
+
hndl: false,
|
|
50
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
51
|
+
message: "CI pipeline signs with GPG, classically an RSA signing key; forgeable once a CRQC exists.",
|
|
52
|
+
remediation: "Plan migration to ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205) signatures.",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
re: /\bjarsigner\b/g,
|
|
57
|
+
meta: {
|
|
58
|
+
id: "ci-jarsigner",
|
|
59
|
+
title: "Java jarsigner (classical)",
|
|
60
|
+
description: "JDK jarsigner code signing in a CI pipeline",
|
|
61
|
+
category: "signature",
|
|
62
|
+
severity: "medium",
|
|
63
|
+
confidence: "high",
|
|
64
|
+
algorithm: "RSA",
|
|
65
|
+
hndl: false,
|
|
66
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
67
|
+
message: "CI pipeline signs JARs with jarsigner (classical RSA/DSA/EC signing key); forgeable once a CRQC exists.",
|
|
68
|
+
remediation: "Plan migration to a PQC signature scheme (ML-DSA-65 / SLH-DSA) as the JDK adds support.",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
// Allow intervening flags (the common `codesign --force --options runtime --sign`
|
|
73
|
+
// form), bounded to the codesign invocation so it can't latch onto a later
|
|
74
|
+
// command's `--sign` across `&&`/`|`/`;`.
|
|
75
|
+
re: /\bcodesign\b[^\n&|;]{0,120}?\s(?:-s\b|--sign\b)/g,
|
|
76
|
+
meta: {
|
|
77
|
+
id: "ci-codesign",
|
|
78
|
+
title: "Apple codesign (RSA)",
|
|
79
|
+
description: "Apple codesign in a CI pipeline",
|
|
80
|
+
category: "signature",
|
|
81
|
+
severity: "medium",
|
|
82
|
+
confidence: "high",
|
|
83
|
+
algorithm: "RSA",
|
|
84
|
+
hndl: false,
|
|
85
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
86
|
+
message: "CI pipeline signs with Apple codesign (classical RSA signing identity); forgeable once a CRQC exists.",
|
|
87
|
+
remediation: "Classical only today; track Apple's PQC signing support and plan migration.",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
re: /\bminisign\b/g,
|
|
92
|
+
meta: {
|
|
93
|
+
id: "ci-minisign",
|
|
94
|
+
title: "minisign (Ed25519)",
|
|
95
|
+
description: "minisign signing in a CI pipeline",
|
|
96
|
+
category: "signature",
|
|
97
|
+
severity: "low",
|
|
98
|
+
confidence: "high",
|
|
99
|
+
algorithm: "EdDSA",
|
|
100
|
+
hndl: false,
|
|
101
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
102
|
+
message: "CI pipeline signs with minisign (Ed25519); modern but classical and forgeable once a CRQC exists.",
|
|
103
|
+
remediation: "Plan migration to ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
/** Detects classical artifact / code signing in CI/CD pipeline definitions. */
|
|
108
|
+
export const cicdDetector = {
|
|
109
|
+
id: "cicd-signing",
|
|
110
|
+
description: "Classical artifact / code signing in CI/CD pipelines",
|
|
111
|
+
scope: "config",
|
|
112
|
+
language: "any",
|
|
113
|
+
rules: CI_RULES.map((r) => r.meta),
|
|
114
|
+
appliesTo: isCiPipelineFile,
|
|
115
|
+
detect({ file, content }) {
|
|
116
|
+
const findings = [];
|
|
117
|
+
// A commented-out CI step (`# - run: cosign sign …`, or a `//` line in a
|
|
118
|
+
// Jenkinsfile) is not an active signing step. Mask comment lines first; offsets
|
|
119
|
+
// are preserved so the snippet from the original `content` stays correct.
|
|
120
|
+
const scan = maskCommentLines(content, ["#", "//"]);
|
|
121
|
+
for (const rule of CI_RULES) {
|
|
122
|
+
eachMatch(rule.re, scan, (m) => {
|
|
123
|
+
findings.push(findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return findings;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=cicd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cicd.js","sourceRoot":"","sources":["../../src/detectors/cicd.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical signing of build artifacts and code in CI/CD\n * pipelines (GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, CircleCI).\n *\n * Artifact and code signatures are the signature-side analogue of \"harvest now,\n * decrypt later\": a release signed today with a classical key is *forgeable* once\n * a CRQC exists, and long-lived artifacts (container images, released binaries,\n * SBOM attestations) keep verifying against that classical public key for years.\n * So these are flagged `hndl:false` (a signature is not confidentiality — nothing\n * to harvest) but remain real quantum-migration debt.\n *\n * Covered command invocations (distinctive enough inside a CI file that the\n * false-positive risk is low):\n * - `cosign sign|attest|generate-key-pair` → sigstore/cosign uses ECDSA P-256\n * (both key-based and keyless/Fulcio).\n * - `gpg --detach-sign|--clearsign|--sign` → GnuPG, classically RSA.\n * - `jarsigner` → Java JAR signing (RSA/DSA/EC).\n * - `codesign --sign|-s` → Apple code signing (RSA).\n * - `minisign` → Ed25519 signatures.\n *\n * Gated to CI/CD pipeline files so `gpg`/`cosign` mentioned in a shell script or\n * doc does not fire; the surface here is specifically the release pipeline.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule, maskCommentLines } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n/** True for the CI/CD pipeline definition files this detector inspects. */\nfunction isCiPipelineFile(filePath: string): boolean {\n const lower = filePath.toLowerCase();\n const base = lower.split(\"/\").pop() ?? lower;\n return (\n (lower.includes(\".github/workflows/\") && (lower.endsWith(\".yml\") || lower.endsWith(\".yaml\"))) ||\n base === \".gitlab-ci.yml\" ||\n lower.endsWith(\".gitlab-ci.yml\") ||\n base === \"jenkinsfile\" ||\n lower.endsWith(\".jenkinsfile\") ||\n base === \"azure-pipelines.yml\" ||\n base === \"azure-pipelines.yaml\" ||\n (lower.includes(\".circleci/\") && (lower.endsWith(\".yml\") || lower.endsWith(\".yaml\")))\n );\n}\n\ninterface CiRule {\n re: RegExp;\n meta: RuleMeta;\n}\n\nconst CI_RULES: CiRule[] = [\n {\n // `sign-blob` precedes `sign` so the longer subcommand wins — otherwise `sign`\n // matches first and the trailing `\\b` succeeds at the `-`, never reaching `sign-blob`.\n re: /\\bcosign\\s+(?:sign-blob|sign|attest|generate-key-pair)\\b/g,\n meta: {\n id: \"ci-cosign-ecdsa\",\n title: \"cosign artifact signing (ECDSA)\",\n description: \"sigstore/cosign signing in a CI pipeline\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CI pipeline signs artifacts with cosign (ECDSA P-256, key-based or keyless/Fulcio); classical signatures are forgeable once a CRQC exists.\",\n remediation:\n \"Track sigstore's post-quantum signing roadmap (ML-DSA); plan hybrid signing for long-lived release artifacts.\",\n },\n },\n {\n // Bound the span to the gpg invocation ([^\\n&|;] stops it crossing `&&`/`|`/`;`\n // into another command's flag), and `(?![\\w-])` stops `--sign` matching the\n // `--sign` prefix of an unrelated flag like `--sign-artifacts`. The short forms\n // `-s` (sign) / `-b` (detach-sign) are safe inside the bounded gpg span.\n re: /\\bgpg\\b[^\\n&|;]{0,120}?\\s(?:-[sb]\\b|--(?:detach-sign|clearsign|sign)(?![\\w-]))/g,\n meta: {\n id: \"ci-gpg-sign\",\n title: \"GPG signing (RSA)\",\n description: \"GnuPG detached/clear signing in a CI pipeline\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CI pipeline signs with GPG, classically an RSA signing key; forgeable once a CRQC exists.\",\n remediation: \"Plan migration to ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205) signatures.\",\n },\n },\n {\n re: /\\bjarsigner\\b/g,\n meta: {\n id: \"ci-jarsigner\",\n title: \"Java jarsigner (classical)\",\n description: \"JDK jarsigner code signing in a CI pipeline\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CI pipeline signs JARs with jarsigner (classical RSA/DSA/EC signing key); forgeable once a CRQC exists.\",\n remediation:\n \"Plan migration to a PQC signature scheme (ML-DSA-65 / SLH-DSA) as the JDK adds support.\",\n },\n },\n {\n // Allow intervening flags (the common `codesign --force --options runtime --sign`\n // form), bounded to the codesign invocation so it can't latch onto a later\n // command's `--sign` across `&&`/`|`/`;`.\n re: /\\bcodesign\\b[^\\n&|;]{0,120}?\\s(?:-s\\b|--sign\\b)/g,\n meta: {\n id: \"ci-codesign\",\n title: \"Apple codesign (RSA)\",\n description: \"Apple codesign in a CI pipeline\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CI pipeline signs with Apple codesign (classical RSA signing identity); forgeable once a CRQC exists.\",\n remediation: \"Classical only today; track Apple's PQC signing support and plan migration.\",\n },\n },\n {\n re: /\\bminisign\\b/g,\n meta: {\n id: \"ci-minisign\",\n title: \"minisign (Ed25519)\",\n description: \"minisign signing in a CI pipeline\",\n category: \"signature\",\n severity: \"low\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CI pipeline signs with minisign (Ed25519); modern but classical and forgeable once a CRQC exists.\",\n remediation: \"Plan migration to ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).\",\n },\n },\n];\n\n/** Detects classical artifact / code signing in CI/CD pipeline definitions. */\nexport const cicdDetector: Detector = {\n id: \"cicd-signing\",\n description: \"Classical artifact / code signing in CI/CD pipelines\",\n scope: \"config\",\n language: \"any\",\n rules: CI_RULES.map((r) => r.meta),\n appliesTo: isCiPipelineFile,\n detect({ file, content }): Finding[] {\n const findings: Finding[] = [];\n // A commented-out CI step (`# - run: cosign sign …`, or a `//` line in a\n // Jenkinsfile) is not an active signing step. Mask comment lines first; offsets\n // are preserved so the snippet from the original `content` stays correct.\n const scan = maskCommentLines(content, [\"#\", \"//\"]);\n for (const rule of CI_RULES) {\n eachMatch(rule.re, scan, (m) => {\n findings.push(\n findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),\n );\n });\n }\n return findings;\n },\n};\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud-KMS SDK detector: classical asymmetric keys minted at RUNTIME through a
|
|
3
|
+
* cloud KMS SDK call — the application-code counterpart to the Terraform detector
|
|
4
|
+
* (which covers the infrastructure-as-code path). AWS KMS `CreateKey` /
|
|
5
|
+
* `GenerateDataKeyPair` select the key type with a `KeySpec` / `KeyPairSpec`
|
|
6
|
+
* (legacy `CustomerMasterKeySpec`) field whose value is `RSA_*` or `ECC_*`.
|
|
7
|
+
*
|
|
8
|
+
* These field names + `RSA_2048` / `ECC_NIST_P256` values are specific to the AWS
|
|
9
|
+
* KMS/ACM API across every SDK language (JS/TS, Python/boto3, Java, Go, the CLI, JSON
|
|
10
|
+
* policies), so a single lexical rule catches them at very low false-positive rate.
|
|
11
|
+
* Covered forms: the quoted-value SDK/JSON form (`KeySpec: "RSA_2048"`), the camelCase
|
|
12
|
+
* CDK/Pulumi prop form (`customerMasterKeySpec: "RSA_2048"`), and the AWS CDK ENUM
|
|
13
|
+
* form (`kms.KeySpec.RSA_2048`, `KeyAlgorithm.EC_prime256v1`). Terraform uses the
|
|
14
|
+
* snake_case `customer_master_key_spec`, so this never double-counts with the IaC
|
|
15
|
+
* detector.
|
|
16
|
+
*
|
|
17
|
+
* HNDL: an RSA KMS key (encryption/KEM) and an EC KMS key (which AWS KMS can use
|
|
18
|
+
* for ECDH key agreement as well as ECDSA signing) are both harvest-now-decrypt-
|
|
19
|
+
* later exposed → hndl:true.
|
|
20
|
+
*/
|
|
21
|
+
import type { Detector } from "../types.js";
|
|
22
|
+
/** Detects classical asymmetric keys provisioned via a cloud KMS SDK call. */
|
|
23
|
+
export declare const cloudKmsDetector: Detector;
|
|
24
|
+
//# sourceMappingURL=cloud-kms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-kms.d.ts","sourceRoot":"","sources":["../../src/detectors/cloud-kms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAoE/D,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,EAAE,QAmD9B,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension, maskBlockComments, maskCommentLines, } from "../detect-utils.js";
|
|
2
|
+
import { isCloudTemplateFile } from "./cloudformation.js";
|
|
3
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
4
|
+
// The AWS KMS / ACM key-spec field names. Both PascalCase (SDK / boto3 / JSON) and
|
|
5
|
+
// camelCase (AWS CDK / Pulumi props) leading letters are accepted. `KeyAlgorithm` is
|
|
6
|
+
// the ACM certificate key spec.
|
|
7
|
+
const SPEC_KEYS = "[Kk]eySpec|[Kk]eyPairSpec|[Cc]ustomerMasterKeySpec|[Kk]eyAlgorithm";
|
|
8
|
+
// (a) The QUOTED-VALUE form: `KeySpec: "RSA_2048"` / `"KeySpec": "RSA_2048"` /
|
|
9
|
+
// `customerMasterKeySpec: "RSA_2048"` (SDK, boto3, JSON, Pulumi props).
|
|
10
|
+
const RE_KMS_RSA = new RegExp(`\\b(?:${SPEC_KEYS})"?\\s*[:=]\\s*['"](?:RSA_\\d+)['"]`, "g");
|
|
11
|
+
const RE_KMS_EC = new RegExp(`\\b(?:${SPEC_KEYS})"?\\s*[:=]\\s*['"](?:ECC_[A-Z0-9_]+|EC_[A-Za-z0-9]+)['"]`, "g");
|
|
12
|
+
// (b) The ENUM-MEMBER form used by AWS CDK: `kms.KeySpec.RSA_2048`,
|
|
13
|
+
// `KeySpec.ECC_NIST_P256`, `KeyAlgorithm.RSA_2048`, `KeyAlgorithm.EC_prime256v1`
|
|
14
|
+
// — an enum reference, no quoted value, so (a) never matches it.
|
|
15
|
+
const RE_KMS_RSA_ENUM = /\b(?:KeySpec|KeyAlgorithm)\.RSA_\d+\b/g;
|
|
16
|
+
const RE_KMS_EC_ENUM = /\b(?:KeySpec|KeyAlgorithm)\.(?:ECC_[A-Z0-9_]+|EC_[A-Za-z0-9]+)\b/g;
|
|
17
|
+
// (c) GCP Cloud KMS: the CryptoKeyVersion algorithm — `RSA_SIGN_PSS_2048_SHA256`,
|
|
18
|
+
// `RSA_DECRYPT_OAEP_*`, `EC_SIGN_P256_SHA256` (enum or quoted string). The
|
|
19
|
+
// `RSA_SIGN`/`RSA_DECRYPT`/`EC_SIGN` prefixes are GCP-KMS-specific.
|
|
20
|
+
const RE_GCP_KMS_RSA = /\bRSA_(?:SIGN|DECRYPT)_[A-Z0-9_]+/g;
|
|
21
|
+
const RE_GCP_KMS_EC = /\bEC_SIGN_[A-Z0-9_]+/g;
|
|
22
|
+
// (d) Azure Key Vault SDK: `createRsaKey` / `createEcKey` (+ `Create*KeyOptions`) and
|
|
23
|
+
// `KeyType.Rsa` / `KeyType.Ec`.
|
|
24
|
+
const RE_AZURE_KV_RSA = /\b[Cc]reateRsaKey(?:Options)?\b|\bKeyType\.Rsa\b/g;
|
|
25
|
+
const RE_AZURE_KV_EC = /\b[Cc]reateEcKey(?:Options)?\b|\bKeyType\.Ec\b/g;
|
|
26
|
+
const RULE_KMS_RSA = {
|
|
27
|
+
id: "cloud-kms-rsa",
|
|
28
|
+
title: "Cloud KMS RSA key",
|
|
29
|
+
description: "AWS/GCP/Azure KMS RSA key spec (KeySpec / RSA_SIGN_* / createRsaKey)",
|
|
30
|
+
category: "kem",
|
|
31
|
+
severity: "high",
|
|
32
|
+
confidence: "high",
|
|
33
|
+
algorithm: "RSA",
|
|
34
|
+
hndl: true,
|
|
35
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
36
|
+
message: "Mints a classical RSA key via a cloud KMS SDK (AWS/GCP/Azure); harvest-now-decrypt-later exposed for encryption.",
|
|
37
|
+
remediation: "Plan migration to PQC as cloud KMS adds ML-KEM / ML-DSA key specs.",
|
|
38
|
+
};
|
|
39
|
+
const RULE_KMS_EC = {
|
|
40
|
+
id: "cloud-kms-ec",
|
|
41
|
+
title: "Cloud KMS EC key",
|
|
42
|
+
description: "AWS/GCP/Azure KMS EC key spec (ECC_* / EC_SIGN_* / createEcKey)",
|
|
43
|
+
category: "key-exchange",
|
|
44
|
+
severity: "high",
|
|
45
|
+
confidence: "high",
|
|
46
|
+
algorithm: "ECDH",
|
|
47
|
+
hndl: true,
|
|
48
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
49
|
+
message: "Mints a classical EC key via a cloud KMS SDK (AWS/GCP/Azure); EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).",
|
|
50
|
+
remediation: "For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
51
|
+
};
|
|
52
|
+
/** Detects classical asymmetric keys provisioned via a cloud KMS SDK call. */
|
|
53
|
+
export const cloudKmsDetector = {
|
|
54
|
+
id: "cloud-kms",
|
|
55
|
+
description: "Classical asymmetric keys minted via a cloud KMS/ACM SDK, AWS CDK, or Pulumi",
|
|
56
|
+
scope: "config",
|
|
57
|
+
language: "any",
|
|
58
|
+
rules: [RULE_KMS_RSA, RULE_KMS_EC],
|
|
59
|
+
// Skip prose/docs: a README or tutorial showing `KeySpec: "RSA_2048"` to describe
|
|
60
|
+
// the KMS API is not a live key-minting call.
|
|
61
|
+
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
|
|
62
|
+
detect({ file, content }) {
|
|
63
|
+
// Fast reject: only proceed if an AWS / GCP / Azure KMS key-spec marker is present.
|
|
64
|
+
const lc = content.toLowerCase();
|
|
65
|
+
if (!lc.includes("keyspec") &&
|
|
66
|
+
!lc.includes("keypairspec") &&
|
|
67
|
+
!lc.includes("keyalgorithm") &&
|
|
68
|
+
!lc.includes("rsa_sign") &&
|
|
69
|
+
!lc.includes("rsa_decrypt") &&
|
|
70
|
+
!lc.includes("ec_sign") &&
|
|
71
|
+
!lc.includes("creatersakey") &&
|
|
72
|
+
!lc.includes("createeckey") &&
|
|
73
|
+
!lc.includes("keytype.")) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
// Inside a CloudFormation / ARM template FILE, the cloudformation detector owns
|
|
77
|
+
// the KMS key specs — defer so a KeySpec line is not counted twice. Gated to the
|
|
78
|
+
// template extensions it scans, so an SDK call in a `.ts` stays covered here.
|
|
79
|
+
if (isCloudTemplateFile(file, content))
|
|
80
|
+
return [];
|
|
81
|
+
// Mask comments before scanning so a commented-out `# KeySpec: "RSA_2048"` in a
|
|
82
|
+
// YAML/JSON/HCL config (which the central source-comment stripper doesn't cover)
|
|
83
|
+
// can't fire. Offsets are preserved, so the finding's snippet — built from the
|
|
84
|
+
// original `content` — stays exact.
|
|
85
|
+
const scan = maskCommentLines(maskBlockComments(content), ["#", "//"]);
|
|
86
|
+
const findings = [];
|
|
87
|
+
const add = (re, rule) => eachMatch(re, scan, (m) => findings.push(findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length })));
|
|
88
|
+
add(RE_KMS_RSA, RULE_KMS_RSA);
|
|
89
|
+
add(RE_KMS_EC, RULE_KMS_EC);
|
|
90
|
+
add(RE_KMS_RSA_ENUM, RULE_KMS_RSA);
|
|
91
|
+
add(RE_KMS_EC_ENUM, RULE_KMS_EC);
|
|
92
|
+
add(RE_GCP_KMS_RSA, RULE_KMS_RSA);
|
|
93
|
+
add(RE_GCP_KMS_EC, RULE_KMS_EC);
|
|
94
|
+
add(RE_AZURE_KV_RSA, RULE_KMS_RSA);
|
|
95
|
+
add(RE_AZURE_KV_EC, RULE_KMS_EC);
|
|
96
|
+
return findings;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=cloud-kms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-kms.js","sourceRoot":"","sources":["../../src/detectors/cloud-kms.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Cloud-KMS SDK detector: classical asymmetric keys minted at RUNTIME through a\n * cloud KMS SDK call — the application-code counterpart to the Terraform detector\n * (which covers the infrastructure-as-code path). AWS KMS `CreateKey` /\n * `GenerateDataKeyPair` select the key type with a `KeySpec` / `KeyPairSpec`\n * (legacy `CustomerMasterKeySpec`) field whose value is `RSA_*` or `ECC_*`.\n *\n * These field names + `RSA_2048` / `ECC_NIST_P256` values are specific to the AWS\n * KMS/ACM API across every SDK language (JS/TS, Python/boto3, Java, Go, the CLI, JSON\n * policies), so a single lexical rule catches them at very low false-positive rate.\n * Covered forms: the quoted-value SDK/JSON form (`KeySpec: \"RSA_2048\"`), the camelCase\n * CDK/Pulumi prop form (`customerMasterKeySpec: \"RSA_2048\"`), and the AWS CDK ENUM\n * form (`kms.KeySpec.RSA_2048`, `KeyAlgorithm.EC_prime256v1`). Terraform uses the\n * snake_case `customer_master_key_spec`, so this never double-counts with the IaC\n * detector.\n *\n * HNDL: an RSA KMS key (encryption/KEM) and an EC KMS key (which AWS KMS can use\n * for ECDH key agreement as well as ECDSA signing) are both harvest-now-decrypt-\n * later exposed → hndl:true.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n DOC_EXTENSIONS,\n eachMatch,\n findingFromRule,\n hasExtension,\n maskBlockComments,\n maskCommentLines,\n} from \"../detect-utils.js\";\nimport { isCloudTemplateFile } from \"./cloudformation.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n// The AWS KMS / ACM key-spec field names. Both PascalCase (SDK / boto3 / JSON) and\n// camelCase (AWS CDK / Pulumi props) leading letters are accepted. `KeyAlgorithm` is\n// the ACM certificate key spec.\nconst SPEC_KEYS = \"[Kk]eySpec|[Kk]eyPairSpec|[Cc]ustomerMasterKeySpec|[Kk]eyAlgorithm\";\n// (a) The QUOTED-VALUE form: `KeySpec: \"RSA_2048\"` / `\"KeySpec\": \"RSA_2048\"` /\n// `customerMasterKeySpec: \"RSA_2048\"` (SDK, boto3, JSON, Pulumi props).\nconst RE_KMS_RSA = new RegExp(`\\\\b(?:${SPEC_KEYS})\"?\\\\s*[:=]\\\\s*['\"](?:RSA_\\\\d+)['\"]`, \"g\");\nconst RE_KMS_EC = new RegExp(\n `\\\\b(?:${SPEC_KEYS})\"?\\\\s*[:=]\\\\s*['\"](?:ECC_[A-Z0-9_]+|EC_[A-Za-z0-9]+)['\"]`,\n \"g\",\n);\n// (b) The ENUM-MEMBER form used by AWS CDK: `kms.KeySpec.RSA_2048`,\n// `KeySpec.ECC_NIST_P256`, `KeyAlgorithm.RSA_2048`, `KeyAlgorithm.EC_prime256v1`\n// — an enum reference, no quoted value, so (a) never matches it.\nconst RE_KMS_RSA_ENUM = /\\b(?:KeySpec|KeyAlgorithm)\\.RSA_\\d+\\b/g;\nconst RE_KMS_EC_ENUM = /\\b(?:KeySpec|KeyAlgorithm)\\.(?:ECC_[A-Z0-9_]+|EC_[A-Za-z0-9]+)\\b/g;\n// (c) GCP Cloud KMS: the CryptoKeyVersion algorithm — `RSA_SIGN_PSS_2048_SHA256`,\n// `RSA_DECRYPT_OAEP_*`, `EC_SIGN_P256_SHA256` (enum or quoted string). The\n// `RSA_SIGN`/`RSA_DECRYPT`/`EC_SIGN` prefixes are GCP-KMS-specific.\nconst RE_GCP_KMS_RSA = /\\bRSA_(?:SIGN|DECRYPT)_[A-Z0-9_]+/g;\nconst RE_GCP_KMS_EC = /\\bEC_SIGN_[A-Z0-9_]+/g;\n// (d) Azure Key Vault SDK: `createRsaKey` / `createEcKey` (+ `Create*KeyOptions`) and\n// `KeyType.Rsa` / `KeyType.Ec`.\nconst RE_AZURE_KV_RSA = /\\b[Cc]reateRsaKey(?:Options)?\\b|\\bKeyType\\.Rsa\\b/g;\nconst RE_AZURE_KV_EC = /\\b[Cc]reateEcKey(?:Options)?\\b|\\bKeyType\\.Ec\\b/g;\n\nconst RULE_KMS_RSA: RuleMeta = {\n id: \"cloud-kms-rsa\",\n title: \"Cloud KMS RSA key\",\n description: \"AWS/GCP/Azure KMS RSA key spec (KeySpec / RSA_SIGN_* / createRsaKey)\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Mints a classical RSA key via a cloud KMS SDK (AWS/GCP/Azure); harvest-now-decrypt-later exposed for encryption.\",\n remediation: \"Plan migration to PQC as cloud KMS adds ML-KEM / ML-DSA key specs.\",\n};\nconst RULE_KMS_EC: RuleMeta = {\n id: \"cloud-kms-ec\",\n title: \"Cloud KMS EC key\",\n description: \"AWS/GCP/Azure KMS EC key spec (ECC_* / EC_SIGN_* / createEcKey)\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Mints a classical EC key via a cloud KMS SDK (AWS/GCP/Azure); EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).\",\n remediation:\n \"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).\",\n};\n\n/** Detects classical asymmetric keys provisioned via a cloud KMS SDK call. */\nexport const cloudKmsDetector: Detector = {\n id: \"cloud-kms\",\n description: \"Classical asymmetric keys minted via a cloud KMS/ACM SDK, AWS CDK, or Pulumi\",\n scope: \"config\",\n language: \"any\",\n rules: [RULE_KMS_RSA, RULE_KMS_EC],\n // Skip prose/docs: a README or tutorial showing `KeySpec: \"RSA_2048\"` to describe\n // the KMS API is not a live key-minting call.\n appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n // Fast reject: only proceed if an AWS / GCP / Azure KMS key-spec marker is present.\n const lc = content.toLowerCase();\n if (\n !lc.includes(\"keyspec\") &&\n !lc.includes(\"keypairspec\") &&\n !lc.includes(\"keyalgorithm\") &&\n !lc.includes(\"rsa_sign\") &&\n !lc.includes(\"rsa_decrypt\") &&\n !lc.includes(\"ec_sign\") &&\n !lc.includes(\"creatersakey\") &&\n !lc.includes(\"createeckey\") &&\n !lc.includes(\"keytype.\")\n ) {\n return [];\n }\n // Inside a CloudFormation / ARM template FILE, the cloudformation detector owns\n // the KMS key specs — defer so a KeySpec line is not counted twice. Gated to the\n // template extensions it scans, so an SDK call in a `.ts` stays covered here.\n if (isCloudTemplateFile(file, content)) return [];\n // Mask comments before scanning so a commented-out `# KeySpec: \"RSA_2048\"` in a\n // YAML/JSON/HCL config (which the central source-comment stripper doesn't cover)\n // can't fire. Offsets are preserved, so the finding's snippet — built from the\n // original `content` — stays exact.\n const scan = maskCommentLines(maskBlockComments(content), [\"#\", \"//\"]);\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta): void =>\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n add(RE_KMS_RSA, RULE_KMS_RSA);\n add(RE_KMS_EC, RULE_KMS_EC);\n add(RE_KMS_RSA_ENUM, RULE_KMS_RSA);\n add(RE_KMS_EC_ENUM, RULE_KMS_EC);\n add(RE_GCP_KMS_RSA, RULE_KMS_RSA);\n add(RE_GCP_KMS_EC, RULE_KMS_EC);\n add(RE_AZURE_KV_RSA, RULE_KMS_RSA);\n add(RE_AZURE_KV_EC, RULE_KMS_EC);\n return findings;\n },\n};\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IaC detector: classical asymmetric cryptography (and legacy TLS config)
|
|
3
|
+
* declared in AWS CloudFormation / Azure ARM (incl. Bicep-compiled JSON)
|
|
4
|
+
* templates — plus CDK `cdk synth` output, which is plain CloudFormation
|
|
5
|
+
* JSON. This is a distinct surface from `terraform.ts`: CloudFormation/ARM
|
|
6
|
+
* use PascalCase JSON/YAML property names, never Terraform's snake_case HCL
|
|
7
|
+
* attributes, so the two detectors never collide on the same file.
|
|
8
|
+
*
|
|
9
|
+
* Covered:
|
|
10
|
+
* - AWS::KMS::Key `KeySpec`: `"RSA_…"` (kem) / `"ECC_…"` (key-exchange)
|
|
11
|
+
* - AWS::CertificateManager::Certificate
|
|
12
|
+
* `KeyAlgorithm`: `"RSA_…"` / `"EC_…"` (certificate)
|
|
13
|
+
* - CloudFront distribution `MinimumProtocolVersion`: legacy `TLSv1` / `TLSv1.1` (tls)
|
|
14
|
+
* - ELB/ALB listener `SslPolicy`: legacy `ELBSecurityPolicy-2016-08` /
|
|
15
|
+
* `-TLS-1-0-…` / `-TLS-1-1-…` (tls)
|
|
16
|
+
* - Azure ARM `Microsoft.KeyVault` key `kty`: `"RSA"` (kem) / `"EC"` (key-exchange)
|
|
17
|
+
*
|
|
18
|
+
* `KeySpec` is PascalCase and case-sensitive, so it never matches Terraform's
|
|
19
|
+
* snake_case `customer_master_key_spec` (same underlying AWS API, different
|
|
20
|
+
* authoring surface). A fast-reject on a CFN/ARM marker keeps this detector
|
|
21
|
+
* from ever firing on arbitrary JSON/YAML that happens to contain one of
|
|
22
|
+
* these property names in an unrelated context.
|
|
23
|
+
*/
|
|
24
|
+
import type { Detector } from "../types.js";
|
|
25
|
+
/**
|
|
26
|
+
* True when a file is one THIS detector actually scans (a `.json`/`.yaml`/`.yml`
|
|
27
|
+
* template). The `cloud-kms` and `jwk` detectors defer to this detector ONLY on
|
|
28
|
+
* such files — deferring on any extension that merely contains a marker string
|
|
29
|
+
* (e.g. a `.ts` with an `AWS::KMS` comment) would drop a real finding, since this
|
|
30
|
+
* detector never runs there.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isCloudTemplateFile(file: string, content: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Detects classical asymmetric crypto and legacy TLS config declared in
|
|
35
|
+
* AWS CloudFormation / Azure ARM (incl. Bicep-compiled JSON) templates.
|
|
36
|
+
*/
|
|
37
|
+
export declare const cloudformationDetector: Detector;
|
|
38
|
+
//# sourceMappingURL=cloudformation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudformation.d.ts","sourceRoot":"","sources":["../../src/detectors/cloudformation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA0B/D;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1E;AA6ID;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,QA8CpC,CAAC"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH } from "../cwe.js";
|
|
3
|
+
const CFN_EXTENSIONS = [".json", ".yaml", ".yml"];
|
|
4
|
+
// Fast-reject markers: at least one must appear before any rule regex runs, so
|
|
5
|
+
// this detector never scans arbitrary JSON/YAML that merely happens to share
|
|
6
|
+
// an attribute name (`KeySpec`, `kty`, …) with an unrelated schema.
|
|
7
|
+
const CFN_MARKERS = [
|
|
8
|
+
"AWS::KMS",
|
|
9
|
+
"AWS::CertificateManager",
|
|
10
|
+
"AWSTemplateFormatVersion",
|
|
11
|
+
"MinimumProtocolVersion",
|
|
12
|
+
"Microsoft.KeyVault",
|
|
13
|
+
"SslPolicy", // unquoted so a YAML `SslPolicy:` key also gates the file in, not just JSON
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* True when the content looks like a CloudFormation / ARM deployment template (one
|
|
17
|
+
* of the {@link CFN_MARKERS} is present).
|
|
18
|
+
*/
|
|
19
|
+
function isCloudTemplate(content) {
|
|
20
|
+
return CFN_MARKERS.some((marker) => content.includes(marker));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* True when a file is one THIS detector actually scans (a `.json`/`.yaml`/`.yml`
|
|
24
|
+
* template). The `cloud-kms` and `jwk` detectors defer to this detector ONLY on
|
|
25
|
+
* such files — deferring on any extension that merely contains a marker string
|
|
26
|
+
* (e.g. a `.ts` with an `AWS::KMS` comment) would drop a real finding, since this
|
|
27
|
+
* detector never runs there.
|
|
28
|
+
*/
|
|
29
|
+
export function isCloudTemplateFile(file, content) {
|
|
30
|
+
return hasExtension(file, CFN_EXTENSIONS) && isCloudTemplate(content);
|
|
31
|
+
}
|
|
32
|
+
// Each attribute is matched with an optional quote around the key and `:` as
|
|
33
|
+
// the separator (both plain-JSON and YAML-block-scalar forms use `:`). The
|
|
34
|
+
// `(?<![\w"-])` lookbehind stops a longer/prefixed attribute name from
|
|
35
|
+
// matching on its suffix.
|
|
36
|
+
// Match all three AWS KMS spec-key spellings so this detector fully owns KMS keys
|
|
37
|
+
// inside a template (KeySpec, the KeyPair variant, and the legacy CustomerMasterKeySpec).
|
|
38
|
+
const RE_CFN_KMS_RSA = /(?<![\w"-])"?(?:KeySpec|KeyPairSpec|CustomerMasterKeySpec)"?\s*:\s*"?RSA_\d+"?/g;
|
|
39
|
+
const RE_CFN_KMS_EC = /(?<![\w"-])"?(?:KeySpec|KeyPairSpec|CustomerMasterKeySpec)"?\s*:\s*"?ECC_[A-Z0-9_]+"?/g;
|
|
40
|
+
const RE_CFN_ACM_RSA = /(?<![\w"-])"?KeyAlgorithm"?\s*:\s*"?RSA_\d+"?/g;
|
|
41
|
+
const RE_CFN_ACM_EC = /(?<![\w"-])"?KeyAlgorithm"?\s*:\s*"?EC_[A-Za-z0-9]+"?/g;
|
|
42
|
+
// CloudFront: only the pre-2018 "TLSv1" / "TLSv1_2016" / "TLSv1.1_2016"
|
|
43
|
+
// values are legacy; the lookahead delimiter stops this from matching a
|
|
44
|
+
// "TLSv1.2_2018"-style value that merely starts with the same prefix.
|
|
45
|
+
const RE_CFN_CLOUDFRONT_TLS = /(?<![\w"-])"?MinimumProtocolVersion"?\s*:\s*"?(?:TLSv1(?:\.1)?_2016|TLSv1|SSLv3)(?=["'\s,}]|$)/gm;
|
|
46
|
+
// ELB/ALB legacy SSL negotiation policies (named, dated policy strings). The value
|
|
47
|
+
// quote is optional (`"?`) so YAML block scalars (`SslPolicy: ELBSecurityPolicy-...`)
|
|
48
|
+
// match too, with a delimiter lookahead so a longer/newer policy name that merely
|
|
49
|
+
// shares the prefix is not matched.
|
|
50
|
+
const RE_CFN_ELB_TLS = /(?<![\w"-])"?SslPolicy"?\s*:\s*"?ELBSecurityPolicy-(?:2016-08|TLS-1-0-\d{4}-\d{2}|TLS-1-1-\d{4}-\d{2})(?=["'\s,}]|$)/g;
|
|
51
|
+
// Azure ARM Microsoft.KeyVault key resource `properties.kty`.
|
|
52
|
+
const RE_CFN_ARM_KV_RSA = /(?<![\w"-])"?kty"?\s*:\s*"?RSA"?(?!\w)/g;
|
|
53
|
+
const RE_CFN_ARM_KV_EC = /(?<![\w"-])"?kty"?\s*:\s*"?EC"?(?!\w)/g;
|
|
54
|
+
const RULE_CFN_KMS_RSA = {
|
|
55
|
+
id: "cfn-kms-rsa",
|
|
56
|
+
title: "CloudFormation KMS RSA key",
|
|
57
|
+
description: 'AWS::KMS::Key KeySpec = "RSA_*"',
|
|
58
|
+
category: "kem",
|
|
59
|
+
severity: "high",
|
|
60
|
+
confidence: "high",
|
|
61
|
+
algorithm: "RSA",
|
|
62
|
+
hndl: true,
|
|
63
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
64
|
+
message: "CloudFormation provisions a classical RSA KMS key (harvest-now-decrypt-later exposed for encryption CMKs).",
|
|
65
|
+
remediation: "Plan migration to PQC as cloud KMS adds ML-KEM / ML-DSA key specs.",
|
|
66
|
+
};
|
|
67
|
+
const RULE_CFN_KMS_EC = {
|
|
68
|
+
id: "cfn-kms-ec",
|
|
69
|
+
title: "CloudFormation KMS EC key",
|
|
70
|
+
description: 'AWS::KMS::Key KeySpec = "ECC_*"',
|
|
71
|
+
category: "key-exchange",
|
|
72
|
+
severity: "high",
|
|
73
|
+
confidence: "high",
|
|
74
|
+
algorithm: "ECDH",
|
|
75
|
+
hndl: true,
|
|
76
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
77
|
+
message: "CloudFormation provisions a classical EC KMS key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).",
|
|
78
|
+
remediation: "For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
79
|
+
};
|
|
80
|
+
const RULE_CFN_ACM_RSA = {
|
|
81
|
+
id: "cfn-acm-rsa",
|
|
82
|
+
title: "CloudFormation ACM RSA certificate",
|
|
83
|
+
description: 'AWS::CertificateManager::Certificate KeyAlgorithm = "RSA_*"',
|
|
84
|
+
category: "certificate",
|
|
85
|
+
severity: "high",
|
|
86
|
+
confidence: "high",
|
|
87
|
+
algorithm: "RSA",
|
|
88
|
+
hndl: true,
|
|
89
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
90
|
+
message: "CloudFormation provisions an ACM certificate with a classical RSA key, which is not quantum-safe.",
|
|
91
|
+
remediation: "Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).",
|
|
92
|
+
};
|
|
93
|
+
const RULE_CFN_ACM_EC = {
|
|
94
|
+
id: "cfn-acm-ec",
|
|
95
|
+
title: "CloudFormation ACM EC certificate",
|
|
96
|
+
description: 'AWS::CertificateManager::Certificate KeyAlgorithm = "EC_*"',
|
|
97
|
+
category: "certificate",
|
|
98
|
+
severity: "high",
|
|
99
|
+
confidence: "high",
|
|
100
|
+
algorithm: "ECDSA",
|
|
101
|
+
hndl: false,
|
|
102
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
103
|
+
message: "CloudFormation provisions an ACM certificate with a classical EC key, forgeable by a quantum attacker.",
|
|
104
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
105
|
+
};
|
|
106
|
+
const RULE_CFN_CLOUDFRONT_TLS = {
|
|
107
|
+
id: "cfn-cloudfront-legacy-tls",
|
|
108
|
+
title: "CloudFormation CloudFront legacy TLS",
|
|
109
|
+
description: 'CloudFront Distribution MinimumProtocolVersion = "TLSv1" / "TLSv1.1"',
|
|
110
|
+
category: "tls",
|
|
111
|
+
severity: "medium",
|
|
112
|
+
confidence: "high",
|
|
113
|
+
hndl: false,
|
|
114
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
115
|
+
message: "CloudFront distribution permits TLS 1.0/1.1, which are deprecated and insecure.",
|
|
116
|
+
remediation: "Set MinimumProtocolVersion to TLSv1.2_2021 (or later) and prefer PQC-hybrid key exchange.",
|
|
117
|
+
};
|
|
118
|
+
const RULE_CFN_ELB_TLS = {
|
|
119
|
+
id: "cfn-elb-legacy-tls",
|
|
120
|
+
title: "CloudFormation ELB/ALB legacy TLS policy",
|
|
121
|
+
description: "Elastic Load Balancer SslPolicy naming a pre-2017 legacy policy",
|
|
122
|
+
category: "tls",
|
|
123
|
+
severity: "medium",
|
|
124
|
+
confidence: "high",
|
|
125
|
+
hndl: false,
|
|
126
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
127
|
+
message: "Load balancer listener uses a legacy SSL negotiation policy permitting TLS 1.0/1.1.",
|
|
128
|
+
remediation: "Use ELBSecurityPolicy-TLS13-1-2-2021-06 (or the latest FS+TLS1.2/1.3 policy).",
|
|
129
|
+
};
|
|
130
|
+
const RULE_CFN_ARM_KV_RSA = {
|
|
131
|
+
id: "cfn-arm-keyvault-rsa",
|
|
132
|
+
title: "ARM template Key Vault RSA key",
|
|
133
|
+
description: 'Microsoft.KeyVault key resource kty = "RSA"',
|
|
134
|
+
category: "kem",
|
|
135
|
+
severity: "high",
|
|
136
|
+
confidence: "high",
|
|
137
|
+
algorithm: "RSA",
|
|
138
|
+
hndl: true,
|
|
139
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
140
|
+
message: "ARM template provisions a classical RSA Azure Key Vault key, which is not quantum-safe.",
|
|
141
|
+
remediation: "Plan migration to PQC (ML-KEM-768 / ML-DSA-65).",
|
|
142
|
+
};
|
|
143
|
+
const RULE_CFN_ARM_KV_EC = {
|
|
144
|
+
id: "cfn-arm-keyvault-ec",
|
|
145
|
+
title: "ARM template Key Vault EC key",
|
|
146
|
+
description: 'Microsoft.KeyVault key resource kty = "EC"',
|
|
147
|
+
category: "key-exchange",
|
|
148
|
+
severity: "high",
|
|
149
|
+
confidence: "high",
|
|
150
|
+
algorithm: "ECDH",
|
|
151
|
+
hndl: true,
|
|
152
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
153
|
+
message: "ARM template provisions a classical EC Azure Key Vault key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).",
|
|
154
|
+
remediation: "For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Detects classical asymmetric crypto and legacy TLS config declared in
|
|
158
|
+
* AWS CloudFormation / Azure ARM (incl. Bicep-compiled JSON) templates.
|
|
159
|
+
*/
|
|
160
|
+
export const cloudformationDetector = {
|
|
161
|
+
id: "cloudformation-crypto",
|
|
162
|
+
description: "Classical asymmetric crypto and legacy TLS config declared in CloudFormation / ARM templates (IaC)",
|
|
163
|
+
scope: "config",
|
|
164
|
+
language: "any",
|
|
165
|
+
rules: [
|
|
166
|
+
RULE_CFN_KMS_RSA,
|
|
167
|
+
RULE_CFN_KMS_EC,
|
|
168
|
+
RULE_CFN_ACM_RSA,
|
|
169
|
+
RULE_CFN_ACM_EC,
|
|
170
|
+
RULE_CFN_CLOUDFRONT_TLS,
|
|
171
|
+
RULE_CFN_ELB_TLS,
|
|
172
|
+
RULE_CFN_ARM_KV_RSA,
|
|
173
|
+
RULE_CFN_ARM_KV_EC,
|
|
174
|
+
],
|
|
175
|
+
appliesTo: (f) => hasExtension(f, CFN_EXTENSIONS),
|
|
176
|
+
detect({ file, content }) {
|
|
177
|
+
if (!CFN_MARKERS.some((marker) => content.includes(marker)))
|
|
178
|
+
return [];
|
|
179
|
+
// Mask whole `#` comment lines (YAML CFN/ARM templates ship large commented-out
|
|
180
|
+
// blocks); harmless for JSON, which has no comments. Offsets preserved so finding
|
|
181
|
+
// locations stay exact. `.yaml`/`.json` are not centrally comment-stripped.
|
|
182
|
+
const scan = maskCommentLines(content, ["#"]);
|
|
183
|
+
const findings = [];
|
|
184
|
+
const add = (re, rule) => eachMatch(re, scan, (m) => findings.push(findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length })));
|
|
185
|
+
add(RE_CFN_KMS_RSA, RULE_CFN_KMS_RSA);
|
|
186
|
+
add(RE_CFN_KMS_EC, RULE_CFN_KMS_EC);
|
|
187
|
+
add(RE_CFN_ACM_RSA, RULE_CFN_ACM_RSA);
|
|
188
|
+
add(RE_CFN_ACM_EC, RULE_CFN_ACM_EC);
|
|
189
|
+
add(RE_CFN_CLOUDFRONT_TLS, RULE_CFN_CLOUDFRONT_TLS);
|
|
190
|
+
add(RE_CFN_ELB_TLS, RULE_CFN_ELB_TLS);
|
|
191
|
+
// The bare `kty` attribute is generic (every JWK has it), so only treat it as
|
|
192
|
+
// an ARM Key Vault key when the ARM resource type is actually present — otherwise
|
|
193
|
+
// a JWK in a file gated in by another marker (e.g. SslPolicy) would misfire here.
|
|
194
|
+
if (content.includes("Microsoft.KeyVault")) {
|
|
195
|
+
add(RE_CFN_ARM_KV_RSA, RULE_CFN_ARM_KV_RSA);
|
|
196
|
+
add(RE_CFN_ARM_KV_EC, RULE_CFN_ARM_KV_EC);
|
|
197
|
+
}
|
|
198
|
+
return findings;
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=cloudformation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudformation.js","sourceRoot":"","sources":["../../src/detectors/cloudformation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IaC detector: classical asymmetric cryptography (and legacy TLS config)\n * declared in AWS CloudFormation / Azure ARM (incl. Bicep-compiled JSON)\n * templates — plus CDK `cdk synth` output, which is plain CloudFormation\n * JSON. This is a distinct surface from `terraform.ts`: CloudFormation/ARM\n * use PascalCase JSON/YAML property names, never Terraform's snake_case HCL\n * attributes, so the two detectors never collide on the same file.\n *\n * Covered:\n * - AWS::KMS::Key `KeySpec`: `\"RSA_…\"` (kem) / `\"ECC_…\"` (key-exchange)\n * - AWS::CertificateManager::Certificate\n * `KeyAlgorithm`: `\"RSA_…\"` / `\"EC_…\"` (certificate)\n * - CloudFront distribution `MinimumProtocolVersion`: legacy `TLSv1` / `TLSv1.1` (tls)\n * - ELB/ALB listener `SslPolicy`: legacy `ELBSecurityPolicy-2016-08` /\n * `-TLS-1-0-…` / `-TLS-1-1-…` (tls)\n * - Azure ARM `Microsoft.KeyVault` key `kty`: `\"RSA\"` (kem) / `\"EC\"` (key-exchange)\n *\n * `KeySpec` is PascalCase and case-sensitive, so it never matches Terraform's\n * snake_case `customer_master_key_spec` (same underlying AWS API, different\n * authoring surface). A fast-reject on a CFN/ARM marker keeps this detector\n * from ever firing on arbitrary JSON/YAML that happens to contain one of\n * these property names in an unrelated context.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule, hasExtension, maskCommentLines } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH } from \"../cwe.js\";\n\nconst CFN_EXTENSIONS: readonly string[] = [\".json\", \".yaml\", \".yml\"];\n\n// Fast-reject markers: at least one must appear before any rule regex runs, so\n// this detector never scans arbitrary JSON/YAML that merely happens to share\n// an attribute name (`KeySpec`, `kty`, …) with an unrelated schema.\nconst CFN_MARKERS: readonly string[] = [\n \"AWS::KMS\",\n \"AWS::CertificateManager\",\n \"AWSTemplateFormatVersion\",\n \"MinimumProtocolVersion\",\n \"Microsoft.KeyVault\",\n \"SslPolicy\", // unquoted so a YAML `SslPolicy:` key also gates the file in, not just JSON\n];\n\n/**\n * True when the content looks like a CloudFormation / ARM deployment template (one\n * of the {@link CFN_MARKERS} is present).\n */\nfunction isCloudTemplate(content: string): boolean {\n return CFN_MARKERS.some((marker) => content.includes(marker));\n}\n\n/**\n * True when a file is one THIS detector actually scans (a `.json`/`.yaml`/`.yml`\n * template). The `cloud-kms` and `jwk` detectors defer to this detector ONLY on\n * such files — deferring on any extension that merely contains a marker string\n * (e.g. a `.ts` with an `AWS::KMS` comment) would drop a real finding, since this\n * detector never runs there.\n */\nexport function isCloudTemplateFile(file: string, content: string): boolean {\n return hasExtension(file, CFN_EXTENSIONS) && isCloudTemplate(content);\n}\n\n// Each attribute is matched with an optional quote around the key and `:` as\n// the separator (both plain-JSON and YAML-block-scalar forms use `:`). The\n// `(?<![\\w\"-])` lookbehind stops a longer/prefixed attribute name from\n// matching on its suffix.\n// Match all three AWS KMS spec-key spellings so this detector fully owns KMS keys\n// inside a template (KeySpec, the KeyPair variant, and the legacy CustomerMasterKeySpec).\nconst RE_CFN_KMS_RSA =\n /(?<![\\w\"-])\"?(?:KeySpec|KeyPairSpec|CustomerMasterKeySpec)\"?\\s*:\\s*\"?RSA_\\d+\"?/g;\nconst RE_CFN_KMS_EC =\n /(?<![\\w\"-])\"?(?:KeySpec|KeyPairSpec|CustomerMasterKeySpec)\"?\\s*:\\s*\"?ECC_[A-Z0-9_]+\"?/g;\nconst RE_CFN_ACM_RSA = /(?<![\\w\"-])\"?KeyAlgorithm\"?\\s*:\\s*\"?RSA_\\d+\"?/g;\nconst RE_CFN_ACM_EC = /(?<![\\w\"-])\"?KeyAlgorithm\"?\\s*:\\s*\"?EC_[A-Za-z0-9]+\"?/g;\n// CloudFront: only the pre-2018 \"TLSv1\" / \"TLSv1_2016\" / \"TLSv1.1_2016\"\n// values are legacy; the lookahead delimiter stops this from matching a\n// \"TLSv1.2_2018\"-style value that merely starts with the same prefix.\nconst RE_CFN_CLOUDFRONT_TLS =\n /(?<![\\w\"-])\"?MinimumProtocolVersion\"?\\s*:\\s*\"?(?:TLSv1(?:\\.1)?_2016|TLSv1|SSLv3)(?=[\"'\\s,}]|$)/gm;\n// ELB/ALB legacy SSL negotiation policies (named, dated policy strings). The value\n// quote is optional (`\"?`) so YAML block scalars (`SslPolicy: ELBSecurityPolicy-...`)\n// match too, with a delimiter lookahead so a longer/newer policy name that merely\n// shares the prefix is not matched.\nconst RE_CFN_ELB_TLS =\n /(?<![\\w\"-])\"?SslPolicy\"?\\s*:\\s*\"?ELBSecurityPolicy-(?:2016-08|TLS-1-0-\\d{4}-\\d{2}|TLS-1-1-\\d{4}-\\d{2})(?=[\"'\\s,}]|$)/g;\n// Azure ARM Microsoft.KeyVault key resource `properties.kty`.\nconst RE_CFN_ARM_KV_RSA = /(?<![\\w\"-])\"?kty\"?\\s*:\\s*\"?RSA\"?(?!\\w)/g;\nconst RE_CFN_ARM_KV_EC = /(?<![\\w\"-])\"?kty\"?\\s*:\\s*\"?EC\"?(?!\\w)/g;\n\nconst RULE_CFN_KMS_RSA: RuleMeta = {\n id: \"cfn-kms-rsa\",\n title: \"CloudFormation KMS RSA key\",\n description: 'AWS::KMS::Key KeySpec = \"RSA_*\"',\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CloudFormation provisions a classical RSA KMS key (harvest-now-decrypt-later exposed for encryption CMKs).\",\n remediation: \"Plan migration to PQC as cloud KMS adds ML-KEM / ML-DSA key specs.\",\n};\nconst RULE_CFN_KMS_EC: RuleMeta = {\n id: \"cfn-kms-ec\",\n title: \"CloudFormation KMS EC key\",\n description: 'AWS::KMS::Key KeySpec = \"ECC_*\"',\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CloudFormation provisions a classical EC KMS key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).\",\n remediation:\n \"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).\",\n};\nconst RULE_CFN_ACM_RSA: RuleMeta = {\n id: \"cfn-acm-rsa\",\n title: \"CloudFormation ACM RSA certificate\",\n description: 'AWS::CertificateManager::Certificate KeyAlgorithm = \"RSA_*\"',\n category: \"certificate\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CloudFormation provisions an ACM certificate with a classical RSA key, which is not quantum-safe.\",\n remediation: \"Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).\",\n};\nconst RULE_CFN_ACM_EC: RuleMeta = {\n id: \"cfn-acm-ec\",\n title: \"CloudFormation ACM EC certificate\",\n description: 'AWS::CertificateManager::Certificate KeyAlgorithm = \"EC_*\"',\n category: \"certificate\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"CloudFormation provisions an ACM certificate with a classical EC key, forgeable by a quantum attacker.\",\n remediation: \"ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).\",\n};\nconst RULE_CFN_CLOUDFRONT_TLS: RuleMeta = {\n id: \"cfn-cloudfront-legacy-tls\",\n title: \"CloudFormation CloudFront legacy TLS\",\n description: 'CloudFront Distribution MinimumProtocolVersion = \"TLSv1\" / \"TLSv1.1\"',\n category: \"tls\",\n severity: \"medium\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_WEAK_STRENGTH,\n message: \"CloudFront distribution permits TLS 1.0/1.1, which are deprecated and insecure.\",\n remediation:\n \"Set MinimumProtocolVersion to TLSv1.2_2021 (or later) and prefer PQC-hybrid key exchange.\",\n};\nconst RULE_CFN_ELB_TLS: RuleMeta = {\n id: \"cfn-elb-legacy-tls\",\n title: \"CloudFormation ELB/ALB legacy TLS policy\",\n description: \"Elastic Load Balancer SslPolicy naming a pre-2017 legacy policy\",\n category: \"tls\",\n severity: \"medium\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_WEAK_STRENGTH,\n message: \"Load balancer listener uses a legacy SSL negotiation policy permitting TLS 1.0/1.1.\",\n remediation: \"Use ELBSecurityPolicy-TLS13-1-2-2021-06 (or the latest FS+TLS1.2/1.3 policy).\",\n};\nconst RULE_CFN_ARM_KV_RSA: RuleMeta = {\n id: \"cfn-arm-keyvault-rsa\",\n title: \"ARM template Key Vault RSA key\",\n description: 'Microsoft.KeyVault key resource kty = \"RSA\"',\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"ARM template provisions a classical RSA Azure Key Vault key, which is not quantum-safe.\",\n remediation: \"Plan migration to PQC (ML-KEM-768 / ML-DSA-65).\",\n};\nconst RULE_CFN_ARM_KV_EC: RuleMeta = {\n id: \"cfn-arm-keyvault-ec\",\n title: \"ARM template Key Vault EC key\",\n description: 'Microsoft.KeyVault key resource kty = \"EC\"',\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"ARM template provisions a classical EC Azure Key Vault key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).\",\n remediation:\n \"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).\",\n};\n\n/**\n * Detects classical asymmetric crypto and legacy TLS config declared in\n * AWS CloudFormation / Azure ARM (incl. Bicep-compiled JSON) templates.\n */\nexport const cloudformationDetector: Detector = {\n id: \"cloudformation-crypto\",\n description:\n \"Classical asymmetric crypto and legacy TLS config declared in CloudFormation / ARM templates (IaC)\",\n scope: \"config\",\n language: \"any\",\n rules: [\n RULE_CFN_KMS_RSA,\n RULE_CFN_KMS_EC,\n RULE_CFN_ACM_RSA,\n RULE_CFN_ACM_EC,\n RULE_CFN_CLOUDFRONT_TLS,\n RULE_CFN_ELB_TLS,\n RULE_CFN_ARM_KV_RSA,\n RULE_CFN_ARM_KV_EC,\n ],\n appliesTo: (f) => hasExtension(f, CFN_EXTENSIONS),\n detect({ file, content }): Finding[] {\n if (!CFN_MARKERS.some((marker) => content.includes(marker))) return [];\n\n // Mask whole `#` comment lines (YAML CFN/ARM templates ship large commented-out\n // blocks); harmless for JSON, which has no comments. Offsets preserved so finding\n // locations stay exact. `.yaml`/`.json` are not centrally comment-stripped.\n const scan = maskCommentLines(content, [\"#\"]);\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta) =>\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n add(RE_CFN_KMS_RSA, RULE_CFN_KMS_RSA);\n add(RE_CFN_KMS_EC, RULE_CFN_KMS_EC);\n add(RE_CFN_ACM_RSA, RULE_CFN_ACM_RSA);\n add(RE_CFN_ACM_EC, RULE_CFN_ACM_EC);\n add(RE_CFN_CLOUDFRONT_TLS, RULE_CFN_CLOUDFRONT_TLS);\n add(RE_CFN_ELB_TLS, RULE_CFN_ELB_TLS);\n // The bare `kty` attribute is generic (every JWK has it), so only treat it as\n // an ARM Key Vault key when the ARM resource type is actually present — otherwise\n // a JWK in a file gated in by another marker (e.g. SslPolicy) would misfire here.\n if (content.includes(\"Microsoft.KeyVault\")) {\n add(RE_CFN_ARM_KV_RSA, RULE_CFN_ARM_KV_RSA);\n add(RE_CFN_ARM_KV_EC, RULE_CFN_ARM_KV_EC);\n }\n return findings;\n },\n};\n"]}
|