@quantakrypto/core 0.4.4 → 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.map +1 -1
- package/dist/cbom-merge.js +30 -8
- package/dist/cbom-merge.js.map +1 -1
- 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 +2 -23
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +24 -5
- package/dist/comments.js.map +1 -1
- package/dist/detect-utils.d.ts +55 -21
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +133 -7
- 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.map +1 -1
- package/dist/detectors/c.js +30 -3
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts.map +1 -1
- package/dist/detectors/cicd.js +18 -5
- package/dist/detectors/cicd.js.map +1 -1
- package/dist/detectors/cloud-kms.d.ts +8 -5
- package/dist/detectors/cloud-kms.d.ts.map +1 -1
- package/dist/detectors/cloud-kms.js +62 -20
- package/dist/detectors/cloud-kms.js.map +1 -1
- package/dist/detectors/cloudformation.d.ts +8 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -1
- package/dist/detectors/cloudformation.js +41 -10
- package/dist/detectors/cloudformation.js.map +1 -1
- 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 +5 -3
- package/dist/detectors/database.d.ts.map +1 -1
- package/dist/detectors/database.js +45 -8
- package/dist/detectors/database.js.map +1 -1
- 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.map +1 -1
- package/dist/detectors/dnssec.js +19 -10
- package/dist/detectors/dnssec.js.map +1 -1
- package/dist/detectors/elixir.d.ts.map +1 -1
- package/dist/detectors/elixir.js +34 -2
- package/dist/detectors/elixir.js.map +1 -1
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +33 -3
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts.map +1 -1
- package/dist/detectors/jose.js +13 -3
- package/dist/detectors/jose.js.map +1 -1
- package/dist/detectors/jwk.d.ts +7 -7
- package/dist/detectors/jwk.d.ts.map +1 -1
- package/dist/detectors/jwk.js +44 -8
- package/dist/detectors/jwk.js.map +1 -1
- package/dist/detectors/k8s.d.ts.map +1 -1
- package/dist/detectors/k8s.js +5 -2
- package/dist/detectors/k8s.js.map +1 -1
- 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 +7 -8
- package/dist/detectors/mesh.d.ts.map +1 -1
- package/dist/detectors/mesh.js +17 -35
- package/dist/detectors/mesh.js.map +1 -1
- package/dist/detectors/messaging.d.ts +2 -2
- package/dist/detectors/messaging.d.ts.map +1 -1
- package/dist/detectors/messaging.js +19 -12
- package/dist/detectors/messaging.js.map +1 -1
- 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 +32 -0
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +2 -1
- package/dist/detectors/php.d.ts.map +1 -1
- package/dist/detectors/php.js +7 -3
- package/dist/detectors/php.js.map +1 -1
- 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/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +5 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +4 -1
- package/dist/detectors/secrets.d.ts.map +1 -1
- package/dist/detectors/secrets.js +5 -21
- package/dist/detectors/secrets.js.map +1 -1
- 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 +53 -6
- 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/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.map +1 -1
- package/dist/detectors/terraform.js +35 -8
- package/dist/detectors/terraform.js.map +1 -1
- 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 +8 -8
- package/dist/detectors/vpn.d.ts.map +1 -1
- package/dist/detectors/vpn.js +12 -38
- package/dist/detectors/vpn.js.map +1 -1
- 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 +74 -4
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +71 -0
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +14 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -11
- 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/registry.d.ts +9 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +51 -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 +1 -1
- 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 +31 -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/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 +38 -8
- package/src/cbom.ts +153 -39
- package/src/comments.ts +25 -6
- package/src/detect-utils.ts +133 -11
- 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 +31 -3
- package/src/detectors/cicd.ts +18 -5
- package/src/detectors/cloud-kms.ts +79 -25
- package/src/detectors/cloudformation.ts +45 -10
- 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 +64 -11
- package/src/detectors/dkim.ts +172 -0
- package/src/detectors/dnssec.ts +19 -10
- package/src/detectors/elixir.ts +33 -2
- package/src/detectors/java.ts +37 -3
- package/src/detectors/jose.ts +19 -3
- package/src/detectors/jwk.ts +59 -17
- package/src/detectors/k8s.ts +5 -2
- package/src/detectors/keystore.ts +109 -0
- package/src/detectors/mesh.ts +21 -45
- package/src/detectors/messaging.ts +22 -14
- package/src/detectors/objc.ts +209 -0
- package/src/detectors/openpgp.ts +194 -0
- package/src/detectors/pem.ts +30 -0
- package/src/detectors/php.ts +9 -4
- package/src/detectors/pkcs11.ts +113 -0
- package/src/detectors/proxy.ts +236 -0
- package/src/detectors/pulumi.ts +106 -0
- package/src/detectors/rust.ts +5 -0
- package/src/detectors/secrets.ts +15 -26
- package/src/detectors/solidity.ts +176 -0
- package/src/detectors/source.ts +48 -5
- package/src/detectors/spire.ts +143 -0
- package/src/detectors/ssh-ca.ts +215 -0
- package/src/detectors/supply-chain.ts +117 -0
- package/src/detectors/swift.ts +149 -0
- package/src/detectors/terraform.ts +42 -8
- package/src/detectors/vault.ts +120 -0
- package/src/detectors/vpn.ts +20 -48
- 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 +127 -4
- package/src/index.ts +41 -13
- package/src/inventory.ts +15 -7
- package/src/registry.ts +51 -4
- package/src/remediate-pipeline.ts +1 -1
- package/src/remediation.ts +39 -0
- package/src/report.ts +36 -1
- package/src/scan-worker.ts +5 -3
- package/src/scan.ts +41 -6
- package/src/standards-profiles.ts +136 -0
- package/src/types.ts +9 -4
- package/src/version.ts +1 -1
- package/src/vex.ts +122 -0
- package/src/walk.ts +33 -2
package/src/detectors/java.ts
CHANGED
|
@@ -35,7 +35,7 @@ const RE_JAVA_GETINSTANCE =
|
|
|
35
35
|
|
|
36
36
|
// BouncyCastle lightweight-API class instantiations.
|
|
37
37
|
const RE_JAVA_BC =
|
|
38
|
-
/\bnew\s+(RSAKeyPairGenerator|DSAKeyPairGenerator|ECKeyPairGenerator|ECDSASigner|Ed25519Signer|Ed448Signer|X25519Agreement|X448Agreement|ECDHBasicAgreement|DHBasicAgreement|X25519KeyPairGenerator|Ed25519KeyPairGenerator|RSAEngine|OAEPEncoding)\s*\(/g;
|
|
38
|
+
/\bnew\s+(RSAKeyPairGenerator|DSAKeyPairGenerator|ECKeyPairGenerator|ECDSASigner|Ed25519Signer|Ed448Signer|X25519Agreement|X448Agreement|ECDHBasicAgreement|DHBasicAgreement|X25519KeyPairGenerator|X448KeyPairGenerator|Ed25519KeyPairGenerator|Ed448KeyPairGenerator|X448PrivateKeyParameters|RSAEngine|OAEPEncoding)\s*\(/g;
|
|
39
39
|
|
|
40
40
|
// BouncyCastle lightweight-API curve/EdDSA classes as a BARE constructor call —
|
|
41
41
|
// the Kotlin form (`X448KeyPairGenerator()`, no `new` keyword). RE_JAVA_BC above
|
|
@@ -47,13 +47,30 @@ const RE_JAVA_BC =
|
|
|
47
47
|
const RE_JAVA_BC_CURVE =
|
|
48
48
|
/(?<!\bnew\s+)\b(X448KeyPairGenerator|X448Agreement|X448PrivateKeyParameters|X25519KeyPairGenerator|X25519Agreement|Ed448KeyPairGenerator|Ed448Signer|Ed25519KeyPairGenerator|Ed25519Signer)\s*\(/g;
|
|
49
49
|
|
|
50
|
+
// The non-curve BouncyCastle classes (RSA/EC/DSA/DH keygen, signers, engines) as a
|
|
51
|
+
// BARE constructor call — the Kotlin form, `ECKeyPairGenerator()` with no `new`. Java
|
|
52
|
+
// and Scala instantiate Java classes with `new` (caught by RE_JAVA_BC), so bare calls
|
|
53
|
+
// to these distinctive class names are Kotlin. The `(?<!\bnew\s+)` avoids double-
|
|
54
|
+
// counting the `new` form; routed through the same BC_CLASS_RULES map.
|
|
55
|
+
const RE_JAVA_BC_BARE =
|
|
56
|
+
/(?<!\bnew\s+)\b(RSAKeyPairGenerator|DSAKeyPairGenerator|ECKeyPairGenerator|ECDSASigner|ECDHBasicAgreement|DHBasicAgreement|RSAEngine|OAEPEncoding)\s*\(/g;
|
|
57
|
+
|
|
50
58
|
// Insecure JSSE / TLS configuration expressed in Java source. Mirrors the JS
|
|
51
59
|
// tlsDetector in source.ts (category "tls"): a legacy SSL/TLS protocol version
|
|
52
60
|
// requested from SSLContext, and TLS hostname verification neutered via an
|
|
53
61
|
// all-trusting verifier (Apache HttpClient's NoopHostnameVerifier /
|
|
54
62
|
// ALLOW_ALL_HOSTNAME_VERIFIER).
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
// `TLSv1(?:\.1)?` catches both TLS 1.0 and the equally-deprecated TLS 1.1 (RFC 8996),
|
|
64
|
+
// mirroring the JS tlsDetector; the closing `"` keeps `"TLSv1.2"`/`"TLSv1.3"`/`"TLS"`
|
|
65
|
+
// from matching. `SSLv2` is included alongside `SSLv3`.
|
|
66
|
+
const RE_JAVA_TLS_LEGACY =
|
|
67
|
+
/\bSSLContext\s*\.\s*getInstance\s*\(\s*"(SSL|SSLv2|SSLv3|TLSv1(?:\.1)?)"/g;
|
|
68
|
+
// Require a USAGE position, not a bare identifier: `NoopHostnameVerifier` followed by
|
|
69
|
+
// `(` (construction) or `.` (`.INSTANCE`), and `ALLOW_ALL_HOSTNAME_VERIFIER` preceded
|
|
70
|
+
// by `.` (member access). This keeps `new NoopHostnameVerifier()` and
|
|
71
|
+
// `SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER` while dropping the common
|
|
72
|
+
// false positive of a plain `import …NoopHostnameVerifier;` line.
|
|
73
|
+
const RE_JAVA_TLS_NOVERIFY = /\bNoopHostnameVerifier\s*[.(]|\.\s*ALLOW_ALL_HOSTNAME_VERIFIER\b/g;
|
|
57
74
|
|
|
58
75
|
// Identifier-form JWT/JOSE signature algorithms (audit F7). The quoted-string
|
|
59
76
|
// alg token ("RS256") is caught by the language-agnostic jwt-jose detector, but
|
|
@@ -278,6 +295,10 @@ function classifyGetInstance(factory: string, rawAlg: string): RuleMeta | null {
|
|
|
278
295
|
const isSignature = factory === "Signature";
|
|
279
296
|
|
|
280
297
|
if (alg.includes("ECDSA")) return RULE_JAVA_ECDSA_SIGN;
|
|
298
|
+
// ECIES (EC Integrated Encryption Scheme, incl. `ECIESwithAES-CBC`) is classical EC
|
|
299
|
+
// public-key ENCRYPTION — harvest-now-decrypt-later exposed. Checked before the EC
|
|
300
|
+
// branches below (which would otherwise miss it) and the RSA/DSA checks.
|
|
301
|
+
if (alg.includes("ECIES")) return RULE_JAVA_ECDH;
|
|
281
302
|
if (alg.includes("ECDH")) return RULE_JAVA_ECDH;
|
|
282
303
|
if (alg === "EC") return isSignature ? RULE_JAVA_ECDSA_SIGN : RULE_JAVA_EC_KEYGEN;
|
|
283
304
|
if (alg.includes("ED25519") || alg.includes("ED448") || alg.includes("EDDSA"))
|
|
@@ -307,7 +328,10 @@ const BC_CLASS_RULES: Record<string, RuleMeta> = {
|
|
|
307
328
|
ECDHBasicAgreement: RULE_JAVA_ECDH,
|
|
308
329
|
DHBasicAgreement: RULE_JAVA_DH,
|
|
309
330
|
X25519KeyPairGenerator: RULE_JAVA_XDH,
|
|
331
|
+
X448KeyPairGenerator: RULE_JAVA_XDH,
|
|
332
|
+
X448PrivateKeyParameters: RULE_JAVA_XDH,
|
|
310
333
|
Ed25519KeyPairGenerator: RULE_JAVA_EDDSA,
|
|
334
|
+
Ed448KeyPairGenerator: RULE_JAVA_EDDSA,
|
|
311
335
|
RSAEngine: RULE_JAVA_RSA,
|
|
312
336
|
OAEPEncoding: RULE_JAVA_RSA,
|
|
313
337
|
};
|
|
@@ -381,6 +405,16 @@ export const javaDetector: Detector = {
|
|
|
381
405
|
);
|
|
382
406
|
});
|
|
383
407
|
|
|
408
|
+
// Bare (Kotlin-form) constructor calls to the non-curve BC classes — same rules
|
|
409
|
+
// as the `new` form, routed via BC_CLASS_RULES.
|
|
410
|
+
eachMatch(RE_JAVA_BC_BARE, content, (m) => {
|
|
411
|
+
const rule = BC_CLASS_RULES[m[1]];
|
|
412
|
+
if (!rule) return;
|
|
413
|
+
findings.push(
|
|
414
|
+
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
|
|
384
418
|
// Legacy SSL/TLS protocol version requested from SSLContext.
|
|
385
419
|
eachMatch(RE_JAVA_TLS_LEGACY, content, (m) => {
|
|
386
420
|
findings.push(
|
package/src/detectors/jose.ts
CHANGED
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
* the detector applies to any file (config, JSON, source) after a fast reject.
|
|
16
16
|
*/
|
|
17
17
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
JWT_HOST_EXTENSIONS,
|
|
20
|
+
DOC_EXTENSIONS,
|
|
21
|
+
eachMatch,
|
|
22
|
+
enclosingObject,
|
|
23
|
+
findingFromRule,
|
|
24
|
+
hasExtension,
|
|
25
|
+
} from "../detect-utils.js";
|
|
19
26
|
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
20
27
|
|
|
21
28
|
interface JoseRule {
|
|
@@ -70,8 +77,12 @@ export const joseDetector: Detector = {
|
|
|
70
77
|
scope: "config",
|
|
71
78
|
language: "any",
|
|
72
79
|
rules: JOSE_RULES.map((r) => r.meta),
|
|
73
|
-
// Prose examples (a README showing `"alg":"RSA-OAEP"`) are not JOSE config.
|
|
74
|
-
|
|
80
|
+
// Prose examples (a README showing `"alg":"RSA-OAEP"`) are not JOSE config. In the
|
|
81
|
+
// JS/TS/Python/Go/Ruby source files that source.ts's `jose-rsa-oaep`/`jose-ecdh-es`
|
|
82
|
+
// token rules actually cover (JWT_HOST_EXTENSIONS), defer to them to avoid a
|
|
83
|
+
// double-count — but STILL run on other source languages (Java/C#/Rust/PHP/Elixir/
|
|
84
|
+
// C/C++), which source.ts does not cover for these tokens.
|
|
85
|
+
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS) && !hasExtension(f, JWT_HOST_EXTENSIONS),
|
|
75
86
|
detect({ file, content }): Finding[] {
|
|
76
87
|
// Fast reject: no JOSE key-management alg token present.
|
|
77
88
|
if (
|
|
@@ -84,6 +95,11 @@ export const joseDetector: Detector = {
|
|
|
84
95
|
const findings: Finding[] = [];
|
|
85
96
|
for (const rule of JOSE_RULES) {
|
|
86
97
|
eachMatch(rule.re, content, (m) => {
|
|
98
|
+
// If this `alg` belongs to a JWK (a `"kty"` sits in the SAME object), the jwk
|
|
99
|
+
// detector already reports the key — defer so it isn't counted twice. Scoped
|
|
100
|
+
// to the enclosing object so an unrelated nearby JWK doesn't suppress a real
|
|
101
|
+
// standalone JWE header.
|
|
102
|
+
if (enclosingObject(content, m.index).includes('"kty"')) return;
|
|
87
103
|
findings.push(
|
|
88
104
|
findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),
|
|
89
105
|
);
|
package/src/detectors/jwk.ts
CHANGED
|
@@ -10,17 +10,24 @@
|
|
|
10
10
|
* - `"crv":"Ed25519"|"Ed448"` → EdDSA signing key.
|
|
11
11
|
* - `"crv":"X25519"|"X448"` → classical Montgomery-curve key agreement.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Usage-aware classification: when a key's surrounding `"use"` / `"alg"` marks it
|
|
14
|
+
* as a SIGNING key (`use:"sig"`, or an `RS`/`PS`/`ES` signature alg), the finding
|
|
15
|
+
* is a `signature` (`hndl:false`) — a signing key is forgeable at a CRQC but not
|
|
16
|
+
* harvest-now exposed. An encryption/unspecified key stays `hndl:true`.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* pair, not necessarily an embedded secret.
|
|
18
|
+
* Deferrals to avoid double-counting: skipped on doc extensions (a README JWK
|
|
19
|
+
* example is not live) and inside CloudFormation/ARM templates, where the
|
|
20
|
+
* cloudformation detector owns the `kty` of a `Microsoft.KeyVault` key resource.
|
|
21
21
|
*/
|
|
22
22
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
DOC_EXTENSIONS,
|
|
25
|
+
eachMatch,
|
|
26
|
+
enclosingObject,
|
|
27
|
+
findingFromRule,
|
|
28
|
+
hasExtension,
|
|
29
|
+
} from "../detect-utils.js";
|
|
30
|
+
import { isCloudTemplateFile } from "./cloudformation.js";
|
|
24
31
|
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
25
32
|
|
|
26
33
|
interface JwkRule {
|
|
@@ -99,6 +106,20 @@ const JWK_RULES: JwkRule[] = [
|
|
|
99
106
|
},
|
|
100
107
|
];
|
|
101
108
|
|
|
109
|
+
/**
|
|
110
|
+
* True when THIS key's own object marks it as a signing key via `use`/`alg`. The
|
|
111
|
+
* caller passes the enclosing `{…}` object so a neighbouring key in a JWKS array
|
|
112
|
+
* can't contaminate the result, and an explicit `"use":"enc"` on the key always
|
|
113
|
+
* wins (never classify an encryption key as a signature).
|
|
114
|
+
*/
|
|
115
|
+
function isSigningUse(objectText: string, sigAlg: RegExp): boolean {
|
|
116
|
+
if (/"use"\s*:\s*"enc"/.test(objectText)) return false;
|
|
117
|
+
return /"use"\s*:\s*"sig"/.test(objectText) || sigAlg.test(objectText);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const RSA_SIG_ALG = /"alg"\s*:\s*"(?:RS|PS)(?:256|384|512)"/;
|
|
121
|
+
const EC_SIG_ALG = /"alg"\s*:\s*"ES(?:256K?|384|512)"/;
|
|
122
|
+
|
|
102
123
|
/** Detects classical JSON Web Key (JWK/JWKS) material in any JSON/text file. */
|
|
103
124
|
export const jwkDetector: Detector = {
|
|
104
125
|
id: "jwk-material",
|
|
@@ -106,21 +127,42 @@ export const jwkDetector: Detector = {
|
|
|
106
127
|
scope: "config",
|
|
107
128
|
language: "any",
|
|
108
129
|
rules: JWK_RULES.map((r) => r.meta),
|
|
109
|
-
appliesTo: () =>
|
|
130
|
+
appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
|
|
110
131
|
detect({ file, content }): Finding[] {
|
|
111
132
|
// Fast reject: a JWK always has one of these two members.
|
|
112
133
|
if (!content.includes('"kty"') && !content.includes('"crv"')) return [];
|
|
134
|
+
// The ONLY overlap with the cloudformation detector is the ARM Key Vault key
|
|
135
|
+
// `kty:"RSA"` (cfn-arm-keyvault-rsa), which only fires when Microsoft.KeyVault is
|
|
136
|
+
// present. Defer just the RSA rule in that case — everything else (crv keys, and
|
|
137
|
+
// JWKs embedded in AWS CloudFormation, which cfn does not own) is still reported.
|
|
138
|
+
const isArm = isCloudTemplateFile(file, content) && content.includes("Microsoft.KeyVault");
|
|
139
|
+
|
|
113
140
|
const findings: Finding[] = [];
|
|
114
141
|
for (const rule of JWK_RULES) {
|
|
115
142
|
eachMatch(rule.re, content, (m) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
if (rule.meta.id === "jwk-rsa" && isArm) return; // cfn-arm-keyvault-rsa owns it
|
|
144
|
+
const at = { file, content, index: m.index, matchLength: m[0].length };
|
|
145
|
+
// Analyse the key's OWN enclosing object so a neighbouring key in a JWKS
|
|
146
|
+
// array can't flip this key's sig/enc classification.
|
|
147
|
+
const obj = enclosingObject(content, m.index);
|
|
148
|
+
let overrides;
|
|
149
|
+
if (rule.meta.id === "jwk-rsa" && isSigningUse(obj, RSA_SIG_ALG)) {
|
|
150
|
+
overrides = {
|
|
151
|
+
category: "signature" as const,
|
|
152
|
+
hndl: false,
|
|
153
|
+
message:
|
|
154
|
+
"RSA JSON Web Key (JWK) used for signing (RS*/PS*); forgeable by a quantum attacker.",
|
|
155
|
+
};
|
|
156
|
+
} else if (rule.meta.id === "jwk-ec" && isSigningUse(obj, EC_SIG_ALG)) {
|
|
157
|
+
overrides = {
|
|
158
|
+
category: "signature" as const,
|
|
159
|
+
algorithm: "ECDSA" as const,
|
|
160
|
+
hndl: false,
|
|
161
|
+
message:
|
|
162
|
+
"Elliptic-curve JSON Web Key (JWK) used for ECDSA signing; forgeable by a quantum attacker.",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
findings.push(findingFromRule(rule.meta, at, overrides));
|
|
124
166
|
});
|
|
125
167
|
}
|
|
126
168
|
return findings;
|
package/src/detectors/k8s.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* traffic, whose (EC)DHE key exchange is classical and harvestable.
|
|
13
13
|
*/
|
|
14
14
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
15
|
-
import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
15
|
+
import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
|
|
16
16
|
import { CWE_BROKEN_CRYPTO, CWE_RISKY_PRIMITIVE } from "../cwe.js";
|
|
17
17
|
|
|
18
18
|
const K8S_EXTENSIONS: readonly string[] = [".yaml", ".yml", ".json"];
|
|
@@ -94,9 +94,12 @@ export const k8sDetector: Detector = {
|
|
|
94
94
|
const isIstio = content.includes("minProtocolVersion");
|
|
95
95
|
if (!isCertManager && !isIstio) return [];
|
|
96
96
|
|
|
97
|
+
// A `# algorithm: RSA` YAML comment is not an active setting; match over
|
|
98
|
+
// comment-masked content (offsets preserved).
|
|
99
|
+
const scan = maskCommentLines(content, ["#"]);
|
|
97
100
|
const findings: Finding[] = [];
|
|
98
101
|
const add = (re: RegExp, rule: RuleMeta) =>
|
|
99
|
-
eachMatch(re,
|
|
102
|
+
eachMatch(re, scan, (m) =>
|
|
100
103
|
findings.push(
|
|
101
104
|
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
102
105
|
),
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical cryptographic KEYSTORES committed to a repository —
|
|
3
|
+
* Java KeyStore (JKS), JCEKS, PKCS#12 (.p12/.pfx), and BouncyCastle (.bks). These
|
|
4
|
+
* binary containers hold private keys and certificate chains, essentially always
|
|
5
|
+
* classical (RSA / EC / DSA). A keystore in version control is committed key
|
|
6
|
+
* material: harvest-now-decrypt-later exposed and retroactively un-fixable.
|
|
7
|
+
*
|
|
8
|
+
* The scan pipeline reads these extensions byte-preserving (latin1; see
|
|
9
|
+
* {@link ../walk.ts isKeystorePath} and scan.ts), so `content.charCodeAt(i)` is the
|
|
10
|
+
* i-th file byte and we can identify the container by its magic number. We do NOT
|
|
11
|
+
* parse the enclosed key algorithm (that needs full ASN.1 / proprietary parsing);
|
|
12
|
+
* flagging the presence of a classical keystore is the actionable signal.
|
|
13
|
+
*
|
|
14
|
+
* The match is sensitive key material, so reporters drop the (binary) snippet.
|
|
15
|
+
*/
|
|
16
|
+
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
17
|
+
import { findingFromRule, hasExtension } from "../detect-utils.js";
|
|
18
|
+
import { CWE_HARDCODED_KEY } from "../cwe.js";
|
|
19
|
+
|
|
20
|
+
const KEYSTORE_EXTENSIONS: readonly string[] = [
|
|
21
|
+
".jks",
|
|
22
|
+
".keystore",
|
|
23
|
+
".jceks",
|
|
24
|
+
".bks",
|
|
25
|
+
".p12",
|
|
26
|
+
".pfx",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function baseRule(id: string, title: string, message: string): RuleMeta {
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
title,
|
|
33
|
+
description: message,
|
|
34
|
+
category: "certificate",
|
|
35
|
+
severity: "high",
|
|
36
|
+
confidence: "high",
|
|
37
|
+
hndl: true,
|
|
38
|
+
cwe: CWE_HARDCODED_KEY,
|
|
39
|
+
sensitive: true,
|
|
40
|
+
message,
|
|
41
|
+
remediation:
|
|
42
|
+
"Remove key material from version control and rotate it; store keys in a secret manager / HSM. Plan re-issuance with PQC keys (ML-DSA-65 / ML-KEM-768) as tooling supports it.",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const RULE_JKS = baseRule(
|
|
47
|
+
"keystore-jks",
|
|
48
|
+
"Java KeyStore (JKS)",
|
|
49
|
+
"A Java KeyStore (JKS) holds classical private keys / certificates (RSA/EC/DSA); a keystore in version control is committed, harvest-now-decrypt-later exposed key material.",
|
|
50
|
+
);
|
|
51
|
+
const RULE_JCEKS = baseRule(
|
|
52
|
+
"keystore-jceks",
|
|
53
|
+
"Java JCEKS keystore",
|
|
54
|
+
"A JCEKS keystore holds classical key material (RSA/EC/DSA); committed to version control it is harvest-now-decrypt-later exposed.",
|
|
55
|
+
);
|
|
56
|
+
const RULE_PKCS12 = baseRule(
|
|
57
|
+
"keystore-pkcs12",
|
|
58
|
+
"PKCS#12 keystore",
|
|
59
|
+
"A PKCS#12 container (a `.p12`/`.pfx`, or a modern `.jks`/`.keystore` — keytool writes PKCS#12 by default since Java 9) holds classical private keys / certificate chains (RSA/EC); committed to version control it is harvest-now-decrypt-later exposed.",
|
|
60
|
+
);
|
|
61
|
+
const RULE_BKS = baseRule(
|
|
62
|
+
"keystore-bks",
|
|
63
|
+
"BouncyCastle keystore (.bks)",
|
|
64
|
+
"A BouncyCastle (.bks) keystore holds classical key material; committed to version control it is harvest-now-decrypt-later exposed.",
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
/** Bytes-equal check against `content` read as latin1 (charCodeAt == file byte). */
|
|
68
|
+
function magic(content: string, ...bytes: number[]): boolean {
|
|
69
|
+
if (content.length < bytes.length) return false;
|
|
70
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
71
|
+
if (content.charCodeAt(i) !== bytes[i]) return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Detects committed classical keystores by magic number / extension. */
|
|
77
|
+
export const keystoreDetector: Detector = {
|
|
78
|
+
id: "keystore-material",
|
|
79
|
+
description: "Committed classical cryptographic keystores (JKS, JCEKS, PKCS#12, BKS)",
|
|
80
|
+
scope: "config",
|
|
81
|
+
language: "any",
|
|
82
|
+
rules: [RULE_JKS, RULE_JCEKS, RULE_PKCS12, RULE_BKS],
|
|
83
|
+
appliesTo: (f) => hasExtension(f, KEYSTORE_EXTENSIONS),
|
|
84
|
+
detect({ file, content }): Finding[] {
|
|
85
|
+
const at = { file, content, index: 0, matchLength: 4 };
|
|
86
|
+
// JKS: 0xFEEDFEED. JCEKS: 0xCECECECE.
|
|
87
|
+
if (magic(content, 0xfe, 0xed, 0xfe, 0xed)) return [findingFromRule(RULE_JKS, at)];
|
|
88
|
+
if (magic(content, 0xce, 0xce, 0xce, 0xce)) return [findingFromRule(RULE_JCEKS, at)];
|
|
89
|
+
// PKCS#12 is a DER SEQUENCE. Since Java 9, `keytool` writes PKCS#12 BY DEFAULT
|
|
90
|
+
// even for files named `.jks`/`.keystore` (and Android release keystores are
|
|
91
|
+
// PKCS#12), so this runs for ANY keystore extension once the JKS/JCEKS magics
|
|
92
|
+
// fail. Accept the long-form length octets 0x81/0x82/0x83 (real PKCS#12 is
|
|
93
|
+
// always >127 bytes, so short-form would be a false positive).
|
|
94
|
+
if (content.length >= 2 && content.charCodeAt(0) === 0x30) {
|
|
95
|
+
const lenOctet = content.charCodeAt(1);
|
|
96
|
+
// Long-form DER length (0x81/0x82/0x83) or BER indefinite length (0x80, emitted
|
|
97
|
+
// by NSS/Firefox .p12 exports). Short-form is excluded (real PKCS#12 is >127 B).
|
|
98
|
+
if (lenOctet === 0x80 || lenOctet === 0x81 || lenOctet === 0x82 || lenOctet === 0x83) {
|
|
99
|
+
return [findingFromRule(RULE_PKCS12, { ...at, matchLength: 2 })];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// BouncyCastle keystores have no stable magic; key off the extension, but
|
|
103
|
+
// require some content so an empty `.bks` placeholder does not fire.
|
|
104
|
+
if (file.toLowerCase().endsWith(".bks") && content.length > 0) {
|
|
105
|
+
return [findingFromRule(RULE_BKS, { ...at, matchLength: 1 })];
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
},
|
|
109
|
+
};
|
package/src/detectors/mesh.ts
CHANGED
|
@@ -18,17 +18,19 @@
|
|
|
18
18
|
* - HashiCorp Consul Connect: the `connect { ca_config { private_key_type =
|
|
19
19
|
* "ec" | "rsa" } } }` mesh CA provider setting (also `ca_provider`). Flagged
|
|
20
20
|
* as a classical mesh CA (certificate; ECDSA for "ec", RSA for "rsa").
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* `minProtocolVersion` legacy-TLS-floor rule.
|
|
21
|
+
* Istio `tls.cipherSuites:` `ECDHE-RSA-*` / `ECDHE-ECDSA-*` suites are NOT handled
|
|
22
|
+
* here — the language-agnostic `tls-classical-kex` token rule in `source.ts`
|
|
23
|
+
* already flags those cipher strings in any config file, so a rule here would
|
|
24
|
+
* double-count.
|
|
26
25
|
*/
|
|
27
26
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
28
|
-
import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
29
|
-
import { CWE_BROKEN_CRYPTO
|
|
27
|
+
import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
|
|
28
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
// `.json` is included because Consul agent config is commonly JSON (the
|
|
31
|
+
// `private_key_type` rules already support the JSON `:` form); the content gates
|
|
32
|
+
// keep unrelated generic JSON/YAML out.
|
|
33
|
+
const MESH_EXTENSIONS: readonly string[] = [".yaml", ".yml", ".hcl", ".json"];
|
|
32
34
|
|
|
33
35
|
// Linkerd's default self-managed identity scheme (`linkerd.io/tls`, as opposed
|
|
34
36
|
// to a `kubernetes.io/tls` bring-your-own-cert secret) mints its trust anchor
|
|
@@ -45,11 +47,6 @@ const RE_MESH_LINKERD_ECDSA =
|
|
|
45
47
|
const RE_MESH_CONSUL_RSA = /(?<![\w"-])"?private_key_type"?\s*[:=]\s*"rsa"/gi;
|
|
46
48
|
const RE_MESH_CONSUL_EC = /(?<![\w"-])"?private_key_type"?\s*[:=]\s*"ec"/gi;
|
|
47
49
|
|
|
48
|
-
// Istio mesh TLS cipher suites: OpenSSL-style suite names naming classical
|
|
49
|
-
// ECDHE key exchange combined with an RSA or ECDSA certificate. These are
|
|
50
|
-
// distinctive tokens (no generic-config collision risk).
|
|
51
|
-
const RE_MESH_ISTIO_CLASSICAL_CIPHER = /\bECDHE-(?:RSA|ECDSA)-[A-Z0-9_-]+\b/g;
|
|
52
|
-
|
|
53
50
|
const RULE_MESH_LINKERD_ECDSA: RuleMeta = {
|
|
54
51
|
id: "mesh-linkerd-identity-ecdsa",
|
|
55
52
|
title: "Linkerd ECDSA identity issuer",
|
|
@@ -94,51 +91,31 @@ const RULE_MESH_CONSUL_EC: RuleMeta = {
|
|
|
94
91
|
"Consul Connect's mesh CA issues leaf certificates from a classical EC private key, forgeable by a quantum attacker.",
|
|
95
92
|
remediation: "Plan migration to ML-DSA-65 (FIPS 204) as the Connect CA provider adds support.",
|
|
96
93
|
};
|
|
97
|
-
const RULE_MESH_ISTIO_CLASSICAL_CIPHER: RuleMeta = {
|
|
98
|
-
id: "mesh-istio-classical-cipher",
|
|
99
|
-
title: "Istio classical ECDHE cipher suite",
|
|
100
|
-
description:
|
|
101
|
-
"Istio DestinationRule/Gateway tls.cipherSuites lists a classical ECDHE-RSA/ECDHE-ECDSA suite",
|
|
102
|
-
category: "tls",
|
|
103
|
-
severity: "medium",
|
|
104
|
-
confidence: "high",
|
|
105
|
-
algorithm: "ECDH",
|
|
106
|
-
hndl: true,
|
|
107
|
-
cwe: CWE_RISKY_PRIMITIVE,
|
|
108
|
-
message:
|
|
109
|
-
"Istio mesh TLS cipher suite allows classical ECDHE key exchange, which is harvest-now-decrypt-later exposed.",
|
|
110
|
-
remediation:
|
|
111
|
-
"Prefer TLS 1.3 AEAD suites and track PQC-hybrid mesh key exchange (X25519MLKEM768) as Envoy/BoringSSL adds support.",
|
|
112
|
-
};
|
|
113
|
-
|
|
114
94
|
/**
|
|
115
95
|
* Detects classical asymmetric crypto in service-mesh configuration: Linkerd's
|
|
116
|
-
* default ECDSA identity issuer
|
|
117
|
-
*
|
|
118
|
-
*
|
|
96
|
+
* default ECDSA identity issuer and Consul Connect's mesh CA private key type.
|
|
97
|
+
* (Istio ECDHE cipher-suite strings are covered by `source.ts`'s
|
|
98
|
+
* `tls-classical-kex` token rule, so they are not duplicated here.)
|
|
119
99
|
*/
|
|
120
100
|
export const meshDetector: Detector = {
|
|
121
101
|
id: "service-mesh-crypto",
|
|
122
|
-
description:
|
|
123
|
-
"Classical crypto in service-mesh config (Linkerd identity, Consul Connect CA, Istio cipher suites)",
|
|
102
|
+
description: "Classical crypto in service-mesh config (Linkerd identity, Consul Connect CA)",
|
|
124
103
|
scope: "config",
|
|
125
104
|
language: "any",
|
|
126
|
-
rules: [
|
|
127
|
-
RULE_MESH_LINKERD_ECDSA,
|
|
128
|
-
RULE_MESH_CONSUL_RSA,
|
|
129
|
-
RULE_MESH_CONSUL_EC,
|
|
130
|
-
RULE_MESH_ISTIO_CLASSICAL_CIPHER,
|
|
131
|
-
],
|
|
105
|
+
rules: [RULE_MESH_LINKERD_ECDSA, RULE_MESH_CONSUL_RSA, RULE_MESH_CONSUL_EC],
|
|
132
106
|
appliesTo: (f) => hasExtension(f, MESH_EXTENSIONS),
|
|
133
107
|
detect({ file, content }): Finding[] {
|
|
134
108
|
const isLinkerd = content.includes("linkerd") || content.includes("identityTrustAnchors");
|
|
135
109
|
const isConsulConnect = content.includes("consul") && content.includes("connect");
|
|
136
|
-
|
|
137
|
-
if (!isLinkerd && !isConsulConnect && !isIstioCipher) return [];
|
|
110
|
+
if (!isLinkerd && !isConsulConnect) return [];
|
|
138
111
|
|
|
112
|
+
// Mask whole comment lines (YAML/HCL `#`, HCL/JSON5 `//`) so a commented-out
|
|
113
|
+
// directive can't fire — `.yaml`/`.hcl`/`.json` are not centrally stripped.
|
|
114
|
+
// Offsets are preserved, so the finding location stays exact.
|
|
115
|
+
const scan = maskCommentLines(content, ["#", "//"]);
|
|
139
116
|
const findings: Finding[] = [];
|
|
140
117
|
const add = (re: RegExp, rule: RuleMeta) =>
|
|
141
|
-
eachMatch(re,
|
|
118
|
+
eachMatch(re, scan, (m) =>
|
|
142
119
|
findings.push(
|
|
143
120
|
findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
|
|
144
121
|
),
|
|
@@ -148,7 +125,6 @@ export const meshDetector: Detector = {
|
|
|
148
125
|
add(RE_MESH_CONSUL_RSA, RULE_MESH_CONSUL_RSA);
|
|
149
126
|
add(RE_MESH_CONSUL_EC, RULE_MESH_CONSUL_EC);
|
|
150
127
|
}
|
|
151
|
-
if (isIstioCipher) add(RE_MESH_ISTIO_CLASSICAL_CIPHER, RULE_MESH_ISTIO_CLASSICAL_CIPHER);
|
|
152
128
|
return findings;
|
|
153
129
|
},
|
|
154
130
|
};
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* Covered in broker config files (`.properties`, `.conf`, `.cfg`, `.ini`):
|
|
8
8
|
* - Kafka `ssl.protocol` / `ssl.enabled.protocols = TLSv1 | TLSv1.1` (legacy).
|
|
9
9
|
* - MQTT/Mosquitto `tls_version tlsv1 | tlsv1.1` (legacy).
|
|
10
|
-
* - Kafka `ssl.cipher.suites` naming a
|
|
11
|
-
*
|
|
10
|
+
* - Kafka `ssl.cipher.suites` naming a static-RSA `TLS_RSA_WITH_*` key-transport
|
|
11
|
+
* suite (the ECDHE/DHE suites are owned by source.ts's `tls-classical-kex`).
|
|
12
12
|
*/
|
|
13
13
|
import type { Detector, Finding, RuleMeta } from "../types.js";
|
|
14
|
-
import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
|
|
14
|
+
import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
|
|
15
15
|
import { CWE_RISKY_PRIMITIVE, CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
16
16
|
|
|
17
17
|
const MQ_EXTENSIONS: readonly string[] = [".properties", ".conf", ".cfg", ".ini"];
|
|
@@ -25,7 +25,7 @@ const MQ_RULES: MqRule[] = [
|
|
|
25
25
|
{
|
|
26
26
|
// Match TLSv1 (=1.0) and TLSv1.1 but never TLSv1.2 / TLSv1.3: the negative
|
|
27
27
|
// lookahead stops "TLSv1" from matching the "TLSv1" inside "TLSv1.3".
|
|
28
|
-
re: /\bssl\.(?:enabled\.)?protocols?\s*=\s*[^\n]
|
|
28
|
+
re: /\bssl\.(?:enabled\.)?protocols?\s*=\s*[^\n]{0,80}?\bTLSv1(?:\.1)?(?![.\d])/gi,
|
|
29
29
|
meta: {
|
|
30
30
|
id: "mq-kafka-legacy-tls",
|
|
31
31
|
title: "Kafka legacy TLS protocol",
|
|
@@ -36,7 +36,7 @@ const MQ_RULES: MqRule[] = [
|
|
|
36
36
|
hndl: false,
|
|
37
37
|
cwe: CWE_RISKY_PRIMITIVE,
|
|
38
38
|
message:
|
|
39
|
-
"Kafka broker permits legacy TLS 1.0/1.1;
|
|
39
|
+
"Kafka broker permits legacy TLS 1.0/1.1, an obsolete protocol; require TLS 1.3 (the harvestable classical key exchange is reported separately by the cipher-suite rule).",
|
|
40
40
|
remediation: "Require TLS 1.3 and track PQC-hybrid KEX (X25519MLKEM768).",
|
|
41
41
|
},
|
|
42
42
|
},
|
|
@@ -52,24 +52,28 @@ const MQ_RULES: MqRule[] = [
|
|
|
52
52
|
hndl: false,
|
|
53
53
|
cwe: CWE_RISKY_PRIMITIVE,
|
|
54
54
|
message:
|
|
55
|
-
"MQTT broker permits legacy TLS 1.0/1.1;
|
|
55
|
+
"MQTT broker permits legacy TLS 1.0/1.1, an obsolete protocol; require TLS 1.3 (the harvestable classical key exchange is reported separately).",
|
|
56
56
|
remediation: "Require TLS 1.3 and track PQC-hybrid KEX for device fleets.",
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
|
|
60
|
+
// Only static-RSA key transport (`TLS_RSA_WITH_…`) is flagged here: the ECDHE /
|
|
61
|
+
// DHE suites are owned by source.ts's language-agnostic `tls-classical-kex` token
|
|
62
|
+
// rule (which fires on `.properties` too), so flagging them here would double-count.
|
|
63
|
+
re: /\bssl\.cipher\.suites\s*=\s*[^\n]{0,200}?\bTLS_RSA_WITH_/g,
|
|
61
64
|
meta: {
|
|
62
|
-
id: "mq-
|
|
63
|
-
title: "Broker
|
|
64
|
-
description:
|
|
65
|
-
|
|
65
|
+
id: "mq-rsa-key-transport",
|
|
66
|
+
title: "Broker static-RSA key transport cipher",
|
|
67
|
+
description:
|
|
68
|
+
"Kafka ssl.cipher.suites names a static-RSA (TLS_RSA_WITH_*) key-transport suite",
|
|
69
|
+
category: "kem",
|
|
70
|
+
algorithm: "RSA",
|
|
66
71
|
severity: "medium",
|
|
67
72
|
confidence: "high",
|
|
68
|
-
algorithm: "ECDH",
|
|
69
73
|
hndl: true,
|
|
70
74
|
cwe: CWE_BROKEN_CRYPTO,
|
|
71
75
|
message:
|
|
72
|
-
"Broker TLS is pinned to a
|
|
76
|
+
"Broker TLS is pinned to a static-RSA (TLS_RSA_WITH_*) key-transport suite; the wrapped session key is harvest-now-decrypt-later exposed (and it has no forward secrecy).",
|
|
73
77
|
remediation:
|
|
74
78
|
"Move to TLS 1.3 with a PQC-hybrid group (X25519MLKEM768) once the broker/runtime supports it.",
|
|
75
79
|
},
|
|
@@ -86,8 +90,12 @@ export const messagingDetector: Detector = {
|
|
|
86
90
|
appliesTo: (f) => hasExtension(f, MQ_EXTENSIONS),
|
|
87
91
|
detect({ file, content }): Finding[] {
|
|
88
92
|
const findings: Finding[] = [];
|
|
93
|
+
// Broker config files ship large commented-out example blocks; a commented
|
|
94
|
+
// directive is not active. Match over comment-masked content (offsets preserved,
|
|
95
|
+
// so the snippet from the original `content` is still correct).
|
|
96
|
+
const scan = maskCommentLines(content, ["#", "!", ";"]);
|
|
89
97
|
for (const rule of MQ_RULES) {
|
|
90
|
-
eachMatch(rule.re,
|
|
98
|
+
eachMatch(rule.re, scan, (m) => {
|
|
91
99
|
findings.push(
|
|
92
100
|
findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),
|
|
93
101
|
);
|