@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,157 @@
|
|
|
1
|
+
import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension, maskBlockComments, maskCommentLines, } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
// --- Envoy: match ONLY the Envoy-DISTINCTIVE tokens (`common_tls_context:`, the
|
|
4
|
+
// `*TlsContext` type names, `tls_certificates:`, the `envoy.transport_sockets.tls`
|
|
5
|
+
// transport socket). The generic `certificate_chain:` / `private_key:` fields are
|
|
6
|
+
// deliberately NOT matched here — they appear in unrelated YAML and would
|
|
7
|
+
// cross-label a non-Envoy file as Envoy (audit L2). A distinctive token IS the
|
|
8
|
+
// evidence, so no separate marker gate is needed for this rule (audit L1).
|
|
9
|
+
const RE_ENVOY_TLS = /\b(?:DownstreamTlsContext|UpstreamTlsContext|common_tls_context\s*:|tls_certificates\s*:|envoy\.transport_sockets\.tls)/g;
|
|
10
|
+
// --- Nginx: the classic `ssl_certificate` / `ssl_certificate_key` directive
|
|
11
|
+
// pair. `ssl_certificate_key` is matched first-class; `ssl_certificate` requires
|
|
12
|
+
// a following space so it doesn't also swallow `ssl_certificate_key` as a second
|
|
13
|
+
// hit on the same directive (kept as a single rule regardless).
|
|
14
|
+
const RE_NGINX_TLS = /\bssl_certificate(?:_key)?\s+\S/g;
|
|
15
|
+
// --- HAProxy: a `bind` line that terminates TLS (`ssl crt <path>`) or a
|
|
16
|
+
// `crt-store` section (HAProxy 2.8+ certificate store). `ssl` and `crt` may be
|
|
17
|
+
// separated by other bind args, so match them independently on a bind context.
|
|
18
|
+
const RE_HAPROXY_TLS = /\bcrt-store\b|\bbind\b[^\n]*\bssl\b[^\n]*\bcrt\b|\bbind\b[^\n]*\bcrt\b[^\n]*\bssl\b/g;
|
|
19
|
+
// --- Traefik: dynamic-config TLS certificate entries (`certFile:` + `keyFile:`)
|
|
20
|
+
// or an ACME `certResolver` reference. `certFile`/`keyFile` are Traefik-specific
|
|
21
|
+
// enough (paired with the `certResolver`/`tls:` marker gate) to anchor on.
|
|
22
|
+
const RE_TRAEFIK_TLS = /\bcertResolver\b|\b(?:certFile|keyFile)\s*:/g;
|
|
23
|
+
// --- gRPC in-code TLS channel credentials across languages.
|
|
24
|
+
const RE_GRPC_TLS = /grpc\.ssl_channel_credentials\s*\(|grpc\.credentials\.createSsl\s*\(|\bTlsChannelCredentials\b|\bcredentials\.NewTLS\s*\(/g;
|
|
25
|
+
const MESSAGE = (proxy) => `${proxy} terminates TLS with a classical certificate + ECDHE key exchange; the recorded handshake is harvest-now-decrypt-later exposed — verify the cert key algorithm and plan a PQC-hybrid (X25519MLKEM768) TLS migration.`;
|
|
26
|
+
const REMEDIATION = "Enable hybrid PQC key exchange (X25519MLKEM768) once the proxy/gRPC stack supports it; re-key certificates to PQC signatures (ML-DSA) when available.";
|
|
27
|
+
const RULE_ENVOY = {
|
|
28
|
+
id: "proxy-envoy-tls",
|
|
29
|
+
title: "Envoy classical TLS termination",
|
|
30
|
+
description: "Envoy TLS context terminates TLS with a classical certificate + ECDHE key exchange",
|
|
31
|
+
category: "key-exchange",
|
|
32
|
+
severity: "medium",
|
|
33
|
+
confidence: "medium",
|
|
34
|
+
algorithm: "unknown",
|
|
35
|
+
hndl: true,
|
|
36
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
37
|
+
message: MESSAGE("Envoy"),
|
|
38
|
+
remediation: REMEDIATION,
|
|
39
|
+
};
|
|
40
|
+
const RULE_NGINX = {
|
|
41
|
+
id: "proxy-nginx-tls",
|
|
42
|
+
title: "Nginx classical TLS termination",
|
|
43
|
+
description: "Nginx ssl_certificate/ssl_certificate_key terminates TLS with a classical cert + ECDHE",
|
|
44
|
+
category: "key-exchange",
|
|
45
|
+
severity: "medium",
|
|
46
|
+
confidence: "medium",
|
|
47
|
+
algorithm: "unknown",
|
|
48
|
+
hndl: true,
|
|
49
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
50
|
+
message: MESSAGE("Nginx"),
|
|
51
|
+
remediation: REMEDIATION,
|
|
52
|
+
};
|
|
53
|
+
const RULE_HAPROXY = {
|
|
54
|
+
id: "proxy-haproxy-tls",
|
|
55
|
+
title: "HAProxy classical TLS termination",
|
|
56
|
+
description: "HAProxy bind ssl crt / crt-store terminates TLS with a classical cert + ECDHE",
|
|
57
|
+
category: "key-exchange",
|
|
58
|
+
severity: "medium",
|
|
59
|
+
confidence: "medium",
|
|
60
|
+
algorithm: "unknown",
|
|
61
|
+
hndl: true,
|
|
62
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
63
|
+
message: MESSAGE("HAProxy"),
|
|
64
|
+
remediation: REMEDIATION,
|
|
65
|
+
};
|
|
66
|
+
const RULE_TRAEFIK = {
|
|
67
|
+
id: "proxy-traefik-tls",
|
|
68
|
+
title: "Traefik classical TLS termination",
|
|
69
|
+
description: "Traefik tls certificates (certFile/keyFile) or certResolver terminate TLS with a classical cert + ECDHE",
|
|
70
|
+
category: "key-exchange",
|
|
71
|
+
severity: "medium",
|
|
72
|
+
confidence: "medium",
|
|
73
|
+
algorithm: "unknown",
|
|
74
|
+
hndl: true,
|
|
75
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
76
|
+
message: MESSAGE("Traefik"),
|
|
77
|
+
remediation: REMEDIATION,
|
|
78
|
+
};
|
|
79
|
+
const RULE_GRPC = {
|
|
80
|
+
id: "grpc-tls-credentials",
|
|
81
|
+
title: "gRPC classical TLS channel credentials",
|
|
82
|
+
description: "gRPC channel established with classical TLS credentials (RSA/ECDSA cert + ECDHE)",
|
|
83
|
+
category: "key-exchange",
|
|
84
|
+
severity: "medium",
|
|
85
|
+
confidence: "medium",
|
|
86
|
+
algorithm: "unknown",
|
|
87
|
+
hndl: true,
|
|
88
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
89
|
+
message: MESSAGE("gRPC"),
|
|
90
|
+
remediation: REMEDIATION,
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* PERF fast-reject only — a superset of substrings that appear in every rule's
|
|
94
|
+
* matchable input, so it can never drop a file a rule would flag. Correctness is
|
|
95
|
+
* in the per-rule regexes (each is distinctive enough to self-gate); this list
|
|
96
|
+
* exists purely to skip the regex work on files with no proxy/gRPC token at all.
|
|
97
|
+
*/
|
|
98
|
+
const PROXY_FAST_REJECT = [
|
|
99
|
+
// Envoy
|
|
100
|
+
"tls_context",
|
|
101
|
+
"TlsContext",
|
|
102
|
+
"tls_certificates",
|
|
103
|
+
"transport_sockets.tls",
|
|
104
|
+
// Nginx
|
|
105
|
+
"ssl_certificate",
|
|
106
|
+
// HAProxy (`crt-store` and `bind … ssl … crt` both contain "crt")
|
|
107
|
+
"crt",
|
|
108
|
+
// Traefik
|
|
109
|
+
"certResolver",
|
|
110
|
+
"certFile",
|
|
111
|
+
"keyFile",
|
|
112
|
+
// gRPC
|
|
113
|
+
"ssl_channel_credentials",
|
|
114
|
+
"createSsl",
|
|
115
|
+
"TlsChannelCredentials",
|
|
116
|
+
"credentials.NewTLS",
|
|
117
|
+
];
|
|
118
|
+
const PROXY_RULES = [
|
|
119
|
+
{ meta: RULE_ENVOY, re: RE_ENVOY_TLS },
|
|
120
|
+
{ meta: RULE_NGINX, re: RE_NGINX_TLS },
|
|
121
|
+
{ meta: RULE_HAPROXY, re: RE_HAPROXY_TLS },
|
|
122
|
+
{ meta: RULE_TRAEFIK, re: RE_TRAEFIK_TLS },
|
|
123
|
+
{ meta: RULE_GRPC, re: RE_GRPC_TLS },
|
|
124
|
+
];
|
|
125
|
+
/**
|
|
126
|
+
* Detects classical-cert TLS termination in standalone reverse-proxy /
|
|
127
|
+
* load-balancer config files and in-code gRPC channel credentials. See the
|
|
128
|
+
* module docstring for the covered surfaces and the fast-reject rationale.
|
|
129
|
+
*/
|
|
130
|
+
export const proxyDetector = {
|
|
131
|
+
id: "proxy-tls-crypto",
|
|
132
|
+
description: "Classical-cert TLS termination in reverse-proxy / load-balancer config (Envoy, Nginx, HAProxy, Traefik) and gRPC channel credentials",
|
|
133
|
+
scope: "config",
|
|
134
|
+
language: "any",
|
|
135
|
+
rules: PROXY_RULES.map((r) => r.meta),
|
|
136
|
+
// Config + source files are all in scope (gRPC creds live in code); only prose
|
|
137
|
+
// documentation is excluded, so a README mentioning `ssl_certificate` in a
|
|
138
|
+
// sentence can't fire (the marker gate already handles most of this).
|
|
139
|
+
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
|
|
140
|
+
detect({ file, content }) {
|
|
141
|
+
// Mask comments first so a directive that only appears inside a commented-out
|
|
142
|
+
// block cannot fire.
|
|
143
|
+
const scan = maskCommentLines(maskBlockComments(content), ["#", "//"]);
|
|
144
|
+
// Cheap fast-reject: skip files with none of the distinctive tokens. This is a
|
|
145
|
+
// perf gate only — each rule's regex is distinctive enough to SELF-GATE (a
|
|
146
|
+
// separate too-narrow marker list previously dropped canonical HAProxy/Traefik
|
|
147
|
+
// configs entirely — audit H1/H2), so correctness lives in the per-rule regexes.
|
|
148
|
+
if (!PROXY_FAST_REJECT.some((mk) => scan.includes(mk)))
|
|
149
|
+
return [];
|
|
150
|
+
const findings = [];
|
|
151
|
+
for (const { meta, re } of PROXY_RULES) {
|
|
152
|
+
eachMatch(re, scan, (m) => findings.push(findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length })));
|
|
153
|
+
}
|
|
154
|
+
return findings;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/detectors/proxy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical-certificate TLS termination in standalone\n * reverse-proxy / load-balancer configuration and in-code gRPC channel\n * credentials.\n *\n * A standalone Envoy, Nginx, HAProxy, Traefik, or Caddy config that terminates\n * TLS with an RSA/ECDSA leaf certificate and negotiates an ECDHE key exchange is\n * the canonical harvest-now-decrypt-later (HNDL) surface: an attacker who\n * records today's handshake + ciphertext can decrypt it retroactively once a\n * cryptographically-relevant quantum computer (CRQC) can break the ephemeral\n * (EC)DHE secret. The Kubernetes / service-mesh detectors (`k8s.ts`, `mesh.ts`)\n * cover the CLUSTER-managed cert + mesh-mTLS surfaces; the STANDALONE proxy\n * config FILES and the in-code gRPC credential constructors below are seen by\n * neither them nor the language packs, so they are covered here.\n *\n * Because the config token itself does not name the cert's key algorithm (the\n * key lives in a referenced PEM file, out of band), these findings use\n * `algorithm: \"unknown\"` — the evidence is \"a classical-cert TLS channel is\n * configured HERE\", not a specific family. They are `category: \"key-exchange\"`\n * (the ECDHE handshake is the harvestable primitive) and `hndl: true`.\n * Confidence is `medium`: a config-level indicator of a classical TLS channel,\n * not a matched algorithm string.\n *\n * PRECISION / fast-reject: several of the matched tokens (`ssl_certificate`,\n * `private_key:`, `certificates:`) are generic enough to appear in unrelated\n * YAML/config, so `detect()` bails unless the file carries a distinctive\n * proxy/gRPC marker (see `hasProxyMarker`). This keeps a random YAML that merely\n * has a `private_key:` key, or a Traefik-shaped `certificates:` list that isn't\n * a Traefik config, from firing. Comments are masked (YAML/nginx/HCL `#`, plus\n * `//` and C-style `/* … *\\/` blocks) so a commented-out directive can't fire.\n *\n * Covered surfaces:\n * - Envoy — `common_tls_context:` / `DownstreamTlsContext` /\n * `UpstreamTlsContext` with `tls_certificates:` carrying a\n * `certificate_chain:` + `private_key:` (and the\n * `envoy.transport_sockets.tls` transport socket).\n * - Nginx — `ssl_certificate` + `ssl_certificate_key` directives.\n * - HAProxy — `bind … ssl crt …` lines and `crt-store` sections.\n * - Traefik — a `tls:` block's `certificates:` with `certFile:` + `keyFile:`\n * (dynamic config) or a `certResolver` (ACME) reference.\n * - gRPC — in-code TLS channel credentials: `grpc.ssl_channel_credentials(`\n * (Python), `grpc.credentials.createSsl(` (Node),\n * `TlsChannelCredentials` (Java), `credentials.NewTLS(` (Go).\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 { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n// --- Envoy: match ONLY the Envoy-DISTINCTIVE tokens (`common_tls_context:`, the\n// `*TlsContext` type names, `tls_certificates:`, the `envoy.transport_sockets.tls`\n// transport socket). The generic `certificate_chain:` / `private_key:` fields are\n// deliberately NOT matched here — they appear in unrelated YAML and would\n// cross-label a non-Envoy file as Envoy (audit L2). A distinctive token IS the\n// evidence, so no separate marker gate is needed for this rule (audit L1).\nconst RE_ENVOY_TLS =\n /\\b(?:DownstreamTlsContext|UpstreamTlsContext|common_tls_context\\s*:|tls_certificates\\s*:|envoy\\.transport_sockets\\.tls)/g;\n\n// --- Nginx: the classic `ssl_certificate` / `ssl_certificate_key` directive\n// pair. `ssl_certificate_key` is matched first-class; `ssl_certificate` requires\n// a following space so it doesn't also swallow `ssl_certificate_key` as a second\n// hit on the same directive (kept as a single rule regardless).\nconst RE_NGINX_TLS = /\\bssl_certificate(?:_key)?\\s+\\S/g;\n\n// --- HAProxy: a `bind` line that terminates TLS (`ssl crt <path>`) or a\n// `crt-store` section (HAProxy 2.8+ certificate store). `ssl` and `crt` may be\n// separated by other bind args, so match them independently on a bind context.\nconst RE_HAPROXY_TLS =\n /\\bcrt-store\\b|\\bbind\\b[^\\n]*\\bssl\\b[^\\n]*\\bcrt\\b|\\bbind\\b[^\\n]*\\bcrt\\b[^\\n]*\\bssl\\b/g;\n\n// --- Traefik: dynamic-config TLS certificate entries (`certFile:` + `keyFile:`)\n// or an ACME `certResolver` reference. `certFile`/`keyFile` are Traefik-specific\n// enough (paired with the `certResolver`/`tls:` marker gate) to anchor on.\nconst RE_TRAEFIK_TLS = /\\bcertResolver\\b|\\b(?:certFile|keyFile)\\s*:/g;\n\n// --- gRPC in-code TLS channel credentials across languages.\nconst RE_GRPC_TLS =\n /grpc\\.ssl_channel_credentials\\s*\\(|grpc\\.credentials\\.createSsl\\s*\\(|\\bTlsChannelCredentials\\b|\\bcredentials\\.NewTLS\\s*\\(/g;\n\nconst MESSAGE = (proxy: string): string =>\n `${proxy} terminates TLS with a classical certificate + ECDHE key exchange; the recorded handshake is harvest-now-decrypt-later exposed — verify the cert key algorithm and plan a PQC-hybrid (X25519MLKEM768) TLS migration.`;\n\nconst REMEDIATION =\n \"Enable hybrid PQC key exchange (X25519MLKEM768) once the proxy/gRPC stack supports it; re-key certificates to PQC signatures (ML-DSA) when available.\";\n\nconst RULE_ENVOY: RuleMeta = {\n id: \"proxy-envoy-tls\",\n title: \"Envoy classical TLS termination\",\n description: \"Envoy TLS context terminates TLS with a classical certificate + ECDHE key exchange\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"medium\",\n algorithm: \"unknown\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: MESSAGE(\"Envoy\"),\n remediation: REMEDIATION,\n};\nconst RULE_NGINX: RuleMeta = {\n id: \"proxy-nginx-tls\",\n title: \"Nginx classical TLS termination\",\n description:\n \"Nginx ssl_certificate/ssl_certificate_key terminates TLS with a classical cert + ECDHE\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"medium\",\n algorithm: \"unknown\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: MESSAGE(\"Nginx\"),\n remediation: REMEDIATION,\n};\nconst RULE_HAPROXY: RuleMeta = {\n id: \"proxy-haproxy-tls\",\n title: \"HAProxy classical TLS termination\",\n description: \"HAProxy bind ssl crt / crt-store terminates TLS with a classical cert + ECDHE\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"medium\",\n algorithm: \"unknown\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: MESSAGE(\"HAProxy\"),\n remediation: REMEDIATION,\n};\nconst RULE_TRAEFIK: RuleMeta = {\n id: \"proxy-traefik-tls\",\n title: \"Traefik classical TLS termination\",\n description:\n \"Traefik tls certificates (certFile/keyFile) or certResolver terminate TLS with a classical cert + ECDHE\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"medium\",\n algorithm: \"unknown\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: MESSAGE(\"Traefik\"),\n remediation: REMEDIATION,\n};\nconst RULE_GRPC: RuleMeta = {\n id: \"grpc-tls-credentials\",\n title: \"gRPC classical TLS channel credentials\",\n description: \"gRPC channel established with classical TLS credentials (RSA/ECDSA cert + ECDHE)\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"medium\",\n algorithm: \"unknown\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: MESSAGE(\"gRPC\"),\n remediation: REMEDIATION,\n};\n\n/**\n * PERF fast-reject only — a superset of substrings that appear in every rule's\n * matchable input, so it can never drop a file a rule would flag. Correctness is\n * in the per-rule regexes (each is distinctive enough to self-gate); this list\n * exists purely to skip the regex work on files with no proxy/gRPC token at all.\n */\nconst PROXY_FAST_REJECT: readonly string[] = [\n // Envoy\n \"tls_context\",\n \"TlsContext\",\n \"tls_certificates\",\n \"transport_sockets.tls\",\n // Nginx\n \"ssl_certificate\",\n // HAProxy (`crt-store` and `bind … ssl … crt` both contain \"crt\")\n \"crt\",\n // Traefik\n \"certResolver\",\n \"certFile\",\n \"keyFile\",\n // gRPC\n \"ssl_channel_credentials\",\n \"createSsl\",\n \"TlsChannelCredentials\",\n \"credentials.NewTLS\",\n];\n\ninterface ProxyRule {\n meta: RuleMeta;\n re: RegExp;\n}\n\nconst PROXY_RULES: readonly ProxyRule[] = [\n { meta: RULE_ENVOY, re: RE_ENVOY_TLS },\n { meta: RULE_NGINX, re: RE_NGINX_TLS },\n { meta: RULE_HAPROXY, re: RE_HAPROXY_TLS },\n { meta: RULE_TRAEFIK, re: RE_TRAEFIK_TLS },\n { meta: RULE_GRPC, re: RE_GRPC_TLS },\n];\n\n/**\n * Detects classical-cert TLS termination in standalone reverse-proxy /\n * load-balancer config files and in-code gRPC channel credentials. See the\n * module docstring for the covered surfaces and the fast-reject rationale.\n */\nexport const proxyDetector: Detector = {\n id: \"proxy-tls-crypto\",\n description:\n \"Classical-cert TLS termination in reverse-proxy / load-balancer config (Envoy, Nginx, HAProxy, Traefik) and gRPC channel credentials\",\n scope: \"config\",\n language: \"any\",\n rules: PROXY_RULES.map((r) => r.meta),\n // Config + source files are all in scope (gRPC creds live in code); only prose\n // documentation is excluded, so a README mentioning `ssl_certificate` in a\n // sentence can't fire (the marker gate already handles most of this).\n appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n // Mask comments first so a directive that only appears inside a commented-out\n // block cannot fire.\n const scan = maskCommentLines(maskBlockComments(content), [\"#\", \"//\"]);\n // Cheap fast-reject: skip files with none of the distinctive tokens. This is a\n // perf gate only — each rule's regex is distinctive enough to SELF-GATE (a\n // separate too-narrow marker list previously dropped canonical HAProxy/Traefik\n // configs entirely — audit H1/H2), so correctness lives in the per-rule regexes.\n if (!PROXY_FAST_REJECT.some((mk) => scan.includes(mk))) return [];\n\n const findings: Finding[] = [];\n for (const { meta, re } of PROXY_RULES) {\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n }\n return findings;\n },\n};\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IaC detector: classical asymmetric keys provisioned by Pulumi's `tls` provider
|
|
3
|
+
* (`@pulumi/tls` / `pulumi_tls` / `pulumi-tls`), across the languages Pulumi programs
|
|
4
|
+
* are written in (TS/JS, Python, Go). A `tls.PrivateKey` resource mints real key
|
|
5
|
+
* material at deploy time — the IaC surface, like Terraform's `tls_private_key`, but
|
|
6
|
+
* expressed as SDK calls in a general-purpose source file rather than HCL.
|
|
7
|
+
*
|
|
8
|
+
* Detection is gated to files that actually use the pulumi-tls provider (a
|
|
9
|
+
* `@pulumi/tls` / `pulumi_tls` / `pulumi-tls` import or a `tls.PrivateKey` /
|
|
10
|
+
* `tls.NewPrivateKey` construction), then classifies each `algorithm` value:
|
|
11
|
+
* - `"RSA"` → RSA key (kem, HNDL)
|
|
12
|
+
* - `"ECDSA"` → EC signing key (signature)
|
|
13
|
+
* - `"ED25519"` → Ed25519 signing key (signature)
|
|
14
|
+
* Comment/string suppression is handled centrally for the host languages.
|
|
15
|
+
*/
|
|
16
|
+
import type { Detector } from "../types.js";
|
|
17
|
+
/** Detects classical asymmetric keys provisioned by Pulumi's tls provider. */
|
|
18
|
+
export declare const pulumiDetector: Detector;
|
|
19
|
+
//# sourceMappingURL=pulumi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulumi.d.ts","sourceRoot":"","sources":["../../src/detectors/pulumi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAsE/D,8EAA8E;AAC9E,eAAO,MAAM,cAAc,EAAE,QAmB5B,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
const PULUMI_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs", ".py", ".go"];
|
|
4
|
+
// The `algorithm` value of a pulumi-tls PrivateKey, in the TS/Python (`algorithm:
|
|
5
|
+
// "RSA"` / `algorithm="RSA"`) and Go (`Algorithm: pulumi.String("RSA")`) spellings.
|
|
6
|
+
const RE_PULUMI_TLS_ALG = /\b[Aa]lgorithm\s*[:=]\s*(?:pulumi\.String\(\s*)?["'](RSA|ECDSA|ED25519)["']/g;
|
|
7
|
+
/** True when the file actually uses the pulumi-tls provider (gate against FPs). */
|
|
8
|
+
function usesPulumiTls(content) {
|
|
9
|
+
return (content.includes("@pulumi/tls") ||
|
|
10
|
+
content.includes("pulumi_tls") ||
|
|
11
|
+
content.includes("pulumi-tls") ||
|
|
12
|
+
content.includes("tls.PrivateKey") ||
|
|
13
|
+
content.includes("tls.NewPrivateKey"));
|
|
14
|
+
}
|
|
15
|
+
const RULE_PULUMI_RSA = {
|
|
16
|
+
id: "pulumi-tls-rsa",
|
|
17
|
+
title: "Pulumi tls.PrivateKey RSA key",
|
|
18
|
+
description: 'Pulumi tls.PrivateKey with algorithm "RSA"',
|
|
19
|
+
category: "kem",
|
|
20
|
+
severity: "high",
|
|
21
|
+
confidence: "high",
|
|
22
|
+
algorithm: "RSA",
|
|
23
|
+
hndl: true,
|
|
24
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
25
|
+
message: "Pulumi provisions a classical RSA key (tls.PrivateKey), which is not quantum-safe.",
|
|
26
|
+
remediation: "Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).",
|
|
27
|
+
};
|
|
28
|
+
const RULE_PULUMI_ECDSA = {
|
|
29
|
+
id: "pulumi-tls-ecdsa",
|
|
30
|
+
title: "Pulumi tls.PrivateKey ECDSA key",
|
|
31
|
+
description: 'Pulumi tls.PrivateKey with algorithm "ECDSA"',
|
|
32
|
+
category: "signature",
|
|
33
|
+
severity: "high",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
algorithm: "ECDSA",
|
|
36
|
+
hndl: false,
|
|
37
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
38
|
+
message: "Pulumi provisions a classical ECDSA key (tls.PrivateKey), forgeable by a quantum attacker.",
|
|
39
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
40
|
+
};
|
|
41
|
+
const RULE_PULUMI_ED25519 = {
|
|
42
|
+
id: "pulumi-tls-ed25519",
|
|
43
|
+
title: "Pulumi tls.PrivateKey Ed25519 key",
|
|
44
|
+
description: 'Pulumi tls.PrivateKey with algorithm "ED25519"',
|
|
45
|
+
category: "signature",
|
|
46
|
+
severity: "medium",
|
|
47
|
+
confidence: "high",
|
|
48
|
+
algorithm: "EdDSA",
|
|
49
|
+
hndl: false,
|
|
50
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
51
|
+
message: "Pulumi provisions a classical Ed25519 key (tls.PrivateKey), forgeable by a quantum attacker.",
|
|
52
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
53
|
+
};
|
|
54
|
+
const RULE_BY_ALG = {
|
|
55
|
+
RSA: RULE_PULUMI_RSA,
|
|
56
|
+
ECDSA: RULE_PULUMI_ECDSA,
|
|
57
|
+
ED25519: RULE_PULUMI_ED25519,
|
|
58
|
+
};
|
|
59
|
+
/** Detects classical asymmetric keys provisioned by Pulumi's tls provider. */
|
|
60
|
+
export const pulumiDetector = {
|
|
61
|
+
id: "pulumi-crypto",
|
|
62
|
+
description: "Classical asymmetric keys provisioned by Pulumi's tls provider (IaC)",
|
|
63
|
+
scope: "config",
|
|
64
|
+
language: "any",
|
|
65
|
+
rules: [RULE_PULUMI_RSA, RULE_PULUMI_ECDSA, RULE_PULUMI_ED25519],
|
|
66
|
+
appliesTo: (f) => hasExtension(f, PULUMI_EXTENSIONS),
|
|
67
|
+
detect({ file, content }) {
|
|
68
|
+
if (!usesPulumiTls(content))
|
|
69
|
+
return [];
|
|
70
|
+
const findings = [];
|
|
71
|
+
eachMatch(RE_PULUMI_TLS_ALG, content, (m) => {
|
|
72
|
+
const rule = RULE_BY_ALG[m[1]];
|
|
73
|
+
if (!rule)
|
|
74
|
+
return;
|
|
75
|
+
findings.push(findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }));
|
|
76
|
+
});
|
|
77
|
+
return findings;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=pulumi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulumi.js","sourceRoot":"","sources":["../../src/detectors/pulumi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IaC detector: classical asymmetric keys provisioned by Pulumi's `tls` provider\n * (`@pulumi/tls` / `pulumi_tls` / `pulumi-tls`), across the languages Pulumi programs\n * are written in (TS/JS, Python, Go). A `tls.PrivateKey` resource mints real key\n * material at deploy time — the IaC surface, like Terraform's `tls_private_key`, but\n * expressed as SDK calls in a general-purpose source file rather than HCL.\n *\n * Detection is gated to files that actually use the pulumi-tls provider (a\n * `@pulumi/tls` / `pulumi_tls` / `pulumi-tls` import or a `tls.PrivateKey` /\n * `tls.NewPrivateKey` construction), then classifies each `algorithm` value:\n * - `\"RSA\"` → RSA key (kem, HNDL)\n * - `\"ECDSA\"` → EC signing key (signature)\n * - `\"ED25519\"` → Ed25519 signing key (signature)\n * Comment/string suppression is handled centrally for the host languages.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule, hasExtension } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\nconst PULUMI_EXTENSIONS: readonly string[] = [\".ts\", \".js\", \".mjs\", \".cjs\", \".py\", \".go\"];\n\n// The `algorithm` value of a pulumi-tls PrivateKey, in the TS/Python (`algorithm:\n// \"RSA\"` / `algorithm=\"RSA\"`) and Go (`Algorithm: pulumi.String(\"RSA\")`) spellings.\nconst RE_PULUMI_TLS_ALG =\n /\\b[Aa]lgorithm\\s*[:=]\\s*(?:pulumi\\.String\\(\\s*)?[\"'](RSA|ECDSA|ED25519)[\"']/g;\n\n/** True when the file actually uses the pulumi-tls provider (gate against FPs). */\nfunction usesPulumiTls(content: string): boolean {\n return (\n content.includes(\"@pulumi/tls\") ||\n content.includes(\"pulumi_tls\") ||\n content.includes(\"pulumi-tls\") ||\n content.includes(\"tls.PrivateKey\") ||\n content.includes(\"tls.NewPrivateKey\")\n );\n}\n\nconst RULE_PULUMI_RSA: RuleMeta = {\n id: \"pulumi-tls-rsa\",\n title: \"Pulumi tls.PrivateKey RSA key\",\n description: 'Pulumi tls.PrivateKey with algorithm \"RSA\"',\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Pulumi provisions a classical RSA key (tls.PrivateKey), which is not quantum-safe.\",\n remediation: \"Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).\",\n};\nconst RULE_PULUMI_ECDSA: RuleMeta = {\n id: \"pulumi-tls-ecdsa\",\n title: \"Pulumi tls.PrivateKey ECDSA key\",\n description: 'Pulumi tls.PrivateKey with algorithm \"ECDSA\"',\n category: \"signature\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Pulumi provisions a classical ECDSA key (tls.PrivateKey), forgeable by a quantum attacker.\",\n remediation: \"ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).\",\n};\nconst RULE_PULUMI_ED25519: RuleMeta = {\n id: \"pulumi-tls-ed25519\",\n title: \"Pulumi tls.PrivateKey Ed25519 key\",\n description: 'Pulumi tls.PrivateKey with algorithm \"ED25519\"',\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Pulumi provisions a classical Ed25519 key (tls.PrivateKey), forgeable by a quantum attacker.\",\n remediation: \"ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).\",\n};\n\nconst RULE_BY_ALG: Record<string, RuleMeta> = {\n RSA: RULE_PULUMI_RSA,\n ECDSA: RULE_PULUMI_ECDSA,\n ED25519: RULE_PULUMI_ED25519,\n};\n\n/** Detects classical asymmetric keys provisioned by Pulumi's tls provider. */\nexport const pulumiDetector: Detector = {\n id: \"pulumi-crypto\",\n description: \"Classical asymmetric keys provisioned by Pulumi's tls provider (IaC)\",\n scope: \"config\",\n language: \"any\",\n rules: [RULE_PULUMI_RSA, RULE_PULUMI_ECDSA, RULE_PULUMI_ED25519],\n appliesTo: (f) => hasExtension(f, PULUMI_EXTENSIONS),\n detect({ file, content }): Finding[] {\n if (!usesPulumiTls(content)) return [];\n const findings: Finding[] = [];\n eachMatch(RE_PULUMI_TLS_ALG, content, (m) => {\n const rule = RULE_BY_ALG[m[1]];\n if (!rule) return;\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n );\n });\n return findings;\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"python.d.ts","sourceRoot":"","sources":["../../src/detectors/python.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"python.d.ts","sourceRoot":"","sources":["../../src/detectors/python.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAsR/D,4DAA4D;AAC5D,eAAO,MAAM,cAAc,EAAE,QA4D5B,CAAC"}
|
package/dist/detectors/python.js
CHANGED
|
@@ -198,6 +198,63 @@ const RULE_PY_TLS_LEGACY = {
|
|
|
198
198
|
message: "TLS 1.0 (ssl.PROTOCOL_TLSv1) is deprecated and insecure; require TLS 1.3.",
|
|
199
199
|
remediation: "Use ssl.PROTOCOL_TLS_CLIENT with minimum_version = ssl.TLSVersion.TLSv1_3 and prefer PQC-hybrid key exchange.",
|
|
200
200
|
};
|
|
201
|
+
/**
|
|
202
|
+
* Aliasable `cryptography` / PyCryptodome modules → the (method, rule) pairs
|
|
203
|
+
* reachable through them. Lets an aliased module import
|
|
204
|
+
* (`from ... import rsa as _rsa` → `_rsa.generate_private_key(`) resolve back to
|
|
205
|
+
* the same rule the direct `rsa.generate_private_key(` would fire, since the
|
|
206
|
+
* detector's regexes are module-qualified and miss a renamed prefix.
|
|
207
|
+
*/
|
|
208
|
+
const PY_MODULE_RULES = {
|
|
209
|
+
rsa: [{ method: "generate_private_key", rule: RULE_PY_RSA_KEYGEN }],
|
|
210
|
+
ec: [
|
|
211
|
+
{ method: "generate_private_key", rule: RULE_PY_EC_KEYGEN },
|
|
212
|
+
{ method: "ECDSA", rule: RULE_PY_ECDSA },
|
|
213
|
+
{ method: "ECDH", rule: RULE_PY_ECDH },
|
|
214
|
+
],
|
|
215
|
+
dsa: [{ method: "generate_private_key", rule: RULE_PY_HAZMAT_DSA }],
|
|
216
|
+
dh: [{ method: "generate_parameters", rule: RULE_PY_DH }],
|
|
217
|
+
padding: [{ method: "OAEP", rule: RULE_PY_RSA_ENCRYPT }],
|
|
218
|
+
// PyCryptodome factory modules (`.generate(`).
|
|
219
|
+
RSA: [{ method: "generate", rule: RULE_PY_RSA_KEYGEN }],
|
|
220
|
+
ECC: [{ method: "generate", rule: RULE_PY_EC_KEYGEN }],
|
|
221
|
+
DSA: [{ method: "generate", rule: RULE_PY_DSA }],
|
|
222
|
+
};
|
|
223
|
+
/** Escape a string for interpolation into a dynamically-built RegExp. */
|
|
224
|
+
function escapePyRe(s) {
|
|
225
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Collect module aliases for the aliasable crypto modules, so a later
|
|
229
|
+
* `<alias>.<method>(` call still resolves. Handles both `from <path> import
|
|
230
|
+
* <mod> as <alias>` (including comma-separated specifiers) and the bare
|
|
231
|
+
* `import <path.mod> as <alias>`. Returns Map<module, alias[]>; an alias equal to
|
|
232
|
+
* its own module name is skipped (matched directly already).
|
|
233
|
+
*/
|
|
234
|
+
function collectPyModuleAliases(content) {
|
|
235
|
+
const out = new Map();
|
|
236
|
+
const add = (mod, alias) => {
|
|
237
|
+
if (!alias || alias === mod || !(mod in PY_MODULE_RULES))
|
|
238
|
+
return;
|
|
239
|
+
const list = out.get(mod) ?? [];
|
|
240
|
+
if (!list.includes(alias))
|
|
241
|
+
list.push(alias);
|
|
242
|
+
out.set(mod, list);
|
|
243
|
+
};
|
|
244
|
+
// `from <path> import a as b, c as d` — scan the import list for `X as Y`.
|
|
245
|
+
const fromRe = /(?:^|\n)[ \t]*from\s+[\w.]+\s+import\s+([^\n#]+)/g;
|
|
246
|
+
for (let m = fromRe.exec(content); m; m = fromRe.exec(content)) {
|
|
247
|
+
const specRe = /([A-Za-z_]\w*)\s+as\s+([A-Za-z_]\w*)/g;
|
|
248
|
+
for (let s = specRe.exec(m[1]); s; s = specRe.exec(m[1]))
|
|
249
|
+
add(s[1], s[2]);
|
|
250
|
+
}
|
|
251
|
+
// `import <path.mod> as <alias>` — the aliased module is the last dotted segment.
|
|
252
|
+
const impRe = /(?:^|\n)[ \t]*import\s+([\w.]+)\s+as\s+([A-Za-z_]\w*)/g;
|
|
253
|
+
for (let m = impRe.exec(content); m; m = impRe.exec(content)) {
|
|
254
|
+
add(m[1].split(".").pop() ?? m[1], m[2]);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
201
258
|
/** Detects classical asymmetric crypto in Python source. */
|
|
202
259
|
export const pythonDetector = {
|
|
203
260
|
id: "python-crypto",
|
|
@@ -236,6 +293,19 @@ export const pythonDetector = {
|
|
|
236
293
|
add(RE_PY_EDDSA, RULE_PY_EDDSA);
|
|
237
294
|
add(RE_PY_TLS_REJECT, RULE_PY_TLS_REJECT);
|
|
238
295
|
add(RE_PY_TLS_LEGACY, RULE_PY_TLS_LEGACY);
|
|
296
|
+
// Module-alias resolution: `from ... import rsa as _rsa` then
|
|
297
|
+
// `_rsa.generate_private_key(` — the direct regexes are module-qualified
|
|
298
|
+
// (`\brsa\.`) and miss a renamed prefix. Runs on the ORIGINAL content so
|
|
299
|
+
// locations stay exact; fires only for an alias explicitly bound to a known
|
|
300
|
+
// crypto module, so precision is unaffected.
|
|
301
|
+
for (const [mod, aliasList] of collectPyModuleAliases(content)) {
|
|
302
|
+
for (const alias of aliasList) {
|
|
303
|
+
const a = escapePyRe(alias);
|
|
304
|
+
for (const { method, rule } of PY_MODULE_RULES[mod]) {
|
|
305
|
+
add(new RegExp(`\\b${a}\\.${method}\\s*\\(`, "g"), rule);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
239
309
|
return findings;
|
|
240
310
|
},
|
|
241
311
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"python.js","sourceRoot":"","sources":["../../src/detectors/python.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Source-code detector for classical, non-quantum-safe asymmetric cryptography\n * in Python. Same lexical (regex-over-source) strategy as the JS/TS detectors:\n * robust to formatting, dependency-free, and confidence-tagged per pattern.\n *\n * Covers the three libraries that carry ~all real-world Python asymmetric\n * crypto:\n * - `cryptography` (hazmat): rsa/ec/dh/x25519/x448/ed25519/ed448 + OAEP padding\n * - PyCryptodome (`Crypto.PublicKey`): RSA/ECC/DSA generate + PKCS1_OAEP\n * - paramiko (SSH): RSAKey / ECDSAKey / Ed25519Key / DSSKey\n *\n * JWT/JOSE algorithm strings (`algorithm=\"RS256\"`) are NOT handled here — the\n * language-agnostic `jwt-jose` detector now applies to Python too (a quoted\n * `\"RS256\"` is the same evidence in any language).\n *\n * HNDL policy mirrors the JS detectors: key agreement / KEM (RSA-OAEP, ECDH, DH,\n * X25519/X448) is harvest-now-decrypt-later exposed (hndl:true); signatures\n * (ECDSA, DSA, Ed25519/Ed448) are hndl:false but still high/low severity because\n * a quantum attacker can forge them. EC key generation is ambiguous (an EC key\n * feeds both ECDSA and ECDH), so it is classified conservatively as\n * key-exchange-capable (hndl:true), exactly as the Node `ec` keygen rule is.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { PYTHON_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from \"../cwe.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Precompiled regexes (module scope) */\n/* -------------------------------------------------------------------------- */\n\n// RSA key generation: cryptography `rsa.generate_private_key(`, PyCryptodome\n// `RSA.generate(`, paramiko `paramiko.RSAKey` / `RSAKey.generate(`.\nconst RE_PY_RSA_KEYGEN =\n /\\brsa\\.generate_private_key\\s*\\(|\\bRSA\\.generate\\s*\\(|\\bparamiko\\.RSAKey\\b|\\bRSAKey\\.generate\\s*\\(/g;\n// RSA public-key encryption padding (RSA-OAEP): confidentiality → HNDL.\nconst RE_PY_RSA_ENCRYPT = /\\bpadding\\.OAEP\\s*\\(|\\bPKCS1_OAEP\\.new\\s*\\(/g;\n// EC key generation (ambiguous ECDSA/ECDH): cryptography + PyCryptodome.\nconst RE_PY_EC_KEYGEN = /\\bec\\.generate_private_key\\s*\\(|\\bECC\\.generate\\s*\\(/g;\n// ECDSA signatures: cryptography `ec.ECDSA(`, paramiko ECDSAKey.\nconst RE_PY_ECDSA = /\\bec\\.ECDSA\\s*\\(|\\bparamiko\\.ECDSAKey\\b|\\bECDSAKey\\.generate\\s*\\(/g;\n// ECDH key agreement: cryptography `ec.ECDH(` — the actual harvest-now event in\n// `private_key.exchange(ec.ECDH(), peer)`; previously missed entirely (audit F3).\nconst RE_PY_ECDH = /\\bec\\.ECDH\\s*\\(/g;\n// DSA: PyCryptodome `DSA.generate(`, paramiko DSSKey.\nconst RE_PY_DSA = /\\bDSA\\.generate\\s*\\(|\\bparamiko\\.DSSKey\\b|\\bDSSKey\\.generate\\s*\\(/g;\n// hazmat DSA: cryptography `dsa.generate_private_key(` — the `cryptography` DSA\n// keygen path (lowercase module) that RE_PY_DSA's uppercase `DSA.generate` misses\n// (audit F9-python).\nconst RE_PY_HAZMAT_DSA = /\\bdsa\\.generate_private_key\\s*\\(/g;\n// Finite-field Diffie-Hellman (cryptography `dh`).\nconst RE_PY_DH = /\\bdh\\.generate_parameters\\s*\\(|\\bdh\\.DHParameterNumbers\\s*\\(/g;\n// Modern-but-classical curve primitives (cryptography). `.generate(` anchors so\n// a bare import of the class name does not fire.\nconst RE_PY_X25519 = /\\bX25519PrivateKey\\.generate\\s*\\(/g;\nconst RE_PY_X448 = /\\bX448PrivateKey\\.generate\\s*\\(/g;\nconst RE_PY_EDDSA = /\\b(?:Ed25519|Ed448)PrivateKey\\.generate\\s*\\(|\\bparamiko\\.Ed25519Key\\b/g;\n\n// Python TLS misconfiguration. Mirrors the JS `tlsDetector` split of source.ts:\n// certificate-verification bypass (requests `verify=False`, `ssl.CERT_NONE`,\n// `check_hostname=False`, `ssl._create_unverified_context(`) is the high-severity\n// MITM surface (CWE-295); a pinned legacy protocol (`ssl.PROTOCOL_TLSv1`) is the\n// medium-severity weak-strength surface (CWE-326).\nconst RE_PY_TLS_REJECT =\n /\\bverify\\s*=\\s*False\\b|\\bssl\\.CERT_NONE\\b|\\bcheck_hostname\\s*=\\s*False\\b|\\bssl\\._create_unverified_context\\s*\\(/g;\nconst RE_PY_TLS_LEGACY = /\\bPROTOCOL_TLSv1\\b/g;\n\n/* -------------------------------------------------------------------------- */\n/* Rule catalog */\n/* -------------------------------------------------------------------------- */\n\nconst RULE_PY_RSA_KEYGEN: RuleMeta = {\n id: \"python-rsa-keygen\",\n title: \"Python RSA key generation\",\n description:\n \"cryptography rsa.generate_private_key / PyCryptodome RSA.generate / paramiko RSAKey\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Generates a classical RSA key pair (Python), which is not quantum-safe.\",\n};\nconst RULE_PY_RSA_ENCRYPT: RuleMeta = {\n id: \"python-rsa-encrypt\",\n title: \"Python RSA public-key encryption\",\n description: \"RSA-OAEP / PKCS1_OAEP encryption padding\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"RSA public-key encryption (OAEP) is broken by Shor's algorithm and exposed to harvest-now-decrypt-later.\",\n};\nconst RULE_PY_EC_KEYGEN: RuleMeta = {\n id: \"python-ec-keygen\",\n title: \"Python EC key generation\",\n description: \"cryptography ec.generate_private_key / PyCryptodome ECC.generate\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Generates a classical EC key pair (Python). EC keys feed BOTH 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_PY_ECDSA: RuleMeta = {\n id: \"python-ecdsa\",\n title: \"Python ECDSA signature\",\n description: \"cryptography ec.ECDSA / paramiko ECDSAKey\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"medium\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Classical ECDSA signing (Python) is forgeable by a quantum attacker.\",\n remediation: \"ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)\",\n};\nconst RULE_PY_ECDH: RuleMeta = {\n id: \"python-ecdh\",\n title: \"Python ECDH key agreement\",\n description: \"cryptography ec.ECDH() exchange\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Elliptic-curve Diffie-Hellman key agreement (Python) is broken by Shor's algorithm (harvest-now-decrypt-later).\",\n};\nconst RULE_PY_DSA: RuleMeta = {\n id: \"python-dsa\",\n title: \"Python DSA key/usage\",\n description: \"PyCryptodome DSA.generate / paramiko DSSKey\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"DSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Classical DSA (Python) is deprecated and forgeable by a quantum attacker.\",\n remediation: \"Rotate off DSA and migrate to ML-DSA-65 (FIPS 204).\",\n};\nconst RULE_PY_HAZMAT_DSA: RuleMeta = {\n id: \"python-hazmat-dsa\",\n title: \"Python DSA key generation (cryptography)\",\n description: \"cryptography dsa.generate_private_key\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"DSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"cryptography dsa.generate_private_key (Python) creates a classical DSA key; DSA is deprecated and forgeable by a quantum attacker.\",\n remediation: \"Rotate off DSA and migrate to ML-DSA-65 (FIPS 204).\",\n};\nconst RULE_PY_DH: RuleMeta = {\n id: \"python-dh\",\n title: \"Python Diffie-Hellman key exchange\",\n description: \"cryptography dh.generate_parameters / DHParameterNumbers\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"DH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Finite-field Diffie-Hellman (Python) is broken by Shor's algorithm (harvest-now-decrypt-later).\",\n};\nconst RULE_PY_X25519: RuleMeta = {\n id: \"python-x25519\",\n title: \"Python X25519 key exchange\",\n description: \"cryptography X25519PrivateKey.generate\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"X25519\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"X25519 (Python) is modern but still classical key agreement — harvest-now-decrypt-later.\",\n};\nconst RULE_PY_X448: RuleMeta = {\n id: \"python-x448\",\n title: \"Python X448 key exchange\",\n description: \"cryptography X448PrivateKey.generate\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"X448\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"X448 (Python) is modern but still classical key agreement — harvest-now-decrypt-later.\",\n};\nconst RULE_PY_EDDSA: RuleMeta = {\n id: \"python-eddsa\",\n title: \"Python Ed25519/Ed448 signature\",\n description: \"cryptography Ed25519/Ed448 PrivateKey.generate / paramiko Ed25519Key\",\n category: \"signature\",\n severity: \"low\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Ed25519/Ed448 (Python) is a modern but still classical signature scheme.\",\n};\nconst RULE_PY_TLS_REJECT: RuleMeta = {\n id: \"python-tls-reject\",\n title: \"Python TLS certificate verification disabled\",\n description:\n \"requests verify=False / ssl.CERT_NONE / check_hostname=False / _create_unverified_context\",\n category: \"tls\",\n severity: \"high\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_CERT_VALIDATION,\n message:\n \"TLS certificate verification is disabled (verify=False / CERT_NONE / check_hostname=False / _create_unverified_context), which allows man-in-the-middle attacks.\",\n remediation:\n \"Enable certificate verification (verify=True, ssl.CERT_REQUIRED, check_hostname=True) and verify certificates properly.\",\n};\nconst RULE_PY_TLS_LEGACY: RuleMeta = {\n id: \"python-tls-legacy-version\",\n title: \"Python legacy TLS version pinned\",\n description: \"ssl.PROTOCOL_TLSv1 (TLS 1.0)\",\n category: \"tls\",\n severity: \"medium\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_WEAK_STRENGTH,\n message: \"TLS 1.0 (ssl.PROTOCOL_TLSv1) is deprecated and insecure; require TLS 1.3.\",\n remediation:\n \"Use ssl.PROTOCOL_TLS_CLIENT with minimum_version = ssl.TLSVersion.TLSv1_3 and prefer PQC-hybrid key exchange.\",\n};\n\n/** Detects classical asymmetric crypto in Python source. */\nexport const pythonDetector: Detector = {\n id: \"python-crypto\",\n description: \"Classical asymmetric crypto in Python (cryptography, PyCryptodome, paramiko)\",\n scope: \"source\",\n language: \"python\",\n rules: [\n RULE_PY_RSA_KEYGEN,\n RULE_PY_RSA_ENCRYPT,\n RULE_PY_EC_KEYGEN,\n RULE_PY_ECDSA,\n RULE_PY_ECDH,\n RULE_PY_DSA,\n RULE_PY_HAZMAT_DSA,\n RULE_PY_DH,\n RULE_PY_X25519,\n RULE_PY_X448,\n RULE_PY_EDDSA,\n RULE_PY_TLS_REJECT,\n RULE_PY_TLS_LEGACY,\n ],\n appliesTo: (f) => hasExtension(f, PYTHON_EXTENSIONS),\n detect({ file, content }): Finding[] {\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta) =>\n eachMatch(re, content, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n\n add(RE_PY_RSA_KEYGEN, RULE_PY_RSA_KEYGEN);\n add(RE_PY_RSA_ENCRYPT, RULE_PY_RSA_ENCRYPT);\n add(RE_PY_EC_KEYGEN, RULE_PY_EC_KEYGEN);\n add(RE_PY_ECDSA, RULE_PY_ECDSA);\n add(RE_PY_ECDH, RULE_PY_ECDH);\n add(RE_PY_DSA, RULE_PY_DSA);\n add(RE_PY_HAZMAT_DSA, RULE_PY_HAZMAT_DSA);\n add(RE_PY_DH, RULE_PY_DH);\n add(RE_PY_X25519, RULE_PY_X25519);\n add(RE_PY_X448, RULE_PY_X448);\n add(RE_PY_EDDSA, RULE_PY_EDDSA);\n add(RE_PY_TLS_REJECT, RULE_PY_TLS_REJECT);\n add(RE_PY_TLS_LEGACY, RULE_PY_TLS_LEGACY);\n\n return findings;\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"python.js","sourceRoot":"","sources":["../../src/detectors/python.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Source-code detector for classical, non-quantum-safe asymmetric cryptography\n * in Python. Same lexical (regex-over-source) strategy as the JS/TS detectors:\n * robust to formatting, dependency-free, and confidence-tagged per pattern.\n *\n * Covers the three libraries that carry ~all real-world Python asymmetric\n * crypto:\n * - `cryptography` (hazmat): rsa/ec/dh/x25519/x448/ed25519/ed448 + OAEP padding\n * - PyCryptodome (`Crypto.PublicKey`): RSA/ECC/DSA generate + PKCS1_OAEP\n * - paramiko (SSH): RSAKey / ECDSAKey / Ed25519Key / DSSKey\n *\n * JWT/JOSE algorithm strings (`algorithm=\"RS256\"`) are NOT handled here — the\n * language-agnostic `jwt-jose` detector now applies to Python too (a quoted\n * `\"RS256\"` is the same evidence in any language).\n *\n * HNDL policy mirrors the JS detectors: key agreement / KEM (RSA-OAEP, ECDH, DH,\n * X25519/X448) is harvest-now-decrypt-later exposed (hndl:true); signatures\n * (ECDSA, DSA, Ed25519/Ed448) are hndl:false but still high/low severity because\n * a quantum attacker can forge them. EC key generation is ambiguous (an EC key\n * feeds both ECDSA and ECDH), so it is classified conservatively as\n * key-exchange-capable (hndl:true), exactly as the Node `ec` keygen rule is.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { PYTHON_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from \"../cwe.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Precompiled regexes (module scope) */\n/* -------------------------------------------------------------------------- */\n\n// RSA key generation: cryptography `rsa.generate_private_key(`, PyCryptodome\n// `RSA.generate(`, paramiko `paramiko.RSAKey` / `RSAKey.generate(`.\nconst RE_PY_RSA_KEYGEN =\n /\\brsa\\.generate_private_key\\s*\\(|\\bRSA\\.generate\\s*\\(|\\bparamiko\\.RSAKey\\b|\\bRSAKey\\.generate\\s*\\(/g;\n// RSA public-key encryption padding (RSA-OAEP): confidentiality → HNDL.\nconst RE_PY_RSA_ENCRYPT = /\\bpadding\\.OAEP\\s*\\(|\\bPKCS1_OAEP\\.new\\s*\\(/g;\n// EC key generation (ambiguous ECDSA/ECDH): cryptography + PyCryptodome.\nconst RE_PY_EC_KEYGEN = /\\bec\\.generate_private_key\\s*\\(|\\bECC\\.generate\\s*\\(/g;\n// ECDSA signatures: cryptography `ec.ECDSA(`, paramiko ECDSAKey.\nconst RE_PY_ECDSA = /\\bec\\.ECDSA\\s*\\(|\\bparamiko\\.ECDSAKey\\b|\\bECDSAKey\\.generate\\s*\\(/g;\n// ECDH key agreement: cryptography `ec.ECDH(` — the actual harvest-now event in\n// `private_key.exchange(ec.ECDH(), peer)`; previously missed entirely (audit F3).\nconst RE_PY_ECDH = /\\bec\\.ECDH\\s*\\(/g;\n// DSA: PyCryptodome `DSA.generate(`, paramiko DSSKey.\nconst RE_PY_DSA = /\\bDSA\\.generate\\s*\\(|\\bparamiko\\.DSSKey\\b|\\bDSSKey\\.generate\\s*\\(/g;\n// hazmat DSA: cryptography `dsa.generate_private_key(` — the `cryptography` DSA\n// keygen path (lowercase module) that RE_PY_DSA's uppercase `DSA.generate` misses\n// (audit F9-python).\nconst RE_PY_HAZMAT_DSA = /\\bdsa\\.generate_private_key\\s*\\(/g;\n// Finite-field Diffie-Hellman (cryptography `dh`).\nconst RE_PY_DH = /\\bdh\\.generate_parameters\\s*\\(|\\bdh\\.DHParameterNumbers\\s*\\(/g;\n// Modern-but-classical curve primitives (cryptography). `.generate(` anchors so\n// a bare import of the class name does not fire.\nconst RE_PY_X25519 = /\\bX25519PrivateKey\\.generate\\s*\\(/g;\nconst RE_PY_X448 = /\\bX448PrivateKey\\.generate\\s*\\(/g;\nconst RE_PY_EDDSA = /\\b(?:Ed25519|Ed448)PrivateKey\\.generate\\s*\\(|\\bparamiko\\.Ed25519Key\\b/g;\n\n// Python TLS misconfiguration. Mirrors the JS `tlsDetector` split of source.ts:\n// certificate-verification bypass (requests `verify=False`, `ssl.CERT_NONE`,\n// `check_hostname=False`, `ssl._create_unverified_context(`) is the high-severity\n// MITM surface (CWE-295); a pinned legacy protocol (`ssl.PROTOCOL_TLSv1`) is the\n// medium-severity weak-strength surface (CWE-326).\nconst RE_PY_TLS_REJECT =\n /\\bverify\\s*=\\s*False\\b|\\bssl\\.CERT_NONE\\b|\\bcheck_hostname\\s*=\\s*False\\b|\\bssl\\._create_unverified_context\\s*\\(/g;\nconst RE_PY_TLS_LEGACY = /\\bPROTOCOL_TLSv1\\b/g;\n\n/* -------------------------------------------------------------------------- */\n/* Rule catalog */\n/* -------------------------------------------------------------------------- */\n\nconst RULE_PY_RSA_KEYGEN: RuleMeta = {\n id: \"python-rsa-keygen\",\n title: \"Python RSA key generation\",\n description:\n \"cryptography rsa.generate_private_key / PyCryptodome RSA.generate / paramiko RSAKey\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Generates a classical RSA key pair (Python), which is not quantum-safe.\",\n};\nconst RULE_PY_RSA_ENCRYPT: RuleMeta = {\n id: \"python-rsa-encrypt\",\n title: \"Python RSA public-key encryption\",\n description: \"RSA-OAEP / PKCS1_OAEP encryption padding\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"RSA public-key encryption (OAEP) is broken by Shor's algorithm and exposed to harvest-now-decrypt-later.\",\n};\nconst RULE_PY_EC_KEYGEN: RuleMeta = {\n id: \"python-ec-keygen\",\n title: \"Python EC key generation\",\n description: \"cryptography ec.generate_private_key / PyCryptodome ECC.generate\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Generates a classical EC key pair (Python). EC keys feed BOTH 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_PY_ECDSA: RuleMeta = {\n id: \"python-ecdsa\",\n title: \"Python ECDSA signature\",\n description: \"cryptography ec.ECDSA / paramiko ECDSAKey\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"medium\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Classical ECDSA signing (Python) is forgeable by a quantum attacker.\",\n remediation: \"ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)\",\n};\nconst RULE_PY_ECDH: RuleMeta = {\n id: \"python-ecdh\",\n title: \"Python ECDH key agreement\",\n description: \"cryptography ec.ECDH() exchange\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Elliptic-curve Diffie-Hellman key agreement (Python) is broken by Shor's algorithm (harvest-now-decrypt-later).\",\n};\nconst RULE_PY_DSA: RuleMeta = {\n id: \"python-dsa\",\n title: \"Python DSA key/usage\",\n description: \"PyCryptodome DSA.generate / paramiko DSSKey\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"DSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Classical DSA (Python) is deprecated and forgeable by a quantum attacker.\",\n remediation: \"Rotate off DSA and migrate to ML-DSA-65 (FIPS 204).\",\n};\nconst RULE_PY_HAZMAT_DSA: RuleMeta = {\n id: \"python-hazmat-dsa\",\n title: \"Python DSA key generation (cryptography)\",\n description: \"cryptography dsa.generate_private_key\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"DSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"cryptography dsa.generate_private_key (Python) creates a classical DSA key; DSA is deprecated and forgeable by a quantum attacker.\",\n remediation: \"Rotate off DSA and migrate to ML-DSA-65 (FIPS 204).\",\n};\nconst RULE_PY_DH: RuleMeta = {\n id: \"python-dh\",\n title: \"Python Diffie-Hellman key exchange\",\n description: \"cryptography dh.generate_parameters / DHParameterNumbers\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"DH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Finite-field Diffie-Hellman (Python) is broken by Shor's algorithm (harvest-now-decrypt-later).\",\n};\nconst RULE_PY_X25519: RuleMeta = {\n id: \"python-x25519\",\n title: \"Python X25519 key exchange\",\n description: \"cryptography X25519PrivateKey.generate\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"X25519\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"X25519 (Python) is modern but still classical key agreement — harvest-now-decrypt-later.\",\n};\nconst RULE_PY_X448: RuleMeta = {\n id: \"python-x448\",\n title: \"Python X448 key exchange\",\n description: \"cryptography X448PrivateKey.generate\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"X448\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"X448 (Python) is modern but still classical key agreement — harvest-now-decrypt-later.\",\n};\nconst RULE_PY_EDDSA: RuleMeta = {\n id: \"python-eddsa\",\n title: \"Python Ed25519/Ed448 signature\",\n description: \"cryptography Ed25519/Ed448 PrivateKey.generate / paramiko Ed25519Key\",\n category: \"signature\",\n severity: \"low\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Ed25519/Ed448 (Python) is a modern but still classical signature scheme.\",\n};\nconst RULE_PY_TLS_REJECT: RuleMeta = {\n id: \"python-tls-reject\",\n title: \"Python TLS certificate verification disabled\",\n description:\n \"requests verify=False / ssl.CERT_NONE / check_hostname=False / _create_unverified_context\",\n category: \"tls\",\n severity: \"high\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_CERT_VALIDATION,\n message:\n \"TLS certificate verification is disabled (verify=False / CERT_NONE / check_hostname=False / _create_unverified_context), which allows man-in-the-middle attacks.\",\n remediation:\n \"Enable certificate verification (verify=True, ssl.CERT_REQUIRED, check_hostname=True) and verify certificates properly.\",\n};\nconst RULE_PY_TLS_LEGACY: RuleMeta = {\n id: \"python-tls-legacy-version\",\n title: \"Python legacy TLS version pinned\",\n description: \"ssl.PROTOCOL_TLSv1 (TLS 1.0)\",\n category: \"tls\",\n severity: \"medium\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_WEAK_STRENGTH,\n message: \"TLS 1.0 (ssl.PROTOCOL_TLSv1) is deprecated and insecure; require TLS 1.3.\",\n remediation:\n \"Use ssl.PROTOCOL_TLS_CLIENT with minimum_version = ssl.TLSVersion.TLSv1_3 and prefer PQC-hybrid key exchange.\",\n};\n\n/**\n * Aliasable `cryptography` / PyCryptodome modules → the (method, rule) pairs\n * reachable through them. Lets an aliased module import\n * (`from ... import rsa as _rsa` → `_rsa.generate_private_key(`) resolve back to\n * the same rule the direct `rsa.generate_private_key(` would fire, since the\n * detector's regexes are module-qualified and miss a renamed prefix.\n */\nconst PY_MODULE_RULES: Record<string, ReadonlyArray<{ method: string; rule: RuleMeta }>> = {\n rsa: [{ method: \"generate_private_key\", rule: RULE_PY_RSA_KEYGEN }],\n ec: [\n { method: \"generate_private_key\", rule: RULE_PY_EC_KEYGEN },\n { method: \"ECDSA\", rule: RULE_PY_ECDSA },\n { method: \"ECDH\", rule: RULE_PY_ECDH },\n ],\n dsa: [{ method: \"generate_private_key\", rule: RULE_PY_HAZMAT_DSA }],\n dh: [{ method: \"generate_parameters\", rule: RULE_PY_DH }],\n padding: [{ method: \"OAEP\", rule: RULE_PY_RSA_ENCRYPT }],\n // PyCryptodome factory modules (`.generate(`).\n RSA: [{ method: \"generate\", rule: RULE_PY_RSA_KEYGEN }],\n ECC: [{ method: \"generate\", rule: RULE_PY_EC_KEYGEN }],\n DSA: [{ method: \"generate\", rule: RULE_PY_DSA }],\n};\n\n/** Escape a string for interpolation into a dynamically-built RegExp. */\nfunction escapePyRe(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Collect module aliases for the aliasable crypto modules, so a later\n * `<alias>.<method>(` call still resolves. Handles both `from <path> import\n * <mod> as <alias>` (including comma-separated specifiers) and the bare\n * `import <path.mod> as <alias>`. Returns Map<module, alias[]>; an alias equal to\n * its own module name is skipped (matched directly already).\n */\nfunction collectPyModuleAliases(content: string): Map<string, string[]> {\n const out = new Map<string, string[]>();\n const add = (mod: string, alias: string): void => {\n if (!alias || alias === mod || !(mod in PY_MODULE_RULES)) return;\n const list = out.get(mod) ?? [];\n if (!list.includes(alias)) list.push(alias);\n out.set(mod, list);\n };\n // `from <path> import a as b, c as d` — scan the import list for `X as Y`.\n const fromRe = /(?:^|\\n)[ \\t]*from\\s+[\\w.]+\\s+import\\s+([^\\n#]+)/g;\n for (let m = fromRe.exec(content); m; m = fromRe.exec(content)) {\n const specRe = /([A-Za-z_]\\w*)\\s+as\\s+([A-Za-z_]\\w*)/g;\n for (let s = specRe.exec(m[1]); s; s = specRe.exec(m[1])) add(s[1], s[2]);\n }\n // `import <path.mod> as <alias>` — the aliased module is the last dotted segment.\n const impRe = /(?:^|\\n)[ \\t]*import\\s+([\\w.]+)\\s+as\\s+([A-Za-z_]\\w*)/g;\n for (let m = impRe.exec(content); m; m = impRe.exec(content)) {\n add(m[1].split(\".\").pop() ?? m[1], m[2]);\n }\n return out;\n}\n\n/** Detects classical asymmetric crypto in Python source. */\nexport const pythonDetector: Detector = {\n id: \"python-crypto\",\n description: \"Classical asymmetric crypto in Python (cryptography, PyCryptodome, paramiko)\",\n scope: \"source\",\n language: \"python\",\n rules: [\n RULE_PY_RSA_KEYGEN,\n RULE_PY_RSA_ENCRYPT,\n RULE_PY_EC_KEYGEN,\n RULE_PY_ECDSA,\n RULE_PY_ECDH,\n RULE_PY_DSA,\n RULE_PY_HAZMAT_DSA,\n RULE_PY_DH,\n RULE_PY_X25519,\n RULE_PY_X448,\n RULE_PY_EDDSA,\n RULE_PY_TLS_REJECT,\n RULE_PY_TLS_LEGACY,\n ],\n appliesTo: (f) => hasExtension(f, PYTHON_EXTENSIONS),\n detect({ file, content }): Finding[] {\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta) =>\n eachMatch(re, content, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n\n add(RE_PY_RSA_KEYGEN, RULE_PY_RSA_KEYGEN);\n add(RE_PY_RSA_ENCRYPT, RULE_PY_RSA_ENCRYPT);\n add(RE_PY_EC_KEYGEN, RULE_PY_EC_KEYGEN);\n add(RE_PY_ECDSA, RULE_PY_ECDSA);\n add(RE_PY_ECDH, RULE_PY_ECDH);\n add(RE_PY_DSA, RULE_PY_DSA);\n add(RE_PY_HAZMAT_DSA, RULE_PY_HAZMAT_DSA);\n add(RE_PY_DH, RULE_PY_DH);\n add(RE_PY_X25519, RULE_PY_X25519);\n add(RE_PY_X448, RULE_PY_X448);\n add(RE_PY_EDDSA, RULE_PY_EDDSA);\n add(RE_PY_TLS_REJECT, RULE_PY_TLS_REJECT);\n add(RE_PY_TLS_LEGACY, RULE_PY_TLS_LEGACY);\n\n // Module-alias resolution: `from ... import rsa as _rsa` then\n // `_rsa.generate_private_key(` — the direct regexes are module-qualified\n // (`\\brsa\\.`) and miss a renamed prefix. Runs on the ORIGINAL content so\n // locations stay exact; fires only for an alias explicitly bound to a known\n // crypto module, so precision is unaffected.\n for (const [mod, aliasList] of collectPyModuleAliases(content)) {\n for (const alias of aliasList) {\n const a = escapePyRe(alias);\n for (const { method, rule } of PY_MODULE_RULES[mod]) {\n add(new RegExp(`\\\\b${a}\\\\.${method}\\\\s*\\\\(`, \"g\"), rule);\n }\n }\n }\n\n return findings;\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rust.d.ts","sourceRoot":"","sources":["../../src/detectors/rust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAmB,QAAQ,EAAqB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"rust.d.ts","sourceRoot":"","sources":["../../src/detectors/rust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAmB,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA4ShF,iFAAiF;AACjF,eAAO,MAAM,YAAY,EAAE,QA4E1B,CAAC"}
|
package/dist/detectors/rust.js
CHANGED
|
@@ -11,6 +11,10 @@ const RE_RUST_ECDH = /\becdh::EphemeralSecret\b|\bagreement::ECDH_P(?:256|384)\b
|
|
|
11
11
|
const RE_RUST_ED25519 = /\bed25519_dalek::(?:SigningKey|Keypair|SecretKey)\b|\bEd25519KeyPair::/g;
|
|
12
12
|
// X25519 key agreement (dalek).
|
|
13
13
|
const RE_RUST_X25519 = /\bx25519_dalek::(?:EphemeralSecret|StaticSecret)\b/g;
|
|
14
|
+
// X448 key agreement (`x448` crate). The qualified `x448::Secret` path — covers the
|
|
15
|
+
// plain `use x448::Secret;` / fully-qualified form the alias mechanism (which only
|
|
16
|
+
// fires on a RENAMED `use … as`) misses.
|
|
17
|
+
const RE_RUST_X448 = /\bx448::Secret\b/g;
|
|
14
18
|
// openssl crate bindings — the `Type::generate(` / `Dh::` call forms, distinct
|
|
15
19
|
// from the ring / RustCrypto path syntax above (audit F5/F9 false-negatives).
|
|
16
20
|
const RE_RUST_OPENSSL_RSA = /\bRsa::generate\s*\(/g;
|
|
@@ -97,6 +101,18 @@ const RULE_RUST_X25519 = {
|
|
|
97
101
|
cwe: CWE_BROKEN_CRYPTO,
|
|
98
102
|
message: "X25519 (Rust) is modern but still classical key agreement — harvest-now-decrypt-later.",
|
|
99
103
|
};
|
|
104
|
+
const RULE_RUST_X448 = {
|
|
105
|
+
id: "rust-x448",
|
|
106
|
+
title: "Rust X448 key agreement",
|
|
107
|
+
description: "the `x448` crate Secret key agreement",
|
|
108
|
+
category: "key-exchange",
|
|
109
|
+
severity: "medium",
|
|
110
|
+
confidence: "high",
|
|
111
|
+
algorithm: "X448",
|
|
112
|
+
hndl: true,
|
|
113
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
114
|
+
message: "X448 (Rust) is modern but still classical key agreement — harvest-now-decrypt-later.",
|
|
115
|
+
};
|
|
100
116
|
const RULE_RUST_OPENSSL_RSA = {
|
|
101
117
|
id: "rust-openssl-rsa",
|
|
102
118
|
title: "Rust openssl RSA key generation",
|
|
@@ -222,6 +238,52 @@ const RULE_RUST_TLS_DANGEROUS = {
|
|
|
222
238
|
message: "rustls `.dangerous()` opts into disabling certificate verification (MITM risk).",
|
|
223
239
|
remediation: "Avoid the dangerous() escape hatch; keep the default certificate verifier.",
|
|
224
240
|
};
|
|
241
|
+
/**
|
|
242
|
+
* Aliasable Rust crypto types (`<crate>::<OrigType>` → rule). A renamed `use`
|
|
243
|
+
* (`use x25519_dalek::{EphemeralSecret as MontgomerySecret}`) binds the alias to a
|
|
244
|
+
* classical-key type; a later `MontgomerySecret::<ctor>(` is that key's
|
|
245
|
+
* construction. The `::`-qualified and braced rules above miss this because the
|
|
246
|
+
* call site uses the renamed identifier, not the original path.
|
|
247
|
+
*/
|
|
248
|
+
const RUST_ALIASABLE = {
|
|
249
|
+
"x25519_dalek::EphemeralSecret": RULE_RUST_X25519,
|
|
250
|
+
"x25519_dalek::StaticSecret": RULE_RUST_X25519,
|
|
251
|
+
"x448::Secret": RULE_RUST_X448,
|
|
252
|
+
"ed25519_dalek::SigningKey": RULE_RUST_ED25519,
|
|
253
|
+
"ed25519_dalek::Keypair": RULE_RUST_ED25519,
|
|
254
|
+
"ed25519_dalek::SecretKey": RULE_RUST_ED25519,
|
|
255
|
+
};
|
|
256
|
+
/** Escape a string for interpolation into a dynamically-built RegExp. */
|
|
257
|
+
function escapeRustRe(s) {
|
|
258
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Collect `use <crate>::<OrigType> as <Alias>` bindings (braced or single) that
|
|
262
|
+
* rename an aliasable crypto type, returning {alias, rule} pairs. An alias equal
|
|
263
|
+
* to its own type name is skipped (the qualified/bare rules already catch it).
|
|
264
|
+
*/
|
|
265
|
+
function collectRustTypeAliases(content) {
|
|
266
|
+
const out = [];
|
|
267
|
+
const push = (crate, orig, alias) => {
|
|
268
|
+
if (!alias || alias === orig)
|
|
269
|
+
return;
|
|
270
|
+
const rule = RUST_ALIASABLE[`${crate}::${orig}`];
|
|
271
|
+
if (rule)
|
|
272
|
+
out.push({ alias, rule });
|
|
273
|
+
};
|
|
274
|
+
// Braced: `use <crate>::{ Orig as Alias, ... }`.
|
|
275
|
+
const braced = /\buse\s+([\w:]+)::\{([^}]*)\}/g;
|
|
276
|
+
for (let m = braced.exec(content); m; m = braced.exec(content)) {
|
|
277
|
+
const specRe = /([A-Za-z_]\w*)\s+as\s+([A-Za-z_]\w*)/g;
|
|
278
|
+
for (let s = specRe.exec(m[2]); s; s = specRe.exec(m[2]))
|
|
279
|
+
push(m[1], s[1], s[2]);
|
|
280
|
+
}
|
|
281
|
+
// Single: `use <crate>::Orig as Alias;`.
|
|
282
|
+
const single = /\buse\s+([\w:]+)::([A-Za-z_]\w*)\s+as\s+([A-Za-z_]\w*)/g;
|
|
283
|
+
for (let m = single.exec(content); m; m = single.exec(content))
|
|
284
|
+
push(m[1], m[2], m[3]);
|
|
285
|
+
return out;
|
|
286
|
+
}
|
|
225
287
|
/** Detects classical asymmetric crypto in Rust (rsa, ring, dalek, p256/k256). */
|
|
226
288
|
export const rustDetector = {
|
|
227
289
|
id: "rust-crypto",
|
|
@@ -234,6 +296,7 @@ export const rustDetector = {
|
|
|
234
296
|
RULE_RUST_ECDH,
|
|
235
297
|
RULE_RUST_ED25519,
|
|
236
298
|
RULE_RUST_X25519,
|
|
299
|
+
RULE_RUST_X448,
|
|
237
300
|
RULE_RUST_OPENSSL_RSA,
|
|
238
301
|
RULE_RUST_OPENSSL_EC,
|
|
239
302
|
RULE_RUST_OPENSSL_DSA,
|
|
@@ -254,6 +317,7 @@ export const rustDetector = {
|
|
|
254
317
|
add(RE_RUST_ECDH, RULE_RUST_ECDH);
|
|
255
318
|
add(RE_RUST_ED25519, RULE_RUST_ED25519);
|
|
256
319
|
add(RE_RUST_X25519, RULE_RUST_X25519);
|
|
320
|
+
add(RE_RUST_X448, RULE_RUST_X448);
|
|
257
321
|
add(RE_RUST_OPENSSL_RSA, RULE_RUST_OPENSSL_RSA);
|
|
258
322
|
add(RE_RUST_OPENSSL_EC, RULE_RUST_OPENSSL_EC);
|
|
259
323
|
add(RE_RUST_OPENSSL_DSA, RULE_RUST_OPENSSL_DSA);
|
|
@@ -270,6 +334,15 @@ export const rustDetector = {
|
|
|
270
334
|
});
|
|
271
335
|
add(RE_RUST_TLS_ACCEPT_INVALID, RULE_RUST_TLS_ACCEPT_INVALID);
|
|
272
336
|
add(RE_RUST_TLS_DANGEROUS, RULE_RUST_TLS_DANGEROUS);
|
|
337
|
+
// Type-alias resolution: `use x25519_dalek::{EphemeralSecret as MontgomerySecret}`
|
|
338
|
+
// then `MontgomerySecret::random_from_rng(` — the braced+renamed `use` defeats
|
|
339
|
+
// the `::`-qualified rules. Match the aliased type's construction calls; runs
|
|
340
|
+
// on the ORIGINAL content so locations stay exact, and fires only for an alias
|
|
341
|
+
// explicitly bound to a known crypto type (precision-safe).
|
|
342
|
+
for (const { alias, rule } of collectRustTypeAliases(content)) {
|
|
343
|
+
const a = escapeRustRe(alias);
|
|
344
|
+
add(new RegExp(`\\b${a}::(?:new|random|random_from_rng|generate|from_bytes)\\s*\\(`, "g"), rule);
|
|
345
|
+
}
|
|
273
346
|
return findings;
|
|
274
347
|
},
|
|
275
348
|
};
|