@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
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Config detector: classical-certificate TLS termination in standalone
3
+ * reverse-proxy / load-balancer configuration and in-code gRPC channel
4
+ * credentials.
5
+ *
6
+ * A standalone Envoy, Nginx, HAProxy, Traefik, or Caddy config that terminates
7
+ * TLS with an RSA/ECDSA leaf certificate and negotiates an ECDHE key exchange is
8
+ * the canonical harvest-now-decrypt-later (HNDL) surface: an attacker who
9
+ * records today's handshake + ciphertext can decrypt it retroactively once a
10
+ * cryptographically-relevant quantum computer (CRQC) can break the ephemeral
11
+ * (EC)DHE secret. The Kubernetes / service-mesh detectors (`k8s.ts`, `mesh.ts`)
12
+ * cover the CLUSTER-managed cert + mesh-mTLS surfaces; the STANDALONE proxy
13
+ * config FILES and the in-code gRPC credential constructors below are seen by
14
+ * neither them nor the language packs, so they are covered here.
15
+ *
16
+ * Because the config token itself does not name the cert's key algorithm (the
17
+ * key lives in a referenced PEM file, out of band), these findings use
18
+ * `algorithm: "unknown"` — the evidence is "a classical-cert TLS channel is
19
+ * configured HERE", not a specific family. They are `category: "key-exchange"`
20
+ * (the ECDHE handshake is the harvestable primitive) and `hndl: true`.
21
+ * Confidence is `medium`: a config-level indicator of a classical TLS channel,
22
+ * not a matched algorithm string.
23
+ *
24
+ * PRECISION / fast-reject: several of the matched tokens (`ssl_certificate`,
25
+ * `private_key:`, `certificates:`) are generic enough to appear in unrelated
26
+ * YAML/config, so `detect()` bails unless the file carries a distinctive
27
+ * proxy/gRPC marker (see `hasProxyMarker`). This keeps a random YAML that merely
28
+ * has a `private_key:` key, or a Traefik-shaped `certificates:` list that isn't
29
+ * a Traefik config, from firing. Comments are masked (YAML/nginx/HCL `#`, plus
30
+ * `//` and C-style `/* … *\/` blocks) so a commented-out directive can't fire.
31
+ *
32
+ * Covered surfaces:
33
+ * - Envoy — `common_tls_context:` / `DownstreamTlsContext` /
34
+ * `UpstreamTlsContext` with `tls_certificates:` carrying a
35
+ * `certificate_chain:` + `private_key:` (and the
36
+ * `envoy.transport_sockets.tls` transport socket).
37
+ * - Nginx — `ssl_certificate` + `ssl_certificate_key` directives.
38
+ * - HAProxy — `bind … ssl crt …` lines and `crt-store` sections.
39
+ * - Traefik — a `tls:` block's `certificates:` with `certFile:` + `keyFile:`
40
+ * (dynamic config) or a `certResolver` (ACME) reference.
41
+ * - gRPC — in-code TLS channel credentials: `grpc.ssl_channel_credentials(`
42
+ * (Python), `grpc.credentials.createSsl(` (Node),
43
+ * `TlsChannelCredentials` (Java), `credentials.NewTLS(` (Go).
44
+ */
45
+ import type { Detector, Finding, RuleMeta } from "../types.js";
46
+ import {
47
+ DOC_EXTENSIONS,
48
+ eachMatch,
49
+ findingFromRule,
50
+ hasExtension,
51
+ maskBlockComments,
52
+ maskCommentLines,
53
+ } from "../detect-utils.js";
54
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
55
+
56
+ // --- Envoy: match ONLY the Envoy-DISTINCTIVE tokens (`common_tls_context:`, the
57
+ // `*TlsContext` type names, `tls_certificates:`, the `envoy.transport_sockets.tls`
58
+ // transport socket). The generic `certificate_chain:` / `private_key:` fields are
59
+ // deliberately NOT matched here — they appear in unrelated YAML and would
60
+ // cross-label a non-Envoy file as Envoy (audit L2). A distinctive token IS the
61
+ // evidence, so no separate marker gate is needed for this rule (audit L1).
62
+ const RE_ENVOY_TLS =
63
+ /\b(?:DownstreamTlsContext|UpstreamTlsContext|common_tls_context\s*:|tls_certificates\s*:|envoy\.transport_sockets\.tls)/g;
64
+
65
+ // --- Nginx: the classic `ssl_certificate` / `ssl_certificate_key` directive
66
+ // pair. `ssl_certificate_key` is matched first-class; `ssl_certificate` requires
67
+ // a following space so it doesn't also swallow `ssl_certificate_key` as a second
68
+ // hit on the same directive (kept as a single rule regardless).
69
+ const RE_NGINX_TLS = /\bssl_certificate(?:_key)?\s+\S/g;
70
+
71
+ // --- HAProxy: a `bind` line that terminates TLS (`ssl crt <path>`) or a
72
+ // `crt-store` section (HAProxy 2.8+ certificate store). `ssl` and `crt` may be
73
+ // separated by other bind args, so match them independently on a bind context.
74
+ const RE_HAPROXY_TLS =
75
+ /\bcrt-store\b|\bbind\b[^\n]*\bssl\b[^\n]*\bcrt\b|\bbind\b[^\n]*\bcrt\b[^\n]*\bssl\b/g;
76
+
77
+ // --- Traefik: dynamic-config TLS certificate entries (`certFile:` + `keyFile:`)
78
+ // or an ACME `certResolver` reference. `certFile`/`keyFile` are Traefik-specific
79
+ // enough (paired with the `certResolver`/`tls:` marker gate) to anchor on.
80
+ const RE_TRAEFIK_TLS = /\bcertResolver\b|\b(?:certFile|keyFile)\s*:/g;
81
+
82
+ // --- gRPC in-code TLS channel credentials across languages.
83
+ const RE_GRPC_TLS =
84
+ /grpc\.ssl_channel_credentials\s*\(|grpc\.credentials\.createSsl\s*\(|\bTlsChannelCredentials\b|\bcredentials\.NewTLS\s*\(/g;
85
+
86
+ const MESSAGE = (proxy: string): string =>
87
+ `${proxy} terminates TLS with a classical certificate + ECDHE key exchange; the recorded handshake is harvest-now-decrypt-later exposed — verify the cert key algorithm and plan a PQC-hybrid (X25519MLKEM768) TLS migration.`;
88
+
89
+ const REMEDIATION =
90
+ "Enable hybrid PQC key exchange (X25519MLKEM768) once the proxy/gRPC stack supports it; re-key certificates to PQC signatures (ML-DSA) when available.";
91
+
92
+ const RULE_ENVOY: RuleMeta = {
93
+ id: "proxy-envoy-tls",
94
+ title: "Envoy classical TLS termination",
95
+ description: "Envoy TLS context terminates TLS with a classical certificate + ECDHE key exchange",
96
+ category: "key-exchange",
97
+ severity: "medium",
98
+ confidence: "medium",
99
+ algorithm: "unknown",
100
+ hndl: true,
101
+ cwe: CWE_BROKEN_CRYPTO,
102
+ message: MESSAGE("Envoy"),
103
+ remediation: REMEDIATION,
104
+ };
105
+ const RULE_NGINX: RuleMeta = {
106
+ id: "proxy-nginx-tls",
107
+ title: "Nginx classical TLS termination",
108
+ description:
109
+ "Nginx ssl_certificate/ssl_certificate_key terminates TLS with a classical cert + ECDHE",
110
+ category: "key-exchange",
111
+ severity: "medium",
112
+ confidence: "medium",
113
+ algorithm: "unknown",
114
+ hndl: true,
115
+ cwe: CWE_BROKEN_CRYPTO,
116
+ message: MESSAGE("Nginx"),
117
+ remediation: REMEDIATION,
118
+ };
119
+ const RULE_HAPROXY: RuleMeta = {
120
+ id: "proxy-haproxy-tls",
121
+ title: "HAProxy classical TLS termination",
122
+ description: "HAProxy bind ssl crt / crt-store terminates TLS with a classical cert + ECDHE",
123
+ category: "key-exchange",
124
+ severity: "medium",
125
+ confidence: "medium",
126
+ algorithm: "unknown",
127
+ hndl: true,
128
+ cwe: CWE_BROKEN_CRYPTO,
129
+ message: MESSAGE("HAProxy"),
130
+ remediation: REMEDIATION,
131
+ };
132
+ const RULE_TRAEFIK: RuleMeta = {
133
+ id: "proxy-traefik-tls",
134
+ title: "Traefik classical TLS termination",
135
+ description:
136
+ "Traefik tls certificates (certFile/keyFile) or certResolver terminate TLS with a classical cert + ECDHE",
137
+ category: "key-exchange",
138
+ severity: "medium",
139
+ confidence: "medium",
140
+ algorithm: "unknown",
141
+ hndl: true,
142
+ cwe: CWE_BROKEN_CRYPTO,
143
+ message: MESSAGE("Traefik"),
144
+ remediation: REMEDIATION,
145
+ };
146
+ const RULE_GRPC: RuleMeta = {
147
+ id: "grpc-tls-credentials",
148
+ title: "gRPC classical TLS channel credentials",
149
+ description: "gRPC channel established with classical TLS credentials (RSA/ECDSA cert + ECDHE)",
150
+ category: "key-exchange",
151
+ severity: "medium",
152
+ confidence: "medium",
153
+ algorithm: "unknown",
154
+ hndl: true,
155
+ cwe: CWE_BROKEN_CRYPTO,
156
+ message: MESSAGE("gRPC"),
157
+ remediation: REMEDIATION,
158
+ };
159
+
160
+ /**
161
+ * PERF fast-reject only — a superset of substrings that appear in every rule's
162
+ * matchable input, so it can never drop a file a rule would flag. Correctness is
163
+ * in the per-rule regexes (each is distinctive enough to self-gate); this list
164
+ * exists purely to skip the regex work on files with no proxy/gRPC token at all.
165
+ */
166
+ const PROXY_FAST_REJECT: readonly string[] = [
167
+ // Envoy
168
+ "tls_context",
169
+ "TlsContext",
170
+ "tls_certificates",
171
+ "transport_sockets.tls",
172
+ // Nginx
173
+ "ssl_certificate",
174
+ // HAProxy (`crt-store` and `bind … ssl … crt` both contain "crt")
175
+ "crt",
176
+ // Traefik
177
+ "certResolver",
178
+ "certFile",
179
+ "keyFile",
180
+ // gRPC
181
+ "ssl_channel_credentials",
182
+ "createSsl",
183
+ "TlsChannelCredentials",
184
+ "credentials.NewTLS",
185
+ ];
186
+
187
+ interface ProxyRule {
188
+ meta: RuleMeta;
189
+ re: RegExp;
190
+ }
191
+
192
+ const PROXY_RULES: readonly ProxyRule[] = [
193
+ { meta: RULE_ENVOY, re: RE_ENVOY_TLS },
194
+ { meta: RULE_NGINX, re: RE_NGINX_TLS },
195
+ { meta: RULE_HAPROXY, re: RE_HAPROXY_TLS },
196
+ { meta: RULE_TRAEFIK, re: RE_TRAEFIK_TLS },
197
+ { meta: RULE_GRPC, re: RE_GRPC_TLS },
198
+ ];
199
+
200
+ /**
201
+ * Detects classical-cert TLS termination in standalone reverse-proxy /
202
+ * load-balancer config files and in-code gRPC channel credentials. See the
203
+ * module docstring for the covered surfaces and the fast-reject rationale.
204
+ */
205
+ export const proxyDetector: Detector = {
206
+ id: "proxy-tls-crypto",
207
+ description:
208
+ "Classical-cert TLS termination in reverse-proxy / load-balancer config (Envoy, Nginx, HAProxy, Traefik) and gRPC channel credentials",
209
+ scope: "config",
210
+ language: "any",
211
+ rules: PROXY_RULES.map((r) => r.meta),
212
+ // Config + source files are all in scope (gRPC creds live in code); only prose
213
+ // documentation is excluded, so a README mentioning `ssl_certificate` in a
214
+ // sentence can't fire (the marker gate already handles most of this).
215
+ appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
216
+ detect({ file, content }): Finding[] {
217
+ // Mask comments first so a directive that only appears inside a commented-out
218
+ // block cannot fire.
219
+ const scan = maskCommentLines(maskBlockComments(content), ["#", "//"]);
220
+ // Cheap fast-reject: skip files with none of the distinctive tokens. This is a
221
+ // perf gate only — each rule's regex is distinctive enough to SELF-GATE (a
222
+ // separate too-narrow marker list previously dropped canonical HAProxy/Traefik
223
+ // configs entirely — audit H1/H2), so correctness lives in the per-rule regexes.
224
+ if (!PROXY_FAST_REJECT.some((mk) => scan.includes(mk))) return [];
225
+
226
+ const findings: Finding[] = [];
227
+ for (const { meta, re } of PROXY_RULES) {
228
+ eachMatch(re, scan, (m) =>
229
+ findings.push(
230
+ findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length }),
231
+ ),
232
+ );
233
+ }
234
+ return findings;
235
+ },
236
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * IaC detector: classical asymmetric keys provisioned by Pulumi's `tls` provider
3
+ * (`@pulumi/tls` / `pulumi_tls` / `pulumi-tls`), across the languages Pulumi programs
4
+ * are written in (TS/JS, Python, Go). A `tls.PrivateKey` resource mints real key
5
+ * material at deploy time — the IaC surface, like Terraform's `tls_private_key`, but
6
+ * expressed as SDK calls in a general-purpose source file rather than HCL.
7
+ *
8
+ * Detection is gated to files that actually use the pulumi-tls provider (a
9
+ * `@pulumi/tls` / `pulumi_tls` / `pulumi-tls` import or a `tls.PrivateKey` /
10
+ * `tls.NewPrivateKey` construction), then classifies each `algorithm` value:
11
+ * - `"RSA"` → RSA key (kem, HNDL)
12
+ * - `"ECDSA"` → EC signing key (signature)
13
+ * - `"ED25519"` → Ed25519 signing key (signature)
14
+ * Comment/string suppression is handled centrally for the host languages.
15
+ */
16
+ import type { Detector, Finding, RuleMeta } from "../types.js";
17
+ import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
18
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
19
+
20
+ const PULUMI_EXTENSIONS: readonly string[] = [".ts", ".js", ".mjs", ".cjs", ".py", ".go"];
21
+
22
+ // The `algorithm` value of a pulumi-tls PrivateKey, in the TS/Python (`algorithm:
23
+ // "RSA"` / `algorithm="RSA"`) and Go (`Algorithm: pulumi.String("RSA")`) spellings.
24
+ const RE_PULUMI_TLS_ALG =
25
+ /\b[Aa]lgorithm\s*[:=]\s*(?:pulumi\.String\(\s*)?["'](RSA|ECDSA|ED25519)["']/g;
26
+
27
+ /** True when the file actually uses the pulumi-tls provider (gate against FPs). */
28
+ function usesPulumiTls(content: string): boolean {
29
+ return (
30
+ content.includes("@pulumi/tls") ||
31
+ content.includes("pulumi_tls") ||
32
+ content.includes("pulumi-tls") ||
33
+ content.includes("tls.PrivateKey") ||
34
+ content.includes("tls.NewPrivateKey")
35
+ );
36
+ }
37
+
38
+ const RULE_PULUMI_RSA: RuleMeta = {
39
+ id: "pulumi-tls-rsa",
40
+ title: "Pulumi tls.PrivateKey RSA key",
41
+ description: 'Pulumi tls.PrivateKey with algorithm "RSA"',
42
+ category: "kem",
43
+ severity: "high",
44
+ confidence: "high",
45
+ algorithm: "RSA",
46
+ hndl: true,
47
+ cwe: CWE_BROKEN_CRYPTO,
48
+ message: "Pulumi provisions a classical RSA key (tls.PrivateKey), which is not quantum-safe.",
49
+ remediation: "Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).",
50
+ };
51
+ const RULE_PULUMI_ECDSA: RuleMeta = {
52
+ id: "pulumi-tls-ecdsa",
53
+ title: "Pulumi tls.PrivateKey ECDSA key",
54
+ description: 'Pulumi tls.PrivateKey with algorithm "ECDSA"',
55
+ category: "signature",
56
+ severity: "high",
57
+ confidence: "high",
58
+ algorithm: "ECDSA",
59
+ hndl: false,
60
+ cwe: CWE_BROKEN_CRYPTO,
61
+ message:
62
+ "Pulumi provisions a classical ECDSA key (tls.PrivateKey), forgeable by a quantum attacker.",
63
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
64
+ };
65
+ const RULE_PULUMI_ED25519: RuleMeta = {
66
+ id: "pulumi-tls-ed25519",
67
+ title: "Pulumi tls.PrivateKey Ed25519 key",
68
+ description: 'Pulumi tls.PrivateKey with algorithm "ED25519"',
69
+ category: "signature",
70
+ severity: "medium",
71
+ confidence: "high",
72
+ algorithm: "EdDSA",
73
+ hndl: false,
74
+ cwe: CWE_BROKEN_CRYPTO,
75
+ message:
76
+ "Pulumi provisions a classical Ed25519 key (tls.PrivateKey), forgeable by a quantum attacker.",
77
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
78
+ };
79
+
80
+ const RULE_BY_ALG: Record<string, RuleMeta> = {
81
+ RSA: RULE_PULUMI_RSA,
82
+ ECDSA: RULE_PULUMI_ECDSA,
83
+ ED25519: RULE_PULUMI_ED25519,
84
+ };
85
+
86
+ /** Detects classical asymmetric keys provisioned by Pulumi's tls provider. */
87
+ export const pulumiDetector: Detector = {
88
+ id: "pulumi-crypto",
89
+ description: "Classical asymmetric keys provisioned by Pulumi's tls provider (IaC)",
90
+ scope: "config",
91
+ language: "any",
92
+ rules: [RULE_PULUMI_RSA, RULE_PULUMI_ECDSA, RULE_PULUMI_ED25519],
93
+ appliesTo: (f) => hasExtension(f, PULUMI_EXTENSIONS),
94
+ detect({ file, content }): Finding[] {
95
+ if (!usesPulumiTls(content)) return [];
96
+ const findings: Finding[] = [];
97
+ eachMatch(RE_PULUMI_TLS_ALG, content, (m) => {
98
+ const rule = RULE_BY_ALG[m[1]];
99
+ if (!rule) return;
100
+ findings.push(
101
+ findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
102
+ );
103
+ });
104
+ return findings;
105
+ },
106
+ };
@@ -27,6 +27,10 @@ const RE_RUST_ECDH = /\becdh::EphemeralSecret\b|\bagreement::ECDH_P(?:256|384)\b
27
27
  const RE_RUST_ED25519 = /\bed25519_dalek::(?:SigningKey|Keypair|SecretKey)\b|\bEd25519KeyPair::/g;
28
28
  // X25519 key agreement (dalek).
29
29
  const RE_RUST_X25519 = /\bx25519_dalek::(?:EphemeralSecret|StaticSecret)\b/g;
30
+ // X448 key agreement (`x448` crate). The qualified `x448::Secret` path — covers the
31
+ // plain `use x448::Secret;` / fully-qualified form the alias mechanism (which only
32
+ // fires on a RENAMED `use … as`) misses.
33
+ const RE_RUST_X448 = /\bx448::Secret\b/g;
30
34
  // openssl crate bindings — the `Type::generate(` / `Dh::` call forms, distinct
31
35
  // from the ring / RustCrypto path syntax above (audit F5/F9 false-negatives).
32
36
  const RE_RUST_OPENSSL_RSA = /\bRsa::generate\s*\(/g;
@@ -346,6 +350,7 @@ export const rustDetector: Detector = {
346
350
  add(RE_RUST_ECDH, RULE_RUST_ECDH);
347
351
  add(RE_RUST_ED25519, RULE_RUST_ED25519);
348
352
  add(RE_RUST_X25519, RULE_RUST_X25519);
353
+ add(RE_RUST_X448, RULE_RUST_X448);
349
354
  add(RE_RUST_OPENSSL_RSA, RULE_RUST_OPENSSL_RSA);
350
355
  add(RE_RUST_OPENSSL_EC, RULE_RUST_OPENSSL_EC);
351
356
  add(RE_RUST_OPENSSL_DSA, RULE_RUST_OPENSSL_DSA);
@@ -8,15 +8,24 @@
8
8
  * Covered:
9
9
  * - Mozilla SOPS / age recipients: `age1…` bech32 public keys (X25519 key
10
10
  * agreement wrapping the data key).
11
- * - PGP-encrypted payloads: `-----BEGIN PGP MESSAGE-----` (RSA/ElGamal ESK).
12
11
  * - Bitnami Sealed Secrets: `kind: SealedSecret` (controller wraps with RSA-OAEP).
13
12
  *
13
+ * Armored PGP blocks (`-----BEGIN PGP MESSAGE-----` and PGP private keys) are owned
14
+ * by the PEM detector (`pem-pgp-message` / `pem-pgp-private-key`), so they are not
15
+ * duplicated here.
16
+ *
14
17
  * Symmetric-only schemes (ansible-vault AES, age with a scrypt passphrase) are
15
18
  * intentionally NOT flagged: a strong symmetric key is only Grover-weakened, not
16
19
  * broken, so it is out of scope for a *classical-asymmetric* readiness signal.
17
20
  */
18
21
  import type { Detector, Finding, RuleMeta } from "../types.js";
19
- import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
22
+ import {
23
+ DOC_EXTENSIONS,
24
+ eachMatch,
25
+ findingFromRule,
26
+ hasExtension,
27
+ maskCommentLines,
28
+ } from "../detect-utils.js";
20
29
  import { CWE_BROKEN_CRYPTO } from "../cwe.js";
21
30
 
22
31
  interface SecretRule {
@@ -44,24 +53,6 @@ const SECRET_RULES: SecretRule[] = [
44
53
  "Track a post-quantum age recipient / KMS (ML-KEM) and re-encrypt; rotate any secret whose ciphertext has left your control.",
45
54
  },
46
55
  },
47
- {
48
- re: /-----BEGIN PGP MESSAGE-----/g,
49
- meta: {
50
- id: "secrets-pgp-message",
51
- title: "PGP-encrypted secret (RSA/ElGamal)",
52
- description: "A PGP MESSAGE block: the session key is wrapped with classical RSA/ElGamal",
53
- category: "kem",
54
- severity: "high",
55
- confidence: "high",
56
- algorithm: "RSA",
57
- hndl: true,
58
- cwe: CWE_BROKEN_CRYPTO,
59
- message:
60
- "A PGP-encrypted secret whose session key is wrapped with classical RSA/ElGamal; harvest-now-decrypt-later exposed.",
61
- remediation:
62
- "Re-encrypt with a post-quantum KEM (ML-KEM-768) once available; rotate the underlying secret.",
63
- },
64
- },
65
56
  {
66
57
  re: /\bkind:\s*["']?SealedSecret\b/g,
67
58
  meta: {
@@ -94,16 +85,14 @@ export const secretsDetector: Detector = {
94
85
  appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
95
86
  detect({ file, content }): Finding[] {
96
87
  // Fast reject: none of the distinctive markers present.
97
- if (
98
- !content.includes("age1") &&
99
- !content.includes("BEGIN PGP MESSAGE") &&
100
- !content.includes("SealedSecret")
101
- ) {
88
+ if (!content.includes("age1") && !content.includes("SealedSecret")) {
102
89
  return [];
103
90
  }
91
+ // A commented `# kind: SealedSecret` / `# age1…` is not an active setting.
92
+ const scan = maskCommentLines(content, ["#"]);
104
93
  const findings: Finding[] = [];
105
94
  for (const rule of SECRET_RULES) {
106
- eachMatch(rule.re, content, (m) => {
95
+ eachMatch(rule.re, scan, (m) => {
107
96
  findings.push(
108
97
  findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),
109
98
  );
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Source detector for classical asymmetric cryptography in smart-contract /
3
+ * blockchain source languages: Solidity (`.sol`), Move (`.move`), and Cairo
4
+ * (`.cairo`). On-chain signature verification is pure classical elliptic-curve
5
+ * cryptography — secp256k1 ECDSA on the EVM, Ed25519 / secp256k1 on Sui & Aptos
6
+ * (Move), and STARK-curve ECDSA on Starknet (Cairo). A cryptographically-relevant
7
+ * quantum computer (CRQC) could forge any of these on-chain authorizations.
8
+ *
9
+ * HNDL: unlike a KEM or key-agreement finding, an on-chain signature check has no
10
+ * confidentiality to harvest — there is nothing to decrypt later — so every rule
11
+ * here is `hndl: false`. The exposure is nonetheless SEVERE: on-chain keys often
12
+ * ARE the custody of the asset (an account's authorization to move funds), so a
13
+ * forged signature is a direct loss, not a downgraded confidentiality guarantee.
14
+ * That is why the findings are `severity: high` despite `hndl: false`.
15
+ *
16
+ * There is no `secp256k1` value in {@link AlgorithmFamily}; secp256k1 usage is
17
+ * mapped to `ECDSA` (the scheme), and the specific curve is named in the message.
18
+ * The STARK-curve check is likewise `ECDSA` (ECDSA over the STARK-friendly curve).
19
+ *
20
+ * Patterns (verified against OpenZeppelin, Solady, Sui/Aptos Move, Starknet docs):
21
+ * - Solidity / EVM (secp256k1 ECDSA → `sol-ecrecover`):
22
+ * `ecrecover(` (the secp256k1 precompile), OpenZeppelin/Solady
23
+ * `ECDSA.recover(` / `ECDSA.tryRecover(`,
24
+ * `SignatureChecker.isValidSignatureNow(`, and imports of the
25
+ * `.../cryptography/ECDSA.sol` / `SignatureChecker.sol` (OZ) or Solady
26
+ * `utils/ECDSA.sol` libraries. Consolidated under one rule; each distinct
27
+ * token/import is its own finding (evidence), but they cannot double-fire on
28
+ * the same span.
29
+ * - Move / Sui & Aptos:
30
+ * `ed25519::signature_verify_strict(` / `ed25519_verify(` → EdDSA
31
+ * (`sol-ed25519`); `ecdsa_k1::secp256k1_verify(` /
32
+ * `ecdsa_k1::secp256k1_ecrecover(` → ECDSA secp256k1
33
+ * (`sol-secp256k1-verify`).
34
+ * - Cairo / Starknet:
35
+ * `check_ecdsa_signature(` (also the `ecdsa::check_ecdsa_signature(`
36
+ * qualified form) → ECDSA over the STARK curve (`cairo-ecdsa`).
37
+ *
38
+ * Comment suppression is done in-detector (Solidity/Move/Cairo all use C-style
39
+ * `//` line comments and `/* … *\/` block comments), so a commented-out
40
+ * verification call cannot fire. Offsets are preserved by the maskers, so finding
41
+ * line/column stay exact.
42
+ */
43
+ import type { Detector, Finding, RuleMeta } from "../types.js";
44
+ import {
45
+ eachMatch,
46
+ findingFromRule,
47
+ hasExtension,
48
+ maskBlockComments,
49
+ maskCommentLines,
50
+ SMART_CONTRACT_EXTENSIONS,
51
+ } from "../detect-utils.js";
52
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
53
+
54
+ // Shared remediation: no PQC on-chain signature scheme is standardized on these
55
+ // chains yet, so the actionable step today is to inventory the exposure and track
56
+ // each chain's roadmap for an alternative (quantum-safe) signature scheme.
57
+ const REMEDIATION_ONCHAIN =
58
+ "No PQC on-chain signature scheme is standardized on these chains yet; inventory this on-chain " +
59
+ "signature verification now and track chain roadmaps (e.g. account abstraction enabling " +
60
+ "alternative signature schemes) for a quantum-safe migration path.";
61
+
62
+ // --- Solidity / EVM: secp256k1 ECDSA. ---
63
+ // The secp256k1 recovery precompile. `\b` before `ecrecover` keeps it off the
64
+ // Move `secp256k1_ecrecover` token (preceded by `_`, a word char → no boundary).
65
+ const RE_SOL_ECRECOVER = /\becrecover\s*\(/g;
66
+ // OpenZeppelin / Solady library calls: `ECDSA.recover(` / `ECDSA.tryRecover(`
67
+ // and `SignatureChecker.isValidSignatureNow(`.
68
+ const RE_SOL_ECDSA_LIB =
69
+ /\bECDSA\.(?:recover|tryRecover)\s*\(|\bSignatureChecker\.isValidSignatureNow\s*\(/g;
70
+ // Import of the classical-signature libraries: OZ `.../cryptography/ECDSA.sol` /
71
+ // `.../cryptography/SignatureChecker.sol`, and Solady `.../utils/ECDSA.sol`.
72
+ const RE_SOL_ECDSA_IMPORT = /(?:cryptography|utils)\/(?:ECDSA|SignatureChecker)\.sol\b/g;
73
+
74
+ // --- Move / Sui & Aptos. ---
75
+ // Ed25519 verification: Aptos `ed25519::signature_verify_strict(`, Sui
76
+ // `ed25519::ed25519_verify(` (matched via the bare `ed25519_verify(` token).
77
+ const RE_MOVE_ED25519 = /\bed25519::signature_verify_strict\s*\(|\bed25519_verify\s*\(/g;
78
+ // secp256k1 ECDSA verification / recovery via the Sui `ecdsa_k1` module.
79
+ const RE_MOVE_SECP256K1 = /\becdsa_k1::secp256k1_(?:verify|ecrecover)\s*\(/g;
80
+
81
+ // --- Cairo / Starknet: STARK-curve ECDSA. ---
82
+ // `check_ecdsa_signature(` — also matches the qualified `ecdsa::check_ecdsa_signature(`
83
+ // (the preceding `:` is a non-word char, so the `\b` boundary still holds).
84
+ const RE_CAIRO_ECDSA = /\bcheck_ecdsa_signature\s*\(/g;
85
+
86
+ const RULE_SOL_ECRECOVER: RuleMeta = {
87
+ id: "sol-ecrecover",
88
+ title: "EVM on-chain ECDSA signature verification (secp256k1)",
89
+ description:
90
+ "Solidity ecrecover / OpenZeppelin-Solady ECDSA.recover / SignatureChecker (secp256k1)",
91
+ category: "signature",
92
+ severity: "high",
93
+ confidence: "high",
94
+ algorithm: "ECDSA",
95
+ hndl: false,
96
+ cwe: CWE_BROKEN_CRYPTO,
97
+ message:
98
+ "On-chain ECDSA over secp256k1 (Solidity/EVM: ecrecover, OpenZeppelin/Solady ECDSA / SignatureChecker) is forgeable by a quantum attacker; on-chain keys often ARE asset custody.",
99
+ remediation: REMEDIATION_ONCHAIN,
100
+ };
101
+ const RULE_MOVE_ED25519: RuleMeta = {
102
+ id: "sol-ed25519",
103
+ title: "Move on-chain Ed25519 signature verification",
104
+ description: "Move (Sui/Aptos) ed25519::signature_verify_strict / ed25519_verify",
105
+ category: "signature",
106
+ severity: "high",
107
+ confidence: "high",
108
+ algorithm: "EdDSA",
109
+ hndl: false,
110
+ cwe: CWE_BROKEN_CRYPTO,
111
+ message:
112
+ "On-chain Ed25519 signature verification (Move / Sui / Aptos) is classical and forgeable by a quantum attacker; on-chain keys often ARE asset custody.",
113
+ remediation: REMEDIATION_ONCHAIN,
114
+ };
115
+ const RULE_MOVE_SECP256K1: RuleMeta = {
116
+ id: "sol-secp256k1-verify",
117
+ title: "Move on-chain ECDSA signature verification (secp256k1)",
118
+ description: "Move (Sui) ecdsa_k1::secp256k1_verify / secp256k1_ecrecover",
119
+ category: "signature",
120
+ severity: "high",
121
+ confidence: "high",
122
+ algorithm: "ECDSA",
123
+ hndl: false,
124
+ cwe: CWE_BROKEN_CRYPTO,
125
+ message:
126
+ "On-chain ECDSA over secp256k1 (Move / Sui ecdsa_k1) is forgeable by a quantum attacker; on-chain keys often ARE asset custody.",
127
+ remediation: REMEDIATION_ONCHAIN,
128
+ };
129
+ const RULE_CAIRO_ECDSA: RuleMeta = {
130
+ id: "cairo-ecdsa",
131
+ title: "Cairo/Starknet on-chain ECDSA signature verification (STARK curve)",
132
+ description: "Cairo check_ecdsa_signature (STARK-curve ECDSA)",
133
+ category: "signature",
134
+ severity: "high",
135
+ confidence: "high",
136
+ algorithm: "ECDSA",
137
+ hndl: false,
138
+ cwe: CWE_BROKEN_CRYPTO,
139
+ message:
140
+ "On-chain ECDSA over the STARK curve (Cairo/Starknet check_ecdsa_signature) is forgeable by a quantum attacker; on-chain keys often ARE asset custody.",
141
+ remediation: REMEDIATION_ONCHAIN,
142
+ };
143
+
144
+ /**
145
+ * Detects classical (quantum-vulnerable) on-chain signature verification in
146
+ * smart-contract source: Solidity/EVM secp256k1 ECDSA, Move Ed25519 & secp256k1,
147
+ * and Cairo/Starknet STARK-curve ECDSA.
148
+ */
149
+ export const solidityDetector: Detector = {
150
+ id: "solidity-crypto",
151
+ description:
152
+ "Classical on-chain signature verification in smart-contract source (Solidity, Move, Cairo)",
153
+ scope: "source",
154
+ language: "solidity",
155
+ rules: [RULE_SOL_ECRECOVER, RULE_MOVE_ED25519, RULE_MOVE_SECP256K1, RULE_CAIRO_ECDSA],
156
+ appliesTo: (f) => hasExtension(f, SMART_CONTRACT_EXTENSIONS),
157
+ detect({ file, content }): Finding[] {
158
+ // Mask C-style comments (line `//` + block `/* */`) so commented-out
159
+ // verification calls can't fire; the maskers preserve byte offsets.
160
+ const scan = maskCommentLines(maskBlockComments(content), ["//"]);
161
+ const findings: Finding[] = [];
162
+ const add = (re: RegExp, rule: RuleMeta) =>
163
+ eachMatch(re, scan, (m) =>
164
+ findings.push(
165
+ findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
166
+ ),
167
+ );
168
+ add(RE_SOL_ECRECOVER, RULE_SOL_ECRECOVER);
169
+ add(RE_SOL_ECDSA_LIB, RULE_SOL_ECRECOVER);
170
+ add(RE_SOL_ECDSA_IMPORT, RULE_SOL_ECRECOVER);
171
+ add(RE_MOVE_ED25519, RULE_MOVE_ED25519);
172
+ add(RE_MOVE_SECP256K1, RULE_MOVE_SECP256K1);
173
+ add(RE_CAIRO_ECDSA, RULE_CAIRO_ECDSA);
174
+ return findings;
175
+ },
176
+ };