@quantakrypto/core 0.4.2 → 0.4.4

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 (223) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -10
  3. package/dist/agent-types.js.map +1 -1
  4. package/dist/baseline.js.map +1 -1
  5. package/dist/cache.js.map +1 -1
  6. package/dist/cbom-merge.d.ts +9 -0
  7. package/dist/cbom-merge.d.ts.map +1 -0
  8. package/dist/cbom-merge.js +99 -0
  9. package/dist/cbom-merge.js.map +1 -0
  10. package/dist/cbom.js.map +1 -1
  11. package/dist/changed.js.map +1 -1
  12. package/dist/codemods/config-toggle.js.map +1 -1
  13. package/dist/codemods/registry.js.map +1 -1
  14. package/dist/comments.d.ts +29 -2
  15. package/dist/comments.d.ts.map +1 -1
  16. package/dist/comments.js +178 -18
  17. package/dist/comments.js.map +1 -1
  18. package/dist/config.js.map +1 -1
  19. package/dist/cwe.js.map +1 -1
  20. package/dist/dependencies.d.ts +3 -2
  21. package/dist/dependencies.d.ts.map +1 -1
  22. package/dist/dependencies.js +138 -2
  23. package/dist/dependencies.js.map +1 -1
  24. package/dist/detect-utils.d.ts +19 -4
  25. package/dist/detect-utils.d.ts.map +1 -1
  26. package/dist/detect-utils.js +43 -6
  27. package/dist/detect-utils.js.map +1 -1
  28. package/dist/detectors/c.d.ts +4 -2
  29. package/dist/detectors/c.d.ts.map +1 -1
  30. package/dist/detectors/c.js +382 -3
  31. package/dist/detectors/c.js.map +1 -1
  32. package/dist/detectors/cicd.d.ts +27 -0
  33. package/dist/detectors/cicd.d.ts.map +1 -0
  34. package/dist/detectors/cicd.js +116 -0
  35. package/dist/detectors/cicd.js.map +1 -0
  36. package/dist/detectors/cloud-kms.d.ts +21 -0
  37. package/dist/detectors/cloud-kms.d.ts.map +1 -0
  38. package/dist/detectors/cloud-kms.js +57 -0
  39. package/dist/detectors/cloud-kms.js.map +1 -0
  40. package/dist/detectors/cloudformation.d.ts +30 -0
  41. package/dist/detectors/cloudformation.d.ts.map +1 -0
  42. package/dist/detectors/cloudformation.js +170 -0
  43. package/dist/detectors/cloudformation.js.map +1 -0
  44. package/dist/detectors/csharp.d.ts.map +1 -1
  45. package/dist/detectors/csharp.js +141 -3
  46. package/dist/detectors/csharp.js.map +1 -1
  47. package/dist/detectors/database.d.ts +17 -0
  48. package/dist/detectors/database.d.ts.map +1 -0
  49. package/dist/detectors/database.js +60 -0
  50. package/dist/detectors/database.js.map +1 -0
  51. package/dist/detectors/dnssec.d.ts +55 -0
  52. package/dist/detectors/dnssec.d.ts.map +1 -0
  53. package/dist/detectors/dnssec.js +107 -0
  54. package/dist/detectors/dnssec.js.map +1 -0
  55. package/dist/detectors/elixir.d.ts +20 -0
  56. package/dist/detectors/elixir.d.ts.map +1 -0
  57. package/dist/detectors/elixir.js +195 -0
  58. package/dist/detectors/elixir.js.map +1 -0
  59. package/dist/detectors/go.d.ts +5 -3
  60. package/dist/detectors/go.d.ts.map +1 -1
  61. package/dist/detectors/go.js +187 -3
  62. package/dist/detectors/go.js.map +1 -1
  63. package/dist/detectors/java.d.ts.map +1 -1
  64. package/dist/detectors/java.js +169 -4
  65. package/dist/detectors/java.js.map +1 -1
  66. package/dist/detectors/jose.d.ts +20 -0
  67. package/dist/detectors/jose.d.ts.map +1 -0
  68. package/dist/detectors/jose.js +62 -0
  69. package/dist/detectors/jose.js.map +1 -0
  70. package/dist/detectors/jwk.d.ts +25 -0
  71. package/dist/detectors/jwk.d.ts.map +1 -0
  72. package/dist/detectors/jwk.js +95 -0
  73. package/dist/detectors/jwk.js.map +1 -0
  74. package/dist/detectors/k8s.d.ts +17 -0
  75. package/dist/detectors/k8s.d.ts.map +1 -0
  76. package/dist/detectors/k8s.js +85 -0
  77. package/dist/detectors/k8s.js.map +1 -0
  78. package/dist/detectors/mesh.d.ts +35 -0
  79. package/dist/detectors/mesh.d.ts.map +1 -0
  80. package/dist/detectors/mesh.js +109 -0
  81. package/dist/detectors/mesh.js.map +1 -0
  82. package/dist/detectors/messaging.d.ts +16 -0
  83. package/dist/detectors/messaging.d.ts.map +1 -0
  84. package/dist/detectors/messaging.js +72 -0
  85. package/dist/detectors/messaging.js.map +1 -0
  86. package/dist/detectors/pem.d.ts.map +1 -1
  87. package/dist/detectors/pem.js +50 -2
  88. package/dist/detectors/pem.js.map +1 -1
  89. package/dist/detectors/php.d.ts +24 -0
  90. package/dist/detectors/php.d.ts.map +1 -0
  91. package/dist/detectors/php.js +167 -0
  92. package/dist/detectors/php.js.map +1 -0
  93. package/dist/detectors/python.d.ts.map +1 -1
  94. package/dist/detectors/python.js +144 -3
  95. package/dist/detectors/python.js.map +1 -1
  96. package/dist/detectors/ruby.d.ts +9 -5
  97. package/dist/detectors/ruby.d.ts.map +1 -1
  98. package/dist/detectors/ruby.js +122 -2
  99. package/dist/detectors/ruby.js.map +1 -1
  100. package/dist/detectors/rust.d.ts +8 -3
  101. package/dist/detectors/rust.d.ts.map +1 -1
  102. package/dist/detectors/rust.js +253 -3
  103. package/dist/detectors/rust.js.map +1 -1
  104. package/dist/detectors/secrets.d.ts +21 -0
  105. package/dist/detectors/secrets.d.ts.map +1 -0
  106. package/dist/detectors/secrets.js +79 -0
  107. package/dist/detectors/secrets.js.map +1 -0
  108. package/dist/detectors/source.d.ts.map +1 -1
  109. package/dist/detectors/source.js +305 -52
  110. package/dist/detectors/source.js.map +1 -1
  111. package/dist/detectors/stateful-hbs.d.ts +22 -0
  112. package/dist/detectors/stateful-hbs.d.ts.map +1 -0
  113. package/dist/detectors/stateful-hbs.js +142 -0
  114. package/dist/detectors/stateful-hbs.js.map +1 -0
  115. package/dist/detectors/terraform.d.ts +21 -0
  116. package/dist/detectors/terraform.d.ts.map +1 -0
  117. package/dist/detectors/terraform.js +123 -0
  118. package/dist/detectors/terraform.js.map +1 -0
  119. package/dist/detectors/vpn.d.ts +22 -0
  120. package/dist/detectors/vpn.d.ts.map +1 -0
  121. package/dist/detectors/vpn.js +99 -0
  122. package/dist/detectors/vpn.js.map +1 -0
  123. package/dist/errors.js.map +1 -1
  124. package/dist/evidence.d.ts +53 -0
  125. package/dist/evidence.d.ts.map +1 -0
  126. package/dist/evidence.js +81 -0
  127. package/dist/evidence.js.map +1 -0
  128. package/dist/index.d.ts +9 -2
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +10 -2
  131. package/dist/index.js.map +1 -1
  132. package/dist/inventory.d.ts +2 -0
  133. package/dist/inventory.d.ts.map +1 -1
  134. package/dist/inventory.js +27 -1
  135. package/dist/inventory.js.map +1 -1
  136. package/dist/parallel.js.map +1 -1
  137. package/dist/patch-policy.js.map +1 -1
  138. package/dist/policy.d.ts +67 -0
  139. package/dist/policy.d.ts.map +1 -0
  140. package/dist/policy.js +115 -0
  141. package/dist/policy.js.map +1 -0
  142. package/dist/redact.d.ts.map +1 -1
  143. package/dist/redact.js +39 -0
  144. package/dist/redact.js.map +1 -1
  145. package/dist/registry.d.ts +2 -2
  146. package/dist/registry.d.ts.map +1 -1
  147. package/dist/registry.js +34 -2
  148. package/dist/registry.js.map +1 -1
  149. package/dist/remediate-pipeline.d.ts +3 -0
  150. package/dist/remediate-pipeline.d.ts.map +1 -1
  151. package/dist/remediate-pipeline.js +71 -0
  152. package/dist/remediate-pipeline.js.map +1 -1
  153. package/dist/remediate-request.js.map +1 -1
  154. package/dist/remediation.d.ts +9 -3
  155. package/dist/remediation.d.ts.map +1 -1
  156. package/dist/remediation.js +17 -5
  157. package/dist/remediation.js.map +1 -1
  158. package/dist/report.d.ts +9 -0
  159. package/dist/report.d.ts.map +1 -1
  160. package/dist/report.js +38 -0
  161. package/dist/report.js.map +1 -1
  162. package/dist/scan-worker.js.map +1 -1
  163. package/dist/scan.d.ts.map +1 -1
  164. package/dist/scan.js +8 -1
  165. package/dist/scan.js.map +1 -1
  166. package/dist/severity.js.map +1 -1
  167. package/dist/standards.d.ts +96 -0
  168. package/dist/standards.d.ts.map +1 -0
  169. package/dist/standards.js +107 -0
  170. package/dist/standards.js.map +1 -0
  171. package/dist/triage.js.map +1 -1
  172. package/dist/types.d.ts +2 -2
  173. package/dist/types.d.ts.map +1 -1
  174. package/dist/types.js.map +1 -1
  175. package/dist/verify.js.map +1 -1
  176. package/dist/version.d.ts +1 -1
  177. package/dist/version.js +1 -1
  178. package/dist/version.js.map +1 -1
  179. package/dist/walk.js.map +1 -1
  180. package/dist/worktree.js.map +1 -1
  181. package/package.json +1 -1
  182. package/src/cbom-merge.ts +116 -0
  183. package/src/comments.ts +183 -18
  184. package/src/dependencies.ts +144 -4
  185. package/src/detect-utils.ts +47 -6
  186. package/src/detectors/c.ts +411 -5
  187. package/src/detectors/cicd.ts +158 -0
  188. package/src/detectors/cloud-kms.ts +87 -0
  189. package/src/detectors/cloudformation.ts +215 -0
  190. package/src/detectors/csharp.ts +154 -3
  191. package/src/detectors/database.ts +88 -0
  192. package/src/detectors/dnssec.ts +186 -0
  193. package/src/detectors/elixir.ts +237 -0
  194. package/src/detectors/go.ts +209 -6
  195. package/src/detectors/java.ts +191 -4
  196. package/src/detectors/jose.ts +94 -0
  197. package/src/detectors/jwk.ts +128 -0
  198. package/src/detectors/k8s.ts +112 -0
  199. package/src/detectors/mesh.ts +154 -0
  200. package/src/detectors/messaging.ts +98 -0
  201. package/src/detectors/pem.ts +54 -2
  202. package/src/detectors/php.ts +225 -0
  203. package/src/detectors/python.ts +153 -3
  204. package/src/detectors/ruby.ts +138 -7
  205. package/src/detectors/rust.ts +282 -7
  206. package/src/detectors/secrets.ts +114 -0
  207. package/src/detectors/source.ts +382 -72
  208. package/src/detectors/stateful-hbs.ts +180 -0
  209. package/src/detectors/terraform.ts +154 -0
  210. package/src/detectors/vpn.ts +131 -0
  211. package/src/evidence.ts +135 -0
  212. package/src/index.ts +15 -1
  213. package/src/inventory.ts +31 -1
  214. package/src/policy.ts +180 -0
  215. package/src/redact.ts +41 -0
  216. package/src/registry.ts +34 -2
  217. package/src/remediate-pipeline.ts +78 -0
  218. package/src/remediation.ts +23 -8
  219. package/src/report.ts +48 -2
  220. package/src/scan.ts +13 -1
  221. package/src/standards.ts +186 -0
  222. package/src/types.ts +3 -1
  223. package/src/version.ts +1 -1
@@ -6,11 +6,13 @@
6
6
  * `ecdsa.SignASN1`, `ecdh.X25519`, …) are precise, low-false-positive signals.
7
7
  *
8
8
  * Covered:
9
- * - crypto/rsa — GenerateKey, EncryptOAEP/PKCS1v15, SignPKCS1v15/PSS
10
- * - crypto/ecdsa — GenerateKey, Sign/SignASN1
9
+ * - crypto/rsa — GenerateKey, EncryptOAEP/PKCS1v15, SignPKCS1v15/PSS, DecryptOAEP, VerifyPKCS1v15/PSS
10
+ * - crypto/ecdsa — GenerateKey, Sign/SignASN1, Verify/VerifyASN1
11
11
  * - crypto/ecdh — P256/P384/P521/X25519 curve construction (key agreement)
12
- * - crypto/ed25519 — GenerateKey, Sign
12
+ * - crypto/elliptic — GenerateKey / ScalarMult (classic pre-1.20 ECDH)
13
+ * - crypto/ed25519 — GenerateKey, Sign, Verify
13
14
  * - crypto/dsa — GenerateKey, GenerateParameters (deprecated)
15
+ * - crypto/tls — InsecureSkipVerify, legacy MinVersion (transport config hygiene)
14
16
  *
15
17
  * HNDL policy mirrors the other detectors: KEM / key agreement (RSA encryption,
16
18
  * ECDH) is harvest-now-decrypt-later exposed (hndl:true); signatures (RSA-sign,
@@ -21,7 +23,7 @@
21
23
  */
22
24
  import type { Detector, Finding, RuleMeta } from "../types.js";
23
25
  import { GO_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
24
- import { CWE_BROKEN_CRYPTO } from "../cwe.js";
26
+ import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cwe.js";
25
27
 
26
28
  /* -------------------------------------------------------------------------- */
27
29
  /* Precompiled regexes (module scope) */
@@ -31,9 +33,37 @@ const RE_GO_RSA_KEYGEN = /\brsa\.GenerateKey\s*\(|\brsa\.GenerateMultiPrimeKey\s
31
33
  const RE_GO_RSA_ENCRYPT = /\brsa\.EncryptOAEP\s*\(|\brsa\.EncryptPKCS1v15\s*\(/g;
32
34
  const RE_GO_RSA_SIGN = /\brsa\.SignPKCS1v15\s*\(|\brsa\.SignPSS\s*\(/g;
33
35
  const RE_GO_ECDSA = /\becdsa\.GenerateKey\s*\(|\becdsa\.SignASN1\s*\(|\becdsa\.Sign\s*\(/g;
34
- const RE_GO_ECDH = /\becdh\.(?:P256|P384|P521|X25519)\s*\(/g;
36
+ const RE_GO_ECDH = /\becdh\.(?:P256|P384|P521)\s*\(/g;
37
+ // X25519 gets its own family (algorithm "X25519", lower severity) to match how
38
+ // every other pack classifies it — Go was the lone site reporting it as ECDH/high
39
+ // (audit F9). Still key-exchange + hndl:true.
40
+ const RE_GO_X25519 = /\becdh\.X25519\s*\(/g;
35
41
  const RE_GO_ED25519 = /\bed25519\.GenerateKey\s*\(|\bed25519\.Sign\s*\(/g;
36
42
  const RE_GO_DSA = /\bdsa\.GenerateKey\s*\(|\bdsa\.GenerateParameters\s*\(/g;
43
+ // Verify / decrypt call sites — the inverse operations the Sign/Encrypt/
44
+ // GenerateKey rules above miss today (audit F4/F6). Deliberately disjoint from
45
+ // those rules to avoid double-counting: DecryptOAEP≠EncryptOAEP, Verify*≠Sign*.
46
+ const RE_GO_RSA_DECRYPT = /\brsa\.DecryptOAEP\s*\(/g;
47
+ const RE_GO_RSA_VERIFY = /\brsa\.VerifyPKCS1v15\s*\(|\brsa\.VerifyPSS\s*\(/g;
48
+ const RE_GO_ECDSA_VERIFY = /\becdsa\.Verify(?:ASN1)?\s*\(/g;
49
+ const RE_GO_ED25519_VERIFY = /\bed25519\.Verify\s*\(/g;
50
+ // Classic pre-1.20 elliptic-curve key agreement (crypto/elliptic), superseded by
51
+ // crypto/ecdh but still widespread. GenerateKey + ScalarMult are the low-level
52
+ // ECDH building blocks the crypto/ecdh rule does not see. `.ScalarMult(` is an
53
+ // unqualified method call, so this rule carries medium confidence.
54
+ const RE_GO_ECDH_CLASSIC = /\belliptic\.GenerateKey\s*\(|\.ScalarMult\s*\(/g;
55
+ // Go TLS misconfiguration (crypto/tls). Mirrors the JS tlsDetector: disabled
56
+ // certificate verification and a legacy TLS/SSL version floor.
57
+ const RE_GO_TLS_SKIP_VERIFY = /InsecureSkipVerify:\s*true/g;
58
+ const RE_GO_TLS_LEGACY_VERSION = /MinVersion:\s*tls\.Version(?:TLS1[01]|SSL30)/g;
59
+ // Identifier-form golang-jwt signing methods (audit gap: the quoted "RS256" alg
60
+ // token is caught by the language-agnostic jwt-jose detector, but golang-jwt
61
+ // passes the alg as an EXPORTED IDENTIFIER — `jwt.SigningMethodRS256`,
62
+ // `jwt.SigningMethodES256`, `jwt.SigningMethodEdDSA` — which the string rule
63
+ // misses. RS*/PS* → RSA, ES* → ECDSA, EdDSA → EdDSA. The `\b`s keep it precise:
64
+ // HS* HMAC methods and longer look-alikes (`SigningMethodRS2560`) do not match.
65
+ const RE_GO_JWT_SIGNINGMETHOD =
66
+ /\bSigningMethod(RS|PS|ES)(?:256|384|512)\b|\bSigningMethodEdDSA\b/g;
37
67
 
38
68
  /* -------------------------------------------------------------------------- */
39
69
  /* Rule catalog */
@@ -93,7 +123,7 @@ const RULE_GO_ECDSA: RuleMeta = {
93
123
  const RULE_GO_ECDH: RuleMeta = {
94
124
  id: "go-ecdh",
95
125
  title: "Go ECDH key exchange",
96
- description: "crypto/ecdh P256/P384/P521/X25519 key agreement",
126
+ description: "crypto/ecdh P256/P384/P521 key agreement",
97
127
  category: "key-exchange",
98
128
  severity: "high",
99
129
  confidence: "high",
@@ -103,6 +133,19 @@ const RULE_GO_ECDH: RuleMeta = {
103
133
  message:
104
134
  "Elliptic-curve Diffie-Hellman (Go crypto/ecdh) is broken by Shor's algorithm (harvest-now-decrypt-later).",
105
135
  };
136
+ const RULE_GO_X25519: RuleMeta = {
137
+ id: "go-x25519",
138
+ title: "Go X25519 key exchange",
139
+ description: "crypto/ecdh X25519 key agreement",
140
+ category: "key-exchange",
141
+ severity: "medium",
142
+ confidence: "high",
143
+ algorithm: "X25519",
144
+ hndl: true,
145
+ cwe: CWE_BROKEN_CRYPTO,
146
+ message:
147
+ "X25519 (Go crypto/ecdh) is modern but still classical key agreement — harvest-now-decrypt-later.",
148
+ };
106
149
  const RULE_GO_ED25519: RuleMeta = {
107
150
  id: "go-ed25519",
108
151
  title: "Go Ed25519 signature",
@@ -128,6 +171,132 @@ const RULE_GO_DSA: RuleMeta = {
128
171
  message: "Classical DSA (Go) is deprecated and forgeable by a quantum attacker.",
129
172
  remediation: "Rotate off DSA and migrate to ML-DSA-65 (FIPS 204).",
130
173
  };
174
+ const RULE_GO_RSA_DECRYPT: RuleMeta = {
175
+ id: "go-rsa-decrypt",
176
+ title: "Go RSA public-key decryption",
177
+ description: "crypto/rsa DecryptOAEP",
178
+ category: "kem",
179
+ severity: "high",
180
+ confidence: "high",
181
+ algorithm: "RSA",
182
+ hndl: true,
183
+ cwe: CWE_BROKEN_CRYPTO,
184
+ message:
185
+ "RSA public-key decryption (Go) recovers data protected by a classical KEM — harvest-now-decrypt-later exposed.",
186
+ };
187
+ const RULE_GO_RSA_VERIFY: RuleMeta = {
188
+ id: "go-rsa-verify",
189
+ title: "Go RSA signature verification",
190
+ description: "crypto/rsa VerifyPKCS1v15 / VerifyPSS",
191
+ category: "signature",
192
+ severity: "high",
193
+ confidence: "high",
194
+ algorithm: "RSA",
195
+ hndl: false,
196
+ cwe: CWE_BROKEN_CRYPTO,
197
+ message: "Verifies classical RSA signatures (Go), which are forgeable by a quantum attacker.",
198
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
199
+ };
200
+ const RULE_GO_ECDSA_VERIFY: RuleMeta = {
201
+ id: "go-ecdsa-verify",
202
+ title: "Go ECDSA signature verification",
203
+ description: "crypto/ecdsa Verify / VerifyASN1",
204
+ category: "signature",
205
+ severity: "high",
206
+ confidence: "high",
207
+ algorithm: "ECDSA",
208
+ hndl: false,
209
+ cwe: CWE_BROKEN_CRYPTO,
210
+ message: "Verifies classical ECDSA signatures (Go), which are forgeable by a quantum attacker.",
211
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205)",
212
+ };
213
+ const RULE_GO_ED25519_VERIFY: RuleMeta = {
214
+ id: "go-ed25519-verify",
215
+ title: "Go Ed25519 signature verification",
216
+ description: "crypto/ed25519 Verify",
217
+ category: "signature",
218
+ severity: "low",
219
+ confidence: "high",
220
+ algorithm: "EdDSA",
221
+ hndl: false,
222
+ cwe: CWE_BROKEN_CRYPTO,
223
+ message: "Verifies Ed25519 signatures (Go) — modern but still classical and quantum-forgeable.",
224
+ };
225
+ const RULE_GO_ECDH_CLASSIC: RuleMeta = {
226
+ id: "go-ecdh-classic",
227
+ title: "Go classic EC key agreement (crypto/elliptic)",
228
+ description: "crypto/elliptic GenerateKey / ScalarMult (pre-1.20 ECDH)",
229
+ category: "key-exchange",
230
+ severity: "high",
231
+ confidence: "medium",
232
+ algorithm: "ECDH",
233
+ hndl: true,
234
+ cwe: CWE_BROKEN_CRYPTO,
235
+ message:
236
+ "Low-level elliptic-curve key agreement (Go crypto/elliptic) is broken by Shor's algorithm (harvest-now-decrypt-later).",
237
+ };
238
+ const RULE_GO_TLS_SKIP_VERIFY: RuleMeta = {
239
+ id: "go-tls-insecure-skip-verify",
240
+ title: "Go TLS certificate verification disabled",
241
+ description: "crypto/tls Config InsecureSkipVerify: true",
242
+ category: "tls",
243
+ severity: "high",
244
+ confidence: "high",
245
+ hndl: false,
246
+ cwe: CWE_CERT_VALIDATION,
247
+ message: "InsecureSkipVerify:true disables TLS certificate verification (Go) — MITM risk.",
248
+ remediation: "Remove InsecureSkipVerify:true; verify certificates properly.",
249
+ };
250
+ const RULE_GO_TLS_LEGACY_VERSION: RuleMeta = {
251
+ id: "go-tls-legacy-version",
252
+ title: "Go legacy TLS version pinned",
253
+ description: "crypto/tls MinVersion pinned to TLS 1.0/1.1 or SSL 3.0",
254
+ category: "tls",
255
+ severity: "medium",
256
+ confidence: "high",
257
+ hndl: false,
258
+ cwe: CWE_WEAK_STRENGTH,
259
+ message:
260
+ "MinVersion pins a deprecated TLS/SSL floor (TLS 1.0/1.1 or SSL 3.0) in Go; require TLS 1.3.",
261
+ remediation: "Set MinVersion: tls.VersionTLS13 and prefer PQC-hybrid key exchange.",
262
+ };
263
+ const RULE_GO_JWT_SIGNINGMETHOD: RuleMeta = {
264
+ id: "go-jwt-signingmethod",
265
+ title: "Go identifier-form JWT signing method",
266
+ description: "golang-jwt SigningMethodRS/PS/ES* / SigningMethodEdDSA identifier",
267
+ category: "signature",
268
+ severity: "high",
269
+ confidence: "high",
270
+ algorithm: "unknown", // representative umbrella; refined per-match (RSA/ECDSA/EdDSA)
271
+ hndl: false,
272
+ cwe: CWE_BROKEN_CRYPTO,
273
+ message:
274
+ "A classical JWT signature algorithm (Go golang-jwt, identifier form) is used, forgeable by a quantum attacker.",
275
+ remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
276
+ };
277
+
278
+ // The x509 PEM/DER key + certificate layer: parsing/marshalling classical RSA,
279
+ // EC and PKIX key material and X.509 certificates. This is the "handles classical
280
+ // keys" surface the Sign/Verify/GenerateKey rules miss — a key-loading utility
281
+ // (`x509.ParsePKCS8PrivateKey`) touches quantum-vulnerable material without ever
282
+ // naming rsa/ecdsa. Family is ambiguous at the parse site, so `unknown`.
283
+ const RE_GO_X509_PARSE =
284
+ /\bx509\.(?:ParsePKCS1PrivateKey|ParsePKCS8PrivateKey|ParsePKIXPublicKey|ParseECPrivateKey|ParsePKCS1PublicKey|MarshalPKCS1PrivateKey|MarshalPKCS8PrivateKey|MarshalPKIXPublicKey|MarshalECPrivateKey|ParseCertificates?|ParseCertificateRequest|CreateCertificate|CreateCertificateRequest)\b/g;
285
+ const RULE_GO_X509_PARSE: RuleMeta = {
286
+ id: "go-x509-parse",
287
+ title: "Go x509 classical key/certificate handling",
288
+ description: "x509.Parse*/Marshal*/Create* for RSA/EC/PKIX keys and X.509 certificates",
289
+ category: "certificate",
290
+ severity: "medium",
291
+ confidence: "medium",
292
+ algorithm: "unknown",
293
+ hndl: false,
294
+ cwe: CWE_BROKEN_CRYPTO,
295
+ message:
296
+ "This code parses/marshals classical asymmetric key or X.509 certificate material (x509.*), a quantum forgery/harvest surface.",
297
+ remediation:
298
+ "Inventory the key/cert types handled here; plan PQC (ML-DSA) certificate + key migration.",
299
+ };
131
300
 
132
301
  /** Detects classical asymmetric crypto in Go source (crypto/* standard library). */
133
302
  export const goDetector: Detector = {
@@ -141,8 +310,18 @@ export const goDetector: Detector = {
141
310
  RULE_GO_RSA_SIGN,
142
311
  RULE_GO_ECDSA,
143
312
  RULE_GO_ECDH,
313
+ RULE_GO_X25519,
144
314
  RULE_GO_ED25519,
145
315
  RULE_GO_DSA,
316
+ RULE_GO_RSA_DECRYPT,
317
+ RULE_GO_RSA_VERIFY,
318
+ RULE_GO_ECDSA_VERIFY,
319
+ RULE_GO_ED25519_VERIFY,
320
+ RULE_GO_ECDH_CLASSIC,
321
+ RULE_GO_TLS_SKIP_VERIFY,
322
+ RULE_GO_TLS_LEGACY_VERSION,
323
+ RULE_GO_JWT_SIGNINGMETHOD,
324
+ RULE_GO_X509_PARSE,
146
325
  ],
147
326
  appliesTo: (f) => hasExtension(f, GO_EXTENSIONS),
148
327
  detect({ file, content }): Finding[] {
@@ -159,8 +338,32 @@ export const goDetector: Detector = {
159
338
  add(RE_GO_RSA_SIGN, RULE_GO_RSA_SIGN);
160
339
  add(RE_GO_ECDSA, RULE_GO_ECDSA);
161
340
  add(RE_GO_ECDH, RULE_GO_ECDH);
341
+ add(RE_GO_X25519, RULE_GO_X25519);
162
342
  add(RE_GO_ED25519, RULE_GO_ED25519);
163
343
  add(RE_GO_DSA, RULE_GO_DSA);
344
+ add(RE_GO_RSA_DECRYPT, RULE_GO_RSA_DECRYPT);
345
+ add(RE_GO_RSA_VERIFY, RULE_GO_RSA_VERIFY);
346
+ add(RE_GO_ECDSA_VERIFY, RULE_GO_ECDSA_VERIFY);
347
+ add(RE_GO_ED25519_VERIFY, RULE_GO_ED25519_VERIFY);
348
+ add(RE_GO_ECDH_CLASSIC, RULE_GO_ECDH_CLASSIC);
349
+ add(RE_GO_TLS_SKIP_VERIFY, RULE_GO_TLS_SKIP_VERIFY);
350
+ add(RE_GO_TLS_LEGACY_VERSION, RULE_GO_TLS_LEGACY_VERSION);
351
+ add(RE_GO_X509_PARSE, RULE_GO_X509_PARSE);
352
+
353
+ // Identifier-form golang-jwt signing methods (jwt.SigningMethodRS256, …).
354
+ // Refine the umbrella "unknown" algorithm per match: m[1] is the RS/PS/ES
355
+ // prefix (RS/PS → RSA, ES → ECDSA); when it is undefined the EdDSA branch
356
+ // matched, so the family is EdDSA.
357
+ eachMatch(RE_GO_JWT_SIGNINGMETHOD, content, (m) => {
358
+ const algorithm: Finding["algorithm"] = m[1] === "ES" ? "ECDSA" : m[1] ? "RSA" : "EdDSA";
359
+ findings.push(
360
+ findingFromRule(
361
+ RULE_GO_JWT_SIGNINGMETHOD,
362
+ { file, content, index: m.index, matchLength: m[0].length },
363
+ { algorithm },
364
+ ),
365
+ );
366
+ });
164
367
 
165
368
  return findings;
166
369
  },
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import type { Detector, Finding, RuleMeta } from "../types.js";
23
23
  import { JAVA_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
24
- import { CWE_BROKEN_CRYPTO } from "../cwe.js";
24
+ import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION, CWE_WEAK_STRENGTH } from "../cwe.js";
25
25
 
26
26
  /* -------------------------------------------------------------------------- */
27
27
  /* Regexes */
@@ -35,7 +35,34 @@ 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)\s*\(/g;
38
+ /\bnew\s+(RSAKeyPairGenerator|DSAKeyPairGenerator|ECKeyPairGenerator|ECDSASigner|Ed25519Signer|Ed448Signer|X25519Agreement|X448Agreement|ECDHBasicAgreement|DHBasicAgreement|X25519KeyPairGenerator|Ed25519KeyPairGenerator|RSAEngine|OAEPEncoding)\s*\(/g;
39
+
40
+ // BouncyCastle lightweight-API curve/EdDSA classes as a BARE constructor call —
41
+ // the Kotlin form (`X448KeyPairGenerator()`, no `new` keyword). RE_JAVA_BC above
42
+ // only matches the Java `new <Class>(` form, so Kotlin sources that drive the
43
+ // lightweight API directly are missed. The negative lookbehind `(?<!\bnew\s+)`
44
+ // keeps this additive: `new X448Agreement(` stays with RE_JAVA_BC (java-xdh),
45
+ // while bare `X448Agreement(` routes to the family-specific rules below. Scoped
46
+ // to the exact X448/X25519/Ed448/Ed25519 class names to stay precise.
47
+ const RE_JAVA_BC_CURVE =
48
+ /(?<!\bnew\s+)\b(X448KeyPairGenerator|X448Agreement|X448PrivateKeyParameters|X25519KeyPairGenerator|X25519Agreement|Ed448KeyPairGenerator|Ed448Signer|Ed25519KeyPairGenerator|Ed25519Signer)\s*\(/g;
49
+
50
+ // Insecure JSSE / TLS configuration expressed in Java source. Mirrors the JS
51
+ // tlsDetector in source.ts (category "tls"): a legacy SSL/TLS protocol version
52
+ // requested from SSLContext, and TLS hostname verification neutered via an
53
+ // all-trusting verifier (Apache HttpClient's NoopHostnameVerifier /
54
+ // 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;
57
+
58
+ // Identifier-form JWT/JOSE signature algorithms (audit F7). The quoted-string
59
+ // alg token ("RS256") is caught by the language-agnostic jwt-jose detector, but
60
+ // the Java JWT libraries pass the alg as an IDENTIFIER, not a string literal:
61
+ // jjwt's `SignatureAlgorithm.RS256` and auth0 java-jwt's `Algorithm.RSA256` /
62
+ // `Algorithm.ECDSA256`. ("SignatureAlgorithm" has no word boundary before its
63
+ // "Algorithm", so the auth0 branch cannot re-match the jjwt form.)
64
+ const RE_JAVA_JWT_ALG =
65
+ /\bSignatureAlgorithm\.(?:RS|PS|ES)(?:256|384|512)\b|\bAlgorithm\.(?:RSA|ECDSA)(?:256|384|512)\b/g;
39
66
 
40
67
  /* -------------------------------------------------------------------------- */
41
68
  /* Rule catalog */
@@ -138,7 +165,7 @@ const RULE_JAVA_XDH: RuleMeta = {
138
165
  title: "Java X25519/X448 key agreement",
139
166
  description: "JCA XDH / X25519 / X448 (KeyPairGenerator / KeyAgreement)",
140
167
  category: "key-exchange",
141
- severity: "low",
168
+ severity: "medium",
142
169
  confidence: "high",
143
170
  algorithm: "X25519",
144
171
  hndl: true,
@@ -158,6 +185,87 @@ const RULE_JAVA_EDDSA: RuleMeta = {
158
185
  cwe: CWE_BROKEN_CRYPTO,
159
186
  message: "Ed25519/Ed448 (Java/JCA) is a modern but still classical signature scheme.",
160
187
  };
188
+ // BouncyCastle lightweight-API rules for bare (Kotlin-form) constructor calls.
189
+ // Split by family so X448 and X25519 report their own algorithm string (java-xdh
190
+ // lumps both under "X25519"). Severity/CWE copied from the JCA siblings above.
191
+ const RULE_JAVA_BC_X448: RuleMeta = {
192
+ id: "java-bc-x448",
193
+ title: "Java/Kotlin X448 key agreement (BouncyCastle lightweight API)",
194
+ description: "BouncyCastle X448KeyPairGenerator / X448Agreement / X448PrivateKeyParameters",
195
+ category: "key-exchange",
196
+ severity: "medium",
197
+ confidence: "high",
198
+ algorithm: "X448",
199
+ hndl: true,
200
+ cwe: CWE_BROKEN_CRYPTO,
201
+ message:
202
+ "X448 (Java/Kotlin, BouncyCastle lightweight API) is modern but still classical key agreement — harvest-now-decrypt-later.",
203
+ };
204
+ const RULE_JAVA_BC_X25519: RuleMeta = {
205
+ id: "java-bc-x25519",
206
+ title: "Java/Kotlin X25519 key agreement (BouncyCastle lightweight API)",
207
+ description: "BouncyCastle X25519KeyPairGenerator / X25519Agreement",
208
+ category: "key-exchange",
209
+ severity: "medium",
210
+ confidence: "high",
211
+ algorithm: "X25519",
212
+ hndl: true,
213
+ cwe: CWE_BROKEN_CRYPTO,
214
+ message:
215
+ "X25519 (Java/Kotlin, BouncyCastle lightweight API) is modern but still classical key agreement — harvest-now-decrypt-later.",
216
+ };
217
+ const RULE_JAVA_BC_EDDSA: RuleMeta = {
218
+ id: "java-bc-eddsa",
219
+ title: "Java/Kotlin Ed25519/Ed448 signature (BouncyCastle lightweight API)",
220
+ description:
221
+ "BouncyCastle Ed448KeyPairGenerator / Ed448Signer / Ed25519KeyPairGenerator / Ed25519Signer",
222
+ category: "signature",
223
+ severity: "low",
224
+ confidence: "high",
225
+ algorithm: "EdDSA",
226
+ hndl: false,
227
+ cwe: CWE_BROKEN_CRYPTO,
228
+ message:
229
+ "Ed25519/Ed448 (Java/Kotlin, BouncyCastle lightweight API) is a modern but still classical signature scheme.",
230
+ };
231
+ const RULE_JAVA_TLS_LEGACY: RuleMeta = {
232
+ id: "java-tls-legacy-version",
233
+ title: "Legacy SSL/TLS version requested",
234
+ description: 'SSLContext.getInstance("SSL" | "SSLv3" | "TLSv1")',
235
+ category: "tls",
236
+ severity: "medium",
237
+ confidence: "high",
238
+ hndl: false,
239
+ cwe: CWE_WEAK_STRENGTH,
240
+ message: "SSL/SSLv3/TLS 1.0 are deprecated and insecure (Java/JSSE); require TLS 1.3.",
241
+ remediation: 'Use SSLContext.getInstance("TLSv1.3") and prefer PQC-hybrid key exchange.',
242
+ };
243
+ const RULE_JAVA_TLS_NOVERIFY: RuleMeta = {
244
+ id: "java-tls-hostname-verification-disabled",
245
+ title: "TLS hostname verification disabled",
246
+ description: "NoopHostnameVerifier / ALLOW_ALL_HOSTNAME_VERIFIER",
247
+ category: "tls",
248
+ severity: "high",
249
+ confidence: "high",
250
+ hndl: false,
251
+ cwe: CWE_CERT_VALIDATION,
252
+ message: "An all-trusting hostname verifier (Java) disables TLS hostname checking (MITM risk).",
253
+ remediation: "Remove the all-trusting verifier; rely on the default hostname verifier.",
254
+ };
255
+ const RULE_JAVA_JWT_ALG: RuleMeta = {
256
+ id: "java-jwt-alg",
257
+ title: "Java identifier-form JWT/JOSE algorithm",
258
+ description: "jjwt SignatureAlgorithm.RS/PS/ES* / auth0 Algorithm.RSA*/ECDSA*",
259
+ category: "signature",
260
+ severity: "high",
261
+ confidence: "high",
262
+ algorithm: "unknown",
263
+ hndl: false,
264
+ cwe: CWE_BROKEN_CRYPTO,
265
+ message:
266
+ "A classical JWT/JOSE signature algorithm (Java, identifier form) is used, forgeable by a quantum attacker.",
267
+ remediation: "ML-DSA-65 (FIPS 204); track IETF PQC JOSE/COSE algorithms",
268
+ };
161
269
 
162
270
  /**
163
271
  * Classify a `<factory>.getInstance("<alg>")` pair into a rule, or null when the
@@ -175,6 +283,11 @@ function classifyGetInstance(factory: string, rawAlg: string): RuleMeta | null {
175
283
  if (alg.includes("ED25519") || alg.includes("ED448") || alg.includes("EDDSA"))
176
284
  return RULE_JAVA_EDDSA;
177
285
  if (alg.includes("X25519") || alg.includes("X448") || alg === "XDH") return RULE_JAVA_XDH;
286
+ // RSASSA-PSS is an RSA *signature* scheme, not encryption/KEM. The string
287
+ // contains "RSA", so it must be caught here — BEFORE the generic RSA branch
288
+ // below, which would otherwise (mis)classify it as HNDL-exposed RSA
289
+ // encryption (java-rsa / hndl:true). Signatures are hndl:false. (Fixes F8.)
290
+ if (alg.includes("PSS")) return RULE_JAVA_RSA_SIGN;
178
291
  if (alg.includes("RSA")) return isSignature ? RULE_JAVA_RSA_SIGN : RULE_JAVA_RSA;
179
292
  if (alg.includes("DSA")) return RULE_JAVA_DSA; // ECDSA already handled above
180
293
  if (alg.includes("DH") || alg.includes("DIFFIEHELLMAN")) return RULE_JAVA_DH;
@@ -191,12 +304,32 @@ const BC_CLASS_RULES: Record<string, RuleMeta> = {
191
304
  Ed448Signer: RULE_JAVA_EDDSA,
192
305
  X25519Agreement: RULE_JAVA_XDH,
193
306
  X448Agreement: RULE_JAVA_XDH,
307
+ ECDHBasicAgreement: RULE_JAVA_ECDH,
308
+ DHBasicAgreement: RULE_JAVA_DH,
309
+ X25519KeyPairGenerator: RULE_JAVA_XDH,
310
+ Ed25519KeyPairGenerator: RULE_JAVA_EDDSA,
311
+ RSAEngine: RULE_JAVA_RSA,
312
+ OAEPEncoding: RULE_JAVA_RSA,
313
+ };
314
+
315
+ /** BouncyCastle lightweight-API bare-constructor class name → family rule. */
316
+ const BC_CURVE_CLASS_RULES: Record<string, RuleMeta> = {
317
+ X448KeyPairGenerator: RULE_JAVA_BC_X448,
318
+ X448Agreement: RULE_JAVA_BC_X448,
319
+ X448PrivateKeyParameters: RULE_JAVA_BC_X448,
320
+ X25519KeyPairGenerator: RULE_JAVA_BC_X25519,
321
+ X25519Agreement: RULE_JAVA_BC_X25519,
322
+ Ed448KeyPairGenerator: RULE_JAVA_BC_EDDSA,
323
+ Ed448Signer: RULE_JAVA_BC_EDDSA,
324
+ Ed25519KeyPairGenerator: RULE_JAVA_BC_EDDSA,
325
+ Ed25519Signer: RULE_JAVA_BC_EDDSA,
194
326
  };
195
327
 
196
328
  /** Detects classical asymmetric crypto in Java / Kotlin (JCA + BouncyCastle). */
197
329
  export const javaDetector: Detector = {
198
330
  id: "java-crypto",
199
- description: "Classical asymmetric crypto in Java/Kotlin (JCA getInstance + BouncyCastle)",
331
+ description:
332
+ "Classical asymmetric crypto on the JVM — Java/Kotlin/Scala (JCA getInstance + BouncyCastle)",
200
333
  scope: "source",
201
334
  language: "java",
202
335
  rules: [
@@ -209,6 +342,12 @@ export const javaDetector: Detector = {
209
342
  RULE_JAVA_DH,
210
343
  RULE_JAVA_XDH,
211
344
  RULE_JAVA_EDDSA,
345
+ RULE_JAVA_BC_X448,
346
+ RULE_JAVA_BC_X25519,
347
+ RULE_JAVA_BC_EDDSA,
348
+ RULE_JAVA_TLS_LEGACY,
349
+ RULE_JAVA_TLS_NOVERIFY,
350
+ RULE_JAVA_JWT_ALG,
212
351
  ],
213
352
  appliesTo: (f) => hasExtension(f, JAVA_EXTENSIONS),
214
353
  detect({ file, content }): Finding[] {
@@ -230,6 +369,54 @@ export const javaDetector: Detector = {
230
369
  );
231
370
  });
232
371
 
372
+ // BouncyCastle lightweight-API classes as a bare constructor call (Kotlin
373
+ // form, no `new`). RE_JAVA_BC_CURVE's negative lookbehind excludes the
374
+ // `new <Class>(` form already handled above, so this only adds the missed
375
+ // 448/25519 Kotlin-style matches — no double-counting.
376
+ eachMatch(RE_JAVA_BC_CURVE, content, (m) => {
377
+ const rule = BC_CURVE_CLASS_RULES[m[1]];
378
+ if (!rule) return;
379
+ findings.push(
380
+ findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
381
+ );
382
+ });
383
+
384
+ // Legacy SSL/TLS protocol version requested from SSLContext.
385
+ eachMatch(RE_JAVA_TLS_LEGACY, content, (m) => {
386
+ findings.push(
387
+ findingFromRule(RULE_JAVA_TLS_LEGACY, {
388
+ file,
389
+ content,
390
+ index: m.index,
391
+ matchLength: m[0].length,
392
+ }),
393
+ );
394
+ });
395
+
396
+ // All-trusting hostname verifier — disables TLS hostname verification.
397
+ eachMatch(RE_JAVA_TLS_NOVERIFY, content, (m) => {
398
+ findings.push(
399
+ findingFromRule(RULE_JAVA_TLS_NOVERIFY, {
400
+ file,
401
+ content,
402
+ index: m.index,
403
+ matchLength: m[0].length,
404
+ }),
405
+ );
406
+ });
407
+
408
+ // Identifier-form JWT/JOSE signature algorithms (jjwt / auth0).
409
+ eachMatch(RE_JAVA_JWT_ALG, content, (m) => {
410
+ findings.push(
411
+ findingFromRule(RULE_JAVA_JWT_ALG, {
412
+ file,
413
+ content,
414
+ index: m.index,
415
+ matchLength: m[0].length,
416
+ }),
417
+ );
418
+ });
419
+
233
420
  return findings;
234
421
  },
235
422
  };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Config detector: classical KEY-MANAGEMENT (JWE) algorithms in JOSE. This is
3
+ * distinct from the JWS signature side (RS256/ES256, handled elsewhere) and from
4
+ * the JWK key-material detector: here we flag the `alg` values that wrap a
5
+ * content-encryption key with classical asymmetric crypto, because the encrypted
6
+ * payload is CONFIDENTIALITY — harvest-now-decrypt-later exposed. A JWE token
7
+ * captured today (from a log, an audit trail, a stored assertion) is decryptable
8
+ * once the wrapping key falls to a CRQC.
9
+ *
10
+ * Covered `alg` (RFC 7518 §4) values:
11
+ * - `RSA-OAEP`, `RSA-OAEP-256`, `RSA1_5` → RSA key encryption.
12
+ * - `ECDH-ES`, `ECDH-ES+A128KW`, … → classical EC Diffie-Hellman key agreement.
13
+ *
14
+ * These exact strings are JOSE identifiers, so a content-level match is precise;
15
+ * the detector applies to any file (config, JSON, source) after a fast reject.
16
+ */
17
+ import type { Detector, Finding, RuleMeta } from "../types.js";
18
+ import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
19
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
20
+
21
+ interface JoseRule {
22
+ re: RegExp;
23
+ meta: RuleMeta;
24
+ }
25
+
26
+ const JOSE_RULES: JoseRule[] = [
27
+ {
28
+ re: /"alg"\s*:\s*"RSA(?:-OAEP(?:-256)?|1_5)"/g,
29
+ meta: {
30
+ id: "jose-jwe-rsa",
31
+ title: "JWE RSA key wrapping",
32
+ description: 'JWE "alg" of RSA-OAEP / RSA-OAEP-256 / RSA1_5 (RFC 7518)',
33
+ category: "kem",
34
+ severity: "high",
35
+ confidence: "high",
36
+ algorithm: "RSA",
37
+ hndl: true,
38
+ cwe: CWE_BROKEN_CRYPTO,
39
+ message:
40
+ "JWE wraps the content-encryption key with classical RSA (RSA-OAEP/RSA1_5); the encrypted payload is harvest-now-decrypt-later exposed.",
41
+ remediation:
42
+ "Plan migration to a post-quantum KEM (ML-KEM-768) for key wrapping as JOSE/COSE PQ algorithms are standardised.",
43
+ },
44
+ },
45
+ {
46
+ re: /"alg"\s*:\s*"ECDH-ES(?:\+A\d{3}KW)?"/g,
47
+ meta: {
48
+ id: "jose-jwe-ecdh",
49
+ title: "JWE ECDH-ES key agreement",
50
+ description: 'JWE "alg" of ECDH-ES / ECDH-ES+A*KW (RFC 7518)',
51
+ category: "key-exchange",
52
+ severity: "high",
53
+ confidence: "high",
54
+ algorithm: "ECDH",
55
+ hndl: true,
56
+ cwe: CWE_BROKEN_CRYPTO,
57
+ message:
58
+ "JWE derives the content-encryption key via classical ECDH-ES; the encrypted payload is harvest-now-decrypt-later exposed.",
59
+ remediation:
60
+ "Plan migration to hybrid post-quantum key agreement (X25519MLKEM768) as JOSE PQ algorithms are standardised.",
61
+ },
62
+ },
63
+ ];
64
+
65
+ /** Detects classical JWE key-management (`alg`) values in JOSE tokens/config. */
66
+ export const joseDetector: Detector = {
67
+ id: "jose-jwe-keymgmt",
68
+ description:
69
+ "Classical JWE key-management algorithms (RSA-OAEP, ECDH-ES) — confidentiality, HNDL",
70
+ scope: "config",
71
+ language: "any",
72
+ 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),
75
+ detect({ file, content }): Finding[] {
76
+ // Fast reject: no JOSE key-management alg token present.
77
+ if (
78
+ !content.includes("RSA-OAEP") &&
79
+ !content.includes("RSA1_5") &&
80
+ !content.includes("ECDH-ES")
81
+ ) {
82
+ return [];
83
+ }
84
+ const findings: Finding[] = [];
85
+ for (const rule of JOSE_RULES) {
86
+ eachMatch(rule.re, content, (m) => {
87
+ findings.push(
88
+ findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),
89
+ );
90
+ });
91
+ }
92
+ return findings;
93
+ },
94
+ };