@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,117 @@
1
+ /**
2
+ * Config detector: classical supply-chain / artifact signing beyond the cosign +
3
+ * GPG cases the `cicd` detector covers — Docker Content Trust (Notary v1 / TUF),
4
+ * CNCF Notation, and in-toto. These sign container images, OCI artifacts, and
5
+ * build provenance with classical keys (ECDSA / RSA / Ed25519). Like all signing,
6
+ * this is the signature-side analogue of harvest-now-decrypt-later: a signature
7
+ * produced today is forgeable once a CRQC exists, and the signed artifact keeps
8
+ * verifying against that classical key for years — so these are `hndl:false` but
9
+ * real quantum-migration debt.
10
+ *
11
+ * Gated to CI pipeline files, Dockerfiles, and shell scripts, over comment-masked
12
+ * content, and keyed on distinctive command/env tokens to keep false positives low.
13
+ */
14
+ import type { Detector, Finding, RuleMeta } from "../types.js";
15
+ import { eachMatch, findingFromRule, maskCommentLines } from "../detect-utils.js";
16
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
17
+
18
+ /** CI pipeline files, Dockerfiles, and shell scripts — where signing runs. */
19
+ function isSigningContext(filePath: string): boolean {
20
+ const lower = filePath.toLowerCase();
21
+ const base = lower.split("/").pop() ?? lower;
22
+ return (
23
+ (lower.includes(".github/workflows/") && (lower.endsWith(".yml") || lower.endsWith(".yaml"))) ||
24
+ base === ".gitlab-ci.yml" ||
25
+ lower.endsWith(".gitlab-ci.yml") ||
26
+ base === "jenkinsfile" ||
27
+ lower.endsWith(".jenkinsfile") ||
28
+ base === "dockerfile" ||
29
+ base.startsWith("dockerfile.") ||
30
+ lower.endsWith(".dockerfile") ||
31
+ lower.endsWith(".sh") ||
32
+ lower.endsWith(".bash")
33
+ );
34
+ }
35
+
36
+ interface ScRule {
37
+ re: RegExp;
38
+ meta: RuleMeta;
39
+ }
40
+
41
+ const SC_RULES: ScRule[] = [
42
+ {
43
+ re: /\bDOCKER_CONTENT_TRUST\s*[:=]\s*["']?1\b|\bdocker\s+trust\s+sign\b/g,
44
+ meta: {
45
+ id: "sc-docker-content-trust",
46
+ title: "Docker Content Trust signing (Notary v1)",
47
+ description: "Docker Content Trust / docker trust sign — Notary v1 (TUF) classical keys",
48
+ category: "signature",
49
+ severity: "medium",
50
+ confidence: "high",
51
+ algorithm: "ECDSA",
52
+ hndl: false,
53
+ cwe: CWE_BROKEN_CRYPTO,
54
+ message:
55
+ "Docker Content Trust signs images with Notary v1 (TUF) classical keys (ECDSA/Ed25519); signatures are forgeable once a CRQC exists.",
56
+ remediation: "Track sigstore/Notary v2 (Notation) PQC roadmap; plan hybrid image signing.",
57
+ },
58
+ },
59
+ {
60
+ re: /\bnotation\s+(?:sign|key\s+generate|cert\s+generate)\b/g,
61
+ meta: {
62
+ id: "sc-notation-sign",
63
+ title: "Notation artifact signing",
64
+ description: "CNCF Notation signing of OCI artifacts with classical keys",
65
+ category: "signature",
66
+ severity: "medium",
67
+ confidence: "high",
68
+ algorithm: "RSA",
69
+ hndl: false,
70
+ cwe: CWE_BROKEN_CRYPTO,
71
+ message:
72
+ "Notation signs OCI artifacts with a classical key (RSA/ECDSA); signatures are forgeable once a CRQC exists.",
73
+ remediation: "Track Notation's PQC signature support (ML-DSA) and plan migration.",
74
+ },
75
+ },
76
+ {
77
+ re: /\bin[-_]toto[-_]?run\b|\bin-toto\b/g,
78
+ meta: {
79
+ id: "sc-in-toto",
80
+ title: "in-toto supply-chain signing",
81
+ description: "in-toto build provenance signed with classical keys",
82
+ category: "signature",
83
+ severity: "medium",
84
+ confidence: "high",
85
+ algorithm: "RSA",
86
+ hndl: false,
87
+ cwe: CWE_BROKEN_CRYPTO,
88
+ message:
89
+ "in-toto signs build provenance with a classical key (RSA/Ed25519); the attestation is forgeable once a CRQC exists.",
90
+ remediation: "Track in-toto/DSSE PQC signature support and plan migration.",
91
+ },
92
+ },
93
+ ];
94
+
95
+ /** Detects classical Docker Content Trust / Notation / in-toto signing. */
96
+ export const supplyChainDetector: Detector = {
97
+ id: "supply-chain-signing",
98
+ description: "Classical container/artifact signing (Docker Content Trust, Notation, in-toto)",
99
+ scope: "config",
100
+ language: "any",
101
+ rules: SC_RULES.map((r) => r.meta),
102
+ appliesTo: isSigningContext,
103
+ detect({ file, content }): Finding[] {
104
+ // `#` covers YAML/Dockerfile/shell; `//` covers Jenkinsfile (Groovy), which has no
105
+ // extension so it is never centrally comment-stripped.
106
+ const scan = maskCommentLines(content, ["#", "//"]);
107
+ const findings: Finding[] = [];
108
+ for (const rule of SC_RULES) {
109
+ eachMatch(rule.re, scan, (m) => {
110
+ findings.push(
111
+ findingFromRule(rule.meta, { file, content, index: m.index, matchLength: m[0].length }),
112
+ );
113
+ });
114
+ }
115
+ return findings;
116
+ },
117
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Source detector for classical asymmetric cryptography in Swift (Apple platforms) —
3
+ * Apple CryptoKit and the older Security framework (`SecKey*`). Lexical, same strategy
4
+ * as the other language packs; the API names are distinctive enough to keep the
5
+ * false-positive risk low. Comment/string suppression is handled centrally
6
+ * (`.swift` is a C-style comment language).
7
+ *
8
+ * CryptoKit:
9
+ * - `P256|P384|P521.Signing.PrivateKey` → ECDSA signature (hndl:false)
10
+ * - `P256|P384|P521.KeyAgreement.PrivateKey` → ECDH key agreement (hndl:true)
11
+ * - `Curve25519.Signing.PrivateKey` → Ed25519 signature (hndl:false)
12
+ * - `Curve25519.KeyAgreement.PrivateKey` → X25519 key agreement (hndl:true)
13
+ * (`SecureEnclave.P256.…` forms are covered by the same P-curve regexes.)
14
+ *
15
+ * Security framework:
16
+ * - `SecKeyCreateRandomKey` with `kSecAttrKeyTypeRSA` → RSA
17
+ * - … with `kSecAttrKeyTypeEC` / `kSecAttrKeyTypeECSECPrimeRandom` → EC
18
+ */
19
+ import type { Detector, Finding, RuleMeta } from "../types.js";
20
+ import { SWIFT_EXTENSIONS, eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
21
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
22
+
23
+ // CryptoKit P-curve keys, split by usage (Signing vs KeyAgreement). `SecureEnclave.`
24
+ // may prefix the P-curve type, so it is allowed optionally.
25
+ const RE_SWIFT_P_SIGN = /\b(?:SecureEnclave\.)?P(?:256|384|521)\.Signing\.PrivateKey\b/g;
26
+ const RE_SWIFT_P_KEX = /\b(?:SecureEnclave\.)?P(?:256|384|521)\.KeyAgreement\.PrivateKey\b/g;
27
+ // CryptoKit Curve25519: Signing = Ed25519 (EdDSA); KeyAgreement = X25519.
28
+ const RE_SWIFT_ED25519 = /\bCurve25519\.Signing\.PrivateKey\b/g;
29
+ const RE_SWIFT_X25519 = /\bCurve25519\.KeyAgreement\.PrivateKey\b/g;
30
+ // Security framework key-type attributes (used with SecKeyCreateRandomKey).
31
+ const RE_SWIFT_SEC_RSA = /\bkSecAttrKeyTypeRSA\b/g;
32
+ const RE_SWIFT_SEC_EC = /\bkSecAttrKeyType(?:EC|ECSECPrimeRandom)\b/g;
33
+
34
+ const RULE_SWIFT_ECDSA: RuleMeta = {
35
+ id: "swift-ecdsa",
36
+ title: "Swift CryptoKit ECDSA signing key",
37
+ description: "CryptoKit P256/P384/P521 Signing.PrivateKey",
38
+ category: "signature",
39
+ severity: "high",
40
+ confidence: "high",
41
+ algorithm: "ECDSA",
42
+ hndl: false,
43
+ cwe: CWE_BROKEN_CRYPTO,
44
+ message: "CryptoKit P-curve ECDSA signing (Swift) is forgeable by a quantum attacker.",
45
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
46
+ };
47
+ const RULE_SWIFT_ECDH: RuleMeta = {
48
+ id: "swift-ecdh",
49
+ title: "Swift CryptoKit ECDH key agreement",
50
+ description: "CryptoKit P256/P384/P521 KeyAgreement.PrivateKey",
51
+ category: "key-exchange",
52
+ severity: "high",
53
+ confidence: "high",
54
+ algorithm: "ECDH",
55
+ hndl: true,
56
+ cwe: CWE_BROKEN_CRYPTO,
57
+ message: "CryptoKit P-curve ECDH key agreement (Swift) is harvest-now-decrypt-later exposed.",
58
+ remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768).",
59
+ };
60
+ const RULE_SWIFT_ED25519: RuleMeta = {
61
+ id: "swift-ed25519",
62
+ title: "Swift CryptoKit Ed25519 signing key",
63
+ description: "CryptoKit Curve25519.Signing.PrivateKey (Ed25519)",
64
+ category: "signature",
65
+ // `low`, aligned with Ed25519 in every other source pack (go/rust/ruby/python/node)
66
+ // — the same primitive must not flip CI exit codes based on which language wrote it.
67
+ severity: "low",
68
+ confidence: "high",
69
+ algorithm: "EdDSA",
70
+ hndl: false,
71
+ cwe: CWE_BROKEN_CRYPTO,
72
+ message: "CryptoKit Ed25519 signing (Swift) is classical and forgeable by a quantum attacker.",
73
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
74
+ };
75
+ const RULE_SWIFT_X25519: RuleMeta = {
76
+ id: "swift-x25519",
77
+ title: "Swift CryptoKit X25519 key agreement",
78
+ description: "CryptoKit Curve25519.KeyAgreement.PrivateKey (X25519)",
79
+ category: "key-exchange",
80
+ severity: "medium",
81
+ confidence: "high",
82
+ algorithm: "X25519",
83
+ hndl: true,
84
+ cwe: CWE_BROKEN_CRYPTO,
85
+ message:
86
+ "CryptoKit X25519 key agreement (Swift) is classical and harvest-now-decrypt-later exposed.",
87
+ remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768).",
88
+ };
89
+ const RULE_SWIFT_RSA: RuleMeta = {
90
+ id: "swift-rsa",
91
+ title: "Swift Security-framework RSA key",
92
+ description: "SecKeyCreateRandomKey with kSecAttrKeyTypeRSA",
93
+ category: "kem",
94
+ severity: "high",
95
+ confidence: "high",
96
+ algorithm: "RSA",
97
+ hndl: true,
98
+ cwe: CWE_BROKEN_CRYPTO,
99
+ message: "Security-framework RSA key (Swift) is classical and not quantum-safe.",
100
+ remediation: "Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).",
101
+ };
102
+ const RULE_SWIFT_SEC_EC: RuleMeta = {
103
+ id: "swift-sec-ec",
104
+ title: "Swift Security-framework EC key",
105
+ description: "SecKeyCreateRandomKey with kSecAttrKeyTypeEC",
106
+ category: "key-exchange",
107
+ severity: "high",
108
+ confidence: "high",
109
+ algorithm: "ECDH",
110
+ hndl: true,
111
+ cwe: CWE_BROKEN_CRYPTO,
112
+ message:
113
+ "Security-framework EC key (Swift); EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).",
114
+ remediation:
115
+ "For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
116
+ };
117
+
118
+ /** Detects classical asymmetric crypto in Swift (CryptoKit + Security framework). */
119
+ export const swiftDetector: Detector = {
120
+ id: "swift-crypto",
121
+ description: "Classical asymmetric crypto in Swift (CryptoKit, Security framework)",
122
+ scope: "source",
123
+ language: "swift",
124
+ rules: [
125
+ RULE_SWIFT_ECDSA,
126
+ RULE_SWIFT_ECDH,
127
+ RULE_SWIFT_ED25519,
128
+ RULE_SWIFT_X25519,
129
+ RULE_SWIFT_RSA,
130
+ RULE_SWIFT_SEC_EC,
131
+ ],
132
+ appliesTo: (f) => hasExtension(f, SWIFT_EXTENSIONS),
133
+ detect({ file, content }): Finding[] {
134
+ const findings: Finding[] = [];
135
+ const add = (re: RegExp, rule: RuleMeta) =>
136
+ eachMatch(re, content, (m) =>
137
+ findings.push(
138
+ findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
139
+ ),
140
+ );
141
+ add(RE_SWIFT_P_SIGN, RULE_SWIFT_ECDSA);
142
+ add(RE_SWIFT_P_KEX, RULE_SWIFT_ECDH);
143
+ add(RE_SWIFT_ED25519, RULE_SWIFT_ED25519);
144
+ add(RE_SWIFT_X25519, RULE_SWIFT_X25519);
145
+ add(RE_SWIFT_SEC_RSA, RULE_SWIFT_RSA);
146
+ add(RE_SWIFT_SEC_EC, RULE_SWIFT_SEC_EC);
147
+ return findings;
148
+ },
149
+ };
@@ -16,7 +16,13 @@
16
16
  * (hndl:true) since a provisioned EC key can feed ECDH.
17
17
  */
18
18
  import type { Detector, Finding, RuleMeta } from "../types.js";
19
- import { eachMatch, findingFromRule, hasExtension } from "../detect-utils.js";
19
+ import {
20
+ eachMatch,
21
+ findingFromRule,
22
+ hasExtension,
23
+ maskBlockComments,
24
+ maskCommentLines,
25
+ } from "../detect-utils.js";
20
26
  import { CWE_BROKEN_CRYPTO } from "../cwe.js";
21
27
 
22
28
  const TF_EXTENSIONS: readonly string[] = [".tf", ".tf.json"];
@@ -29,12 +35,21 @@ const TF_EXTENSIONS: readonly string[] = [".tf", ".tf.json"];
29
35
  const RE_TF_RSA = /(?<![\w"-])"?algorithm"?\s*[:=]\s*"RSA(?:_[A-Z0-9_]+)?"/g;
30
36
  // ECDSA / EC signing: tls_private_key `"ECDSA"` and Google KMS `"EC_SIGN_…"`.
31
37
  const RE_TF_ECDSA = /(?<![\w"-])"?algorithm"?\s*[:=]\s*"(?:ECDSA|EC_SIGN_[A-Z0-9_]+)"/g;
32
- // AWS KMS customer master key specs.
33
- const RE_TF_KMS_RSA = /(?<![\w"-])"?customer_master_key_spec"?\s*[:=]\s*"RSA_\d+"/g;
34
- const RE_TF_KMS_EC = /(?<![\w"-])"?customer_master_key_spec"?\s*[:=]\s*"ECC_[A-Z0-9_]+"/g;
35
- // Azure Key Vault key type (incl. the `-HSM` variants).
36
- const RE_TF_AZ_RSA = /(?<![\w"-])"?key_type"?\s*[:=]\s*"RSA(?:-HSM)?"/g;
37
- const RE_TF_AZ_EC = /(?<![\w"-])"?key_type"?\s*[:=]\s*"EC(?:-HSM)?"/g;
38
+ // EdDSA signing: hashicorp/tls `tls_private_key` `algorithm = "ED25519"` (a supported,
39
+ // classical algorithm the RSA/ECDSA rules miss).
40
+ const RE_TF_ED25519 = /(?<![\w"-])"?algorithm"?\s*[:=]\s*"ED25519"/g;
41
+ // AWS KMS key specs: the legacy `customer_master_key_spec` and the modern `key_spec`
42
+ // alias (aws provider v4+). The `(?<![\w"-])` lookbehind rejects the `key_spec` suffix
43
+ // inside `customer_master_key_spec` (preceded by `_`), so no double-count.
44
+ const RE_TF_KMS_RSA = /(?<![\w"-])"?(?:customer_master_key_spec|key_spec)"?\s*[:=]\s*"RSA_\d+"/g;
45
+ const RE_TF_KMS_EC =
46
+ /(?<![\w"-])"?(?:customer_master_key_spec|key_spec)"?\s*[:=]\s*"ECC_[A-Z0-9_]+"/g;
47
+ // `key_type` key material: Azure Key Vault (`"RSA"` / `"EC"`, incl. `-HSM`) and
48
+ // HashiCorp Vault PKI, which uses the lowercase `"rsa"` / `"ec"` tokens — so the value
49
+ // is matched case-insensitively. The `"…"` bound keeps `"ec"` from matching `"ecc"`,
50
+ // and `customer_master_key_spec` (AWS) is a different attribute handled above.
51
+ const RE_TF_AZ_RSA = /(?<![\w"-])"?key_type"?\s*[:=]\s*"RSA(?:-HSM)?"/gi;
52
+ const RE_TF_AZ_EC = /(?<![\w"-])"?key_type"?\s*[:=]\s*"EC(?:-HSM)?"/gi;
38
53
 
39
54
  const RULE_TF_RSA: RuleMeta = {
40
55
  id: "tf-rsa-key",
@@ -62,6 +77,19 @@ const RULE_TF_ECDSA: RuleMeta = {
62
77
  message: "Terraform provisions a classical ECDSA key, forgeable by a quantum attacker.",
63
78
  remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
64
79
  };
80
+ const RULE_TF_ED25519: RuleMeta = {
81
+ id: "tf-ed25519-key",
82
+ title: "Terraform Ed25519 key",
83
+ description: 'Terraform tls_private_key algorithm = "ED25519"',
84
+ category: "signature",
85
+ severity: "medium",
86
+ confidence: "high",
87
+ algorithm: "EdDSA",
88
+ hndl: false,
89
+ cwe: CWE_BROKEN_CRYPTO,
90
+ message: "Terraform provisions a classical Ed25519 key, forgeable by a quantum attacker.",
91
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
92
+ };
65
93
  const RULE_TF_KMS_RSA: RuleMeta = {
66
94
  id: "tf-kms-rsa",
67
95
  title: "Terraform AWS KMS RSA CMK",
@@ -129,6 +157,7 @@ export const terraformDetector: Detector = {
129
157
  rules: [
130
158
  RULE_TF_RSA,
131
159
  RULE_TF_ECDSA,
160
+ RULE_TF_ED25519,
132
161
  RULE_TF_KMS_RSA,
133
162
  RULE_TF_KMS_EC,
134
163
  RULE_TF_AZ_RSA,
@@ -137,14 +166,19 @@ export const terraformDetector: Detector = {
137
166
  appliesTo: (f) => hasExtension(f, TF_EXTENSIONS),
138
167
  detect({ file, content }): Finding[] {
139
168
  const findings: Finding[] = [];
169
+ // A commented HCL argument (`# algorithm = "RSA"`, `//` lines, and `/* … */` block
170
+ // comments) is not an active resource argument. Mask block comments then line
171
+ // comments; offsets preserved.
172
+ const scan = maskCommentLines(maskBlockComments(content), ["#", "//"]);
140
173
  const add = (re: RegExp, rule: RuleMeta) =>
141
- eachMatch(re, content, (m) =>
174
+ eachMatch(re, scan, (m) =>
142
175
  findings.push(
143
176
  findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
144
177
  ),
145
178
  );
146
179
  add(RE_TF_RSA, RULE_TF_RSA);
147
180
  add(RE_TF_ECDSA, RULE_TF_ECDSA);
181
+ add(RE_TF_ED25519, RULE_TF_ED25519);
148
182
  add(RE_TF_KMS_RSA, RULE_TF_KMS_RSA);
149
183
  add(RE_TF_KMS_EC, RULE_TF_KMS_EC);
150
184
  add(RE_TF_AZ_RSA, RULE_TF_AZ_RSA);
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Config detector: classical asymmetric keys in native HashiCorp Vault server
3
+ * config / policy (HCL). Terraform-provisioned Vault is caught by the terraform
4
+ * detector (`.tf`); this covers Vault's OWN `.hcl` config — the `transit` and
5
+ * `pki` secrets engines, whose keys sign and wrap real production data.
6
+ *
7
+ * Covered (gated to a `transit`/`pki` marker to avoid firing on unrelated HCL):
8
+ * - transit key `type = "rsa-2048|3072|4096"` (RSA — key wrapping / signing).
9
+ * - transit key `type = "ecdsa-p256|p384|p521"` / `"ed25519"` (classical signing).
10
+ * - pki role `key_type = "rsa" | "ec"`.
11
+ */
12
+ import type { Detector, Finding, RuleMeta } from "../types.js";
13
+ import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
14
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
15
+
16
+ const HCL_EXTENSIONS: readonly string[] = [".hcl"];
17
+
18
+ const RE_TRANSIT_RSA = /\btype\s*=\s*"rsa-\d+"/g;
19
+ const RE_TRANSIT_ECDSA = /\btype\s*=\s*"ecdsa-p\d+"/g;
20
+ const RE_TRANSIT_ED25519 = /\btype\s*=\s*"ed25519"/g;
21
+ const RE_PKI_RSA = /\bkey_type\s*=\s*"rsa"/g;
22
+ const RE_PKI_EC = /\bkey_type\s*=\s*"ec"/g;
23
+
24
+ const RULE_TRANSIT_RSA: RuleMeta = {
25
+ id: "vault-transit-rsa",
26
+ title: "Vault transit RSA key",
27
+ description: 'Vault transit secrets engine key type = "rsa-*"',
28
+ category: "kem",
29
+ severity: "high",
30
+ confidence: "high",
31
+ algorithm: "RSA",
32
+ hndl: true,
33
+ cwe: CWE_BROKEN_CRYPTO,
34
+ message:
35
+ "Vault transit provisions a classical RSA key (signing / key wrapping), not quantum-safe.",
36
+ remediation:
37
+ "Plan migration to PQC (ML-KEM-768 for wrapping, ML-DSA-65 for signing) as Vault adds support.",
38
+ };
39
+ const RULE_TRANSIT_ECDSA: RuleMeta = {
40
+ id: "vault-transit-ecdsa",
41
+ title: "Vault transit ECDSA key",
42
+ description: 'Vault transit secrets engine key type = "ecdsa-p*"',
43
+ category: "signature",
44
+ severity: "medium",
45
+ confidence: "high",
46
+ algorithm: "ECDSA",
47
+ hndl: false,
48
+ cwe: CWE_BROKEN_CRYPTO,
49
+ message:
50
+ "Vault transit provisions a classical ECDSA signing key, forgeable by a quantum attacker.",
51
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
52
+ };
53
+ const RULE_TRANSIT_ED25519: RuleMeta = {
54
+ id: "vault-transit-ed25519",
55
+ title: "Vault transit Ed25519 key",
56
+ description: 'Vault transit secrets engine key type = "ed25519"',
57
+ category: "signature",
58
+ severity: "low",
59
+ confidence: "high",
60
+ algorithm: "EdDSA",
61
+ hndl: false,
62
+ cwe: CWE_BROKEN_CRYPTO,
63
+ message:
64
+ "Vault transit provisions a classical Ed25519 signing key, forgeable by a quantum attacker.",
65
+ remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
66
+ };
67
+ const RULE_PKI_RSA: RuleMeta = {
68
+ id: "vault-pki-rsa",
69
+ title: "Vault PKI RSA role",
70
+ description: 'Vault pki secrets engine role key_type = "rsa"',
71
+ category: "certificate",
72
+ severity: "medium",
73
+ confidence: "high",
74
+ algorithm: "RSA",
75
+ hndl: true,
76
+ cwe: CWE_BROKEN_CRYPTO,
77
+ message: "Vault PKI mints certificates with a classical RSA key, not quantum-safe.",
78
+ remediation: "Plan migration to PQC certificate keys (ML-DSA-65) as the CA chain adds support.",
79
+ };
80
+ const RULE_PKI_EC: RuleMeta = {
81
+ id: "vault-pki-ec",
82
+ title: "Vault PKI EC role",
83
+ description: 'Vault pki secrets engine role key_type = "ec"',
84
+ category: "certificate",
85
+ severity: "medium",
86
+ confidence: "high",
87
+ algorithm: "ECDSA",
88
+ hndl: false,
89
+ cwe: CWE_BROKEN_CRYPTO,
90
+ message: "Vault PKI mints certificates with a classical EC key, forgeable by a quantum attacker.",
91
+ remediation: "Plan migration to ML-DSA-65 (FIPS 204) certificate keys.",
92
+ };
93
+
94
+ /** Detects classical asymmetric keys in native Vault HCL config. */
95
+ export const vaultDetector: Detector = {
96
+ id: "vault-crypto",
97
+ description: "Classical asymmetric keys in native HashiCorp Vault config (transit, pki)",
98
+ scope: "config",
99
+ language: "any",
100
+ rules: [RULE_TRANSIT_RSA, RULE_TRANSIT_ECDSA, RULE_TRANSIT_ED25519, RULE_PKI_RSA, RULE_PKI_EC],
101
+ appliesTo: (f) => hasExtension(f, HCL_EXTENSIONS),
102
+ detect({ file, content }): Finding[] {
103
+ // Gate: only inside a Vault transit / pki context.
104
+ if (!content.includes("transit") && !content.includes("pki")) return [];
105
+ const scan = maskCommentLines(content, ["#", "//"]);
106
+ const findings: Finding[] = [];
107
+ const add = (re: RegExp, rule: RuleMeta) =>
108
+ eachMatch(re, scan, (m) =>
109
+ findings.push(
110
+ findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),
111
+ ),
112
+ );
113
+ add(RE_TRANSIT_RSA, RULE_TRANSIT_RSA);
114
+ add(RE_TRANSIT_ECDSA, RULE_TRANSIT_ECDSA);
115
+ add(RE_TRANSIT_ED25519, RULE_TRANSIT_ED25519);
116
+ add(RE_PKI_RSA, RULE_PKI_RSA);
117
+ add(RE_PKI_EC, RULE_PKI_EC);
118
+ return findings;
119
+ },
120
+ };
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Config detector: classical key exchange in network transport / VPN config —
3
- * WireGuard, IPsec (strongSwan), and OpenSSH server config. These are the tunnels
4
- * carrying "communication between things"; their key exchange is classical and so
5
- * harvest-now-decrypt-later exposed (a recorded tunnel is decryptable once a CRQC
6
- * exists).
3
+ * WireGuard and IPsec (strongSwan). These are the tunnels carrying "communication
4
+ * between things"; their key exchange is classical and so harvest-now-decrypt-later
5
+ * exposed (a recorded tunnel is decryptable once a CRQC exists).
7
6
  *
8
7
  * Covered (each gated so it only fires inside the relevant config):
9
8
  * - **WireGuard** — `[Interface]`/`[Peer]` sections with `PrivateKey`/`PublicKey`.
@@ -12,18 +11,20 @@
12
11
  * tunnel-wrapping layer. The private key is treated as sensitive key material.
13
12
  * - **IPsec / strongSwan** — IKE/ESP proposals naming classical DH groups
14
13
  * (`modp*` = finite-field DH, `ecp*` = ECDH).
15
- * - **sshd_config / ssh_config** — a `KexAlgorithms` line that offers no PQC hybrid
16
- * KEX (`sntrup761x25519-*`, `mlkem768x25519-*`). If a PQC KEX IS listed the line
17
- * is left alone, so a hardened server stays silent.
14
+ *
15
+ * `sshd_config` / `ssh_config` KexAlgorithms are intentionally NOT handled here
16
+ * the language-agnostic SSH token detector in `source.ts` (`ssh-kex-classical`)
17
+ * already covers them; a rule here would double-count.
18
18
  */
19
19
  import type { Detector, Finding, RuleMeta } from "../types.js";
20
- import { eachMatch, findingFromRule } from "../detect-utils.js";
20
+ import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
21
21
  import { CWE_BROKEN_CRYPTO } from "../cwe.js";
22
22
 
23
+ const CONF_EXTENSIONS: readonly string[] = [".conf"];
24
+
23
25
  const RE_WG_KEY = /\b(?:PrivateKey|PublicKey)\s*=\s*[A-Za-z0-9+/]{42,}=/g;
24
26
  const RE_IPSEC_MODP = /\bmodp\d+\b/gi;
25
27
  const RE_IPSEC_ECP = /\becp\d+(?:bp)?\b/gi;
26
- const RE_SSHD_KEX = /^[ \t]*KexAlgorithms[ \t]+([^\n]*)/gm;
27
28
 
28
29
  const RULE_WG: RuleMeta = {
29
30
  id: "net-wireguard-x25519",
@@ -70,61 +71,32 @@ const RULE_IPSEC_EC: RuleMeta = {
70
71
  remediation:
71
72
  "Add a PQC/hybrid IKE proposal (X25519MLKEM768 / ML-KEM) as your IPsec stack supports it.",
72
73
  };
73
- const RULE_SSHD_KEX: RuleMeta = {
74
- id: "net-sshd-classical-kex",
75
- title: "SSH server offers no PQC key exchange",
76
- description:
77
- "sshd_config KexAlgorithms lists only classical key exchange (no sntrup761/mlkem768)",
78
- category: "key-exchange",
79
- severity: "medium",
80
- confidence: "high",
81
- algorithm: "ECDH",
82
- hndl: true,
83
- cwe: CWE_BROKEN_CRYPTO,
84
- message:
85
- "SSH server's KexAlgorithms offers only classical key exchange (no sntrup761x25519 / mlkem768x25519); sessions are harvest-now-decrypt-later exposed.",
86
- remediation: "Add a PQC hybrid KEX: sntrup761x25519-sha512@openssh.com or mlkem768x25519-sha256.",
87
- };
88
-
89
- /** True when a KexAlgorithms value already offers a post-quantum hybrid KEX. */
90
- function offersPqKex(kexLine: string): boolean {
91
- return /sntrup761x25519|mlkem768/.test(kexLine);
92
- }
93
74
 
94
- /** Detects classical key exchange in WireGuard / IPsec / sshd network config. */
75
+ /** Detects classical key exchange in WireGuard / IPsec network config. */
95
76
  export const vpnDetector: Detector = {
96
77
  id: "network-transport-crypto",
97
- description: "Classical key exchange in network transport / VPN config (WireGuard, IPsec, sshd)",
78
+ description: "Classical key exchange in network transport / VPN config (WireGuard, IPsec)",
98
79
  scope: "config",
99
80
  language: "any",
100
- rules: [RULE_WG, RULE_IPSEC_DH, RULE_IPSEC_EC, RULE_SSHD_KEX],
101
- appliesTo: (f) => {
102
- const lower = f.toLowerCase();
103
- const base = lower.split("/").pop() ?? lower;
104
- return lower.endsWith(".conf") || base === "sshd_config" || base === "ssh_config";
105
- },
81
+ rules: [RULE_WG, RULE_IPSEC_DH, RULE_IPSEC_EC],
82
+ appliesTo: (f) => hasExtension(f, CONF_EXTENSIONS),
106
83
  detect({ file, content }): Finding[] {
107
84
  const findings: Finding[] = [];
108
85
  const push = (rule: RuleMeta, index: number, length: number) =>
109
86
  findings.push(findingFromRule(rule, { file, content, index, matchLength: length }));
87
+ // ipsec.conf / wg configs use `#` line comments. Match over comment-masked
88
+ // content (offsets preserved); gates stay on the original.
89
+ const scan = maskCommentLines(content, ["#"]);
110
90
 
111
91
  // WireGuard: gated to a WireGuard config section.
112
92
  if (content.includes("[Interface]") || content.includes("[Peer]")) {
113
- eachMatch(RE_WG_KEY, content, (m) => push(RULE_WG, m.index, m[0].length));
93
+ eachMatch(RE_WG_KEY, scan, (m) => push(RULE_WG, m.index, m[0].length));
114
94
  }
115
95
 
116
96
  // IPsec / strongSwan: gated to a proposal assignment.
117
97
  if (/\b(?:ike|esp|proposals?|keyexchange)\s*=/i.test(content)) {
118
- eachMatch(RE_IPSEC_MODP, content, (m) => push(RULE_IPSEC_DH, m.index, m[0].length));
119
- eachMatch(RE_IPSEC_ECP, content, (m) => push(RULE_IPSEC_EC, m.index, m[0].length));
120
- }
121
-
122
- // sshd_config / ssh_config: KexAlgorithms line with no PQC hybrid offered.
123
- const base = file.toLowerCase().split("/").pop() ?? "";
124
- if (base === "sshd_config" || base === "ssh_config") {
125
- eachMatch(RE_SSHD_KEX, content, (m) => {
126
- if (!offersPqKex(m[1])) push(RULE_SSHD_KEX, m.index, m[0].length);
127
- });
98
+ eachMatch(RE_IPSEC_MODP, scan, (m) => push(RULE_IPSEC_DH, m.index, m[0].length));
99
+ eachMatch(RE_IPSEC_ECP, scan, (m) => push(RULE_IPSEC_EC, m.index, m[0].length));
128
100
  }
129
101
  return findings;
130
102
  },