@quantakrypto/core 0.4.4 → 0.6.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.
Files changed (302) hide show
  1. package/README.md +76 -27
  2. package/dist/cbom-merge.d.ts.map +1 -1
  3. package/dist/cbom-merge.js +30 -8
  4. package/dist/cbom-merge.js.map +1 -1
  5. package/dist/cbom.d.ts +5 -0
  6. package/dist/cbom.d.ts.map +1 -1
  7. package/dist/cbom.js +132 -37
  8. package/dist/cbom.js.map +1 -1
  9. package/dist/comments.d.ts +2 -23
  10. package/dist/comments.d.ts.map +1 -1
  11. package/dist/comments.js +24 -5
  12. package/dist/comments.js.map +1 -1
  13. package/dist/crypto-agility.d.ts +158 -0
  14. package/dist/crypto-agility.d.ts.map +1 -0
  15. package/dist/crypto-agility.js +285 -0
  16. package/dist/crypto-agility.js.map +1 -0
  17. package/dist/dependencies.d.ts.map +1 -1
  18. package/dist/dependencies.js +180 -18
  19. package/dist/dependencies.js.map +1 -1
  20. package/dist/detect-utils.d.ts +55 -21
  21. package/dist/detect-utils.d.ts.map +1 -1
  22. package/dist/detect-utils.js +133 -7
  23. package/dist/detect-utils.js.map +1 -1
  24. package/dist/detectors/age.d.ts +16 -0
  25. package/dist/detectors/age.d.ts.map +1 -0
  26. package/dist/detectors/age.js +40 -0
  27. package/dist/detectors/age.js.map +1 -0
  28. package/dist/detectors/ansible.d.ts +13 -0
  29. package/dist/detectors/ansible.d.ts.map +1 -0
  30. package/dist/detectors/ansible.js +108 -0
  31. package/dist/detectors/ansible.js.map +1 -0
  32. package/dist/detectors/bicep.d.ts +20 -0
  33. package/dist/detectors/bicep.d.ts.map +1 -0
  34. package/dist/detectors/bicep.js +74 -0
  35. package/dist/detectors/bicep.js.map +1 -0
  36. package/dist/detectors/c.d.ts.map +1 -1
  37. package/dist/detectors/c.js +30 -3
  38. package/dist/detectors/c.js.map +1 -1
  39. package/dist/detectors/cicd.d.ts.map +1 -1
  40. package/dist/detectors/cicd.js +18 -5
  41. package/dist/detectors/cicd.js.map +1 -1
  42. package/dist/detectors/cloud-kms.d.ts +8 -5
  43. package/dist/detectors/cloud-kms.d.ts.map +1 -1
  44. package/dist/detectors/cloud-kms.js +62 -20
  45. package/dist/detectors/cloud-kms.js.map +1 -1
  46. package/dist/detectors/cloudformation.d.ts +8 -0
  47. package/dist/detectors/cloudformation.d.ts.map +1 -1
  48. package/dist/detectors/cloudformation.js +41 -10
  49. package/dist/detectors/cloudformation.js.map +1 -1
  50. package/dist/detectors/codesign.d.ts +50 -0
  51. package/dist/detectors/codesign.d.ts.map +1 -0
  52. package/dist/detectors/codesign.js +143 -0
  53. package/dist/detectors/codesign.js.map +1 -0
  54. package/dist/detectors/csharp.d.ts.map +1 -1
  55. package/dist/detectors/csharp.js +13 -3
  56. package/dist/detectors/csharp.js.map +1 -1
  57. package/dist/detectors/dart.d.ts +35 -0
  58. package/dist/detectors/dart.d.ts.map +1 -0
  59. package/dist/detectors/dart.js +177 -0
  60. package/dist/detectors/dart.js.map +1 -0
  61. package/dist/detectors/database.d.ts +5 -3
  62. package/dist/detectors/database.d.ts.map +1 -1
  63. package/dist/detectors/database.js +45 -8
  64. package/dist/detectors/database.js.map +1 -1
  65. package/dist/detectors/dkim.d.ts +54 -0
  66. package/dist/detectors/dkim.d.ts.map +1 -0
  67. package/dist/detectors/dkim.js +92 -0
  68. package/dist/detectors/dkim.js.map +1 -0
  69. package/dist/detectors/dnssec.d.ts.map +1 -1
  70. package/dist/detectors/dnssec.js +19 -10
  71. package/dist/detectors/dnssec.js.map +1 -1
  72. package/dist/detectors/elixir.d.ts.map +1 -1
  73. package/dist/detectors/elixir.js +34 -2
  74. package/dist/detectors/elixir.js.map +1 -1
  75. package/dist/detectors/java.d.ts.map +1 -1
  76. package/dist/detectors/java.js +33 -3
  77. package/dist/detectors/java.js.map +1 -1
  78. package/dist/detectors/jose.d.ts.map +1 -1
  79. package/dist/detectors/jose.js +13 -3
  80. package/dist/detectors/jose.js.map +1 -1
  81. package/dist/detectors/jwk.d.ts +7 -7
  82. package/dist/detectors/jwk.d.ts.map +1 -1
  83. package/dist/detectors/jwk.js +44 -8
  84. package/dist/detectors/jwk.js.map +1 -1
  85. package/dist/detectors/k8s.d.ts.map +1 -1
  86. package/dist/detectors/k8s.js +5 -2
  87. package/dist/detectors/k8s.js.map +1 -1
  88. package/dist/detectors/keystore.d.ts +19 -0
  89. package/dist/detectors/keystore.d.ts.map +1 -0
  90. package/dist/detectors/keystore.js +76 -0
  91. package/dist/detectors/keystore.js.map +1 -0
  92. package/dist/detectors/mesh.d.ts +7 -8
  93. package/dist/detectors/mesh.d.ts.map +1 -1
  94. package/dist/detectors/mesh.js +17 -35
  95. package/dist/detectors/mesh.js.map +1 -1
  96. package/dist/detectors/messaging.d.ts +2 -2
  97. package/dist/detectors/messaging.d.ts.map +1 -1
  98. package/dist/detectors/messaging.js +19 -12
  99. package/dist/detectors/messaging.js.map +1 -1
  100. package/dist/detectors/objc.d.ts +41 -0
  101. package/dist/detectors/objc.d.ts.map +1 -0
  102. package/dist/detectors/objc.js +146 -0
  103. package/dist/detectors/objc.js.map +1 -0
  104. package/dist/detectors/openpgp.d.ts +20 -0
  105. package/dist/detectors/openpgp.d.ts.map +1 -0
  106. package/dist/detectors/openpgp.js +151 -0
  107. package/dist/detectors/openpgp.js.map +1 -0
  108. package/dist/detectors/pem.d.ts.map +1 -1
  109. package/dist/detectors/pem.js +32 -0
  110. package/dist/detectors/pem.js.map +1 -1
  111. package/dist/detectors/php.d.ts +2 -1
  112. package/dist/detectors/php.d.ts.map +1 -1
  113. package/dist/detectors/php.js +7 -3
  114. package/dist/detectors/php.js.map +1 -1
  115. package/dist/detectors/pkcs11.d.ts +17 -0
  116. package/dist/detectors/pkcs11.d.ts.map +1 -0
  117. package/dist/detectors/pkcs11.js +87 -0
  118. package/dist/detectors/pkcs11.js.map +1 -0
  119. package/dist/detectors/proxy.d.ts +52 -0
  120. package/dist/detectors/proxy.d.ts.map +1 -0
  121. package/dist/detectors/proxy.js +157 -0
  122. package/dist/detectors/proxy.js.map +1 -0
  123. package/dist/detectors/pulumi.d.ts +19 -0
  124. package/dist/detectors/pulumi.d.ts.map +1 -0
  125. package/dist/detectors/pulumi.js +80 -0
  126. package/dist/detectors/pulumi.js.map +1 -0
  127. package/dist/detectors/rust.d.ts.map +1 -1
  128. package/dist/detectors/rust.js +5 -0
  129. package/dist/detectors/rust.js.map +1 -1
  130. package/dist/detectors/secrets.d.ts +4 -1
  131. package/dist/detectors/secrets.d.ts.map +1 -1
  132. package/dist/detectors/secrets.js +5 -21
  133. package/dist/detectors/secrets.js.map +1 -1
  134. package/dist/detectors/solidity.d.ts +50 -0
  135. package/dist/detectors/solidity.d.ts.map +1 -0
  136. package/dist/detectors/solidity.js +108 -0
  137. package/dist/detectors/solidity.js.map +1 -0
  138. package/dist/detectors/source.d.ts.map +1 -1
  139. package/dist/detectors/source.js +105 -6
  140. package/dist/detectors/source.js.map +1 -1
  141. package/dist/detectors/spire.d.ts +49 -0
  142. package/dist/detectors/spire.d.ts.map +1 -0
  143. package/dist/detectors/spire.js +77 -0
  144. package/dist/detectors/spire.js.map +1 -0
  145. package/dist/detectors/ssh-ca.d.ts +72 -0
  146. package/dist/detectors/ssh-ca.d.ts.map +1 -0
  147. package/dist/detectors/ssh-ca.js +119 -0
  148. package/dist/detectors/ssh-ca.js.map +1 -0
  149. package/dist/detectors/supply-chain.d.ts +17 -0
  150. package/dist/detectors/supply-chain.d.ts.map +1 -0
  151. package/dist/detectors/supply-chain.js +89 -0
  152. package/dist/detectors/supply-chain.js.map +1 -0
  153. package/dist/detectors/swift.d.ts +22 -0
  154. package/dist/detectors/swift.d.ts.map +1 -0
  155. package/dist/detectors/swift.js +120 -0
  156. package/dist/detectors/swift.js.map +1 -0
  157. package/dist/detectors/terraform.d.ts.map +1 -1
  158. package/dist/detectors/terraform.js +35 -8
  159. package/dist/detectors/terraform.js.map +1 -1
  160. package/dist/detectors/vault.d.ts +15 -0
  161. package/dist/detectors/vault.d.ts.map +1 -0
  162. package/dist/detectors/vault.js +97 -0
  163. package/dist/detectors/vault.js.map +1 -0
  164. package/dist/detectors/vpn.d.ts +8 -8
  165. package/dist/detectors/vpn.d.ts.map +1 -1
  166. package/dist/detectors/vpn.js +12 -38
  167. package/dist/detectors/vpn.js.map +1 -1
  168. package/dist/detectors/weak-hash.d.ts +71 -0
  169. package/dist/detectors/weak-hash.d.ts.map +1 -0
  170. package/dist/detectors/weak-hash.js +116 -0
  171. package/dist/detectors/weak-hash.js.map +1 -0
  172. package/dist/detectors/webauthn.d.ts +61 -0
  173. package/dist/detectors/webauthn.d.ts.map +1 -0
  174. package/dist/detectors/webauthn.js +109 -0
  175. package/dist/detectors/webauthn.js.map +1 -0
  176. package/dist/detectors/xmldsig.d.ts +23 -0
  177. package/dist/detectors/xmldsig.d.ts.map +1 -0
  178. package/dist/detectors/xmldsig.js +84 -0
  179. package/dist/detectors/xmldsig.js.map +1 -0
  180. package/dist/evidence.d.ts +74 -4
  181. package/dist/evidence.d.ts.map +1 -1
  182. package/dist/evidence.js +71 -0
  183. package/dist/evidence.js.map +1 -1
  184. package/dist/hndl.d.ts +241 -0
  185. package/dist/hndl.d.ts.map +1 -0
  186. package/dist/hndl.js +752 -0
  187. package/dist/hndl.js.map +1 -0
  188. package/dist/index.d.ts +18 -11
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +21 -11
  191. package/dist/index.js.map +1 -1
  192. package/dist/inventory.d.ts +0 -2
  193. package/dist/inventory.d.ts.map +1 -1
  194. package/dist/inventory.js +15 -6
  195. package/dist/inventory.js.map +1 -1
  196. package/dist/parallel.d.ts.map +1 -1
  197. package/dist/parallel.js +22 -23
  198. package/dist/parallel.js.map +1 -1
  199. package/dist/registry.d.ts +9 -4
  200. package/dist/registry.d.ts.map +1 -1
  201. package/dist/registry.js +51 -4
  202. package/dist/registry.js.map +1 -1
  203. package/dist/remediate-pipeline.d.ts +0 -3
  204. package/dist/remediate-pipeline.d.ts.map +1 -1
  205. package/dist/remediate-pipeline.js +1 -1
  206. package/dist/remediate-pipeline.js.map +1 -1
  207. package/dist/remediation.d.ts +10 -0
  208. package/dist/remediation.d.ts.map +1 -1
  209. package/dist/remediation.js +32 -0
  210. package/dist/remediation.js.map +1 -1
  211. package/dist/report.d.ts +17 -0
  212. package/dist/report.d.ts.map +1 -1
  213. package/dist/report.js +106 -20
  214. package/dist/report.js.map +1 -1
  215. package/dist/scan-worker.js +5 -3
  216. package/dist/scan-worker.js.map +1 -1
  217. package/dist/scan.d.ts.map +1 -1
  218. package/dist/scan.js +35 -6
  219. package/dist/scan.js.map +1 -1
  220. package/dist/standards-profiles.d.ts +58 -0
  221. package/dist/standards-profiles.d.ts.map +1 -0
  222. package/dist/standards-profiles.js +100 -0
  223. package/dist/standards-profiles.js.map +1 -0
  224. package/dist/types.d.ts +7 -6
  225. package/dist/types.d.ts.map +1 -1
  226. package/dist/types.js.map +1 -1
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.js +1 -1
  229. package/dist/version.js.map +1 -1
  230. package/dist/vex.d.ts +39 -0
  231. package/dist/vex.d.ts.map +1 -0
  232. package/dist/vex.js +87 -0
  233. package/dist/vex.js.map +1 -0
  234. package/dist/walk.d.ts +39 -4
  235. package/dist/walk.d.ts.map +1 -1
  236. package/dist/walk.js +62 -10
  237. package/dist/walk.js.map +1 -1
  238. package/package.json +1 -1
  239. package/src/cbom-merge.ts +38 -8
  240. package/src/cbom.ts +153 -39
  241. package/src/comments.ts +25 -6
  242. package/src/crypto-agility.ts +407 -0
  243. package/src/dependencies.ts +182 -16
  244. package/src/detect-utils.ts +133 -11
  245. package/src/detectors/age.ts +60 -0
  246. package/src/detectors/ansible.ts +140 -0
  247. package/src/detectors/bicep.ts +107 -0
  248. package/src/detectors/c.ts +31 -3
  249. package/src/detectors/cicd.ts +18 -5
  250. package/src/detectors/cloud-kms.ts +79 -25
  251. package/src/detectors/cloudformation.ts +45 -10
  252. package/src/detectors/codesign.ts +214 -0
  253. package/src/detectors/csharp.ts +13 -3
  254. package/src/detectors/dart.ts +237 -0
  255. package/src/detectors/database.ts +64 -11
  256. package/src/detectors/dkim.ts +172 -0
  257. package/src/detectors/dnssec.ts +19 -10
  258. package/src/detectors/elixir.ts +33 -2
  259. package/src/detectors/java.ts +37 -3
  260. package/src/detectors/jose.ts +19 -3
  261. package/src/detectors/jwk.ts +59 -17
  262. package/src/detectors/k8s.ts +5 -2
  263. package/src/detectors/keystore.ts +109 -0
  264. package/src/detectors/mesh.ts +21 -45
  265. package/src/detectors/messaging.ts +22 -14
  266. package/src/detectors/objc.ts +209 -0
  267. package/src/detectors/openpgp.ts +194 -0
  268. package/src/detectors/pem.ts +30 -0
  269. package/src/detectors/php.ts +9 -4
  270. package/src/detectors/pkcs11.ts +113 -0
  271. package/src/detectors/proxy.ts +236 -0
  272. package/src/detectors/pulumi.ts +106 -0
  273. package/src/detectors/rust.ts +5 -0
  274. package/src/detectors/secrets.ts +15 -26
  275. package/src/detectors/solidity.ts +176 -0
  276. package/src/detectors/source.ts +110 -5
  277. package/src/detectors/spire.ts +143 -0
  278. package/src/detectors/ssh-ca.ts +215 -0
  279. package/src/detectors/supply-chain.ts +117 -0
  280. package/src/detectors/swift.ts +149 -0
  281. package/src/detectors/terraform.ts +42 -8
  282. package/src/detectors/vault.ts +120 -0
  283. package/src/detectors/vpn.ts +20 -48
  284. package/src/detectors/weak-hash.ts +217 -0
  285. package/src/detectors/webauthn.ts +205 -0
  286. package/src/detectors/xmldsig.ts +115 -0
  287. package/src/evidence.ts +127 -4
  288. package/src/hndl.ts +1012 -0
  289. package/src/index.ts +93 -13
  290. package/src/inventory.ts +15 -7
  291. package/src/parallel.ts +21 -20
  292. package/src/registry.ts +51 -4
  293. package/src/remediate-pipeline.ts +1 -1
  294. package/src/remediation.ts +39 -0
  295. package/src/report.ts +120 -20
  296. package/src/scan-worker.ts +5 -3
  297. package/src/scan.ts +41 -6
  298. package/src/standards-profiles.ts +136 -0
  299. package/src/types.ts +18 -5
  300. package/src/version.ts +1 -1
  301. package/src/vex.ts +122 -0
  302. package/src/walk.ts +80 -12
@@ -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
- const RE_JAVA_TLS_LEGACY = /\bSSLContext\s*\.\s*getInstance\s*\(\s*"(SSL|SSLv3|TLSv1)"/g;
56
- const RE_JAVA_TLS_NOVERIFY = /\b(NoopHostnameVerifier|ALLOW_ALL_HOSTNAME_VERIFIER)\b/g;
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(
@@ -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 { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
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
- appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
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
  );
@@ -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
- * Keying EC/OKP off `crv` (present in every EC/OKP JWK) rather than `kty` avoids
14
- * double-counting a single key, and `"kty"`/`"crv"` are specific enough to RFC
15
- * 7517 that the false-positive risk on ordinary JSON is low.
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
- * HNDL: RSA and (EC)DH / X25519 / X448 key agreement are harvest-now-decrypt-later
18
- * exposed (hndl:true); EdDSA signing keys are hndl:false but forgeable. A JWKS is
19
- * usually PUBLIC keys, so severity is medium — the exposure is a classical key
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 { eachMatch, findingFromRule } from "../detect-utils.js";
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: () => true,
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
- findings.push(
117
- findingFromRule(rule.meta, {
118
- file,
119
- content,
120
- index: m.index,
121
- matchLength: m[0].length,
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;
@@ -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, content, (m) =>
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
+ };
@@ -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
- * - Istio `DestinationRule` / `Gateway` `tls.cipherSuites:` listing a classical
22
- * `ECDHE-RSA-*` / `ECDHE-ECDSA-*` suite — the (EC)DHE key exchange in these
23
- * suites is classical and harvest-now-decrypt-later exposed (tls, ECDH,
24
- * hndl:true). This complements, and does not duplicate, `k8s.ts`'s
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, CWE_RISKY_PRIMITIVE } from "../cwe.js";
27
+ import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
28
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
30
29
 
31
- const MESH_EXTENSIONS: readonly string[] = [".yaml", ".yml", ".hcl"];
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, Consul Connect's mesh CA private key type,
117
- * and classical ECDHE cipher suites in Istio DestinationRule/Gateway TLS
118
- * policy.
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
- const isIstioCipher = content.includes("DestinationRule") || content.includes("cipherSuites");
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, content, (m) =>
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 classical `ECDHE_RSA` / `ECDHE_ECDSA` /
11
- * `TLS_RSA` suite the harvestable key-exchange path.
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]*\bTLSv1(?:\.1)?(?![.\d])/gi,
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; its classical key exchange is weak and harvestable.",
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; its classical key exchange is weak and harvestable.",
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
- re: /\bssl\.cipher\.suites\s*=\s*[^\n]*(?:ECDHE_RSA|ECDHE_ECDSA|TLS_RSA|_DHE_RSA)/g,
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-classical-cipher",
63
- title: "Broker classical (EC)DHE cipher suite",
64
- description: "Kafka ssl.cipher.suites names a classical ECDHE/DHE/RSA suite",
65
- category: "tls",
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 classical (EC)DHE/RSA cipher suite; the key exchange is harvest-now-decrypt-later exposed.",
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, content, (m) => {
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
  );