@quantakrypto/core 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/README.md +76 -27
  2. package/dist/cbom-merge.d.ts +9 -0
  3. package/dist/cbom-merge.d.ts.map +1 -0
  4. package/dist/cbom-merge.js +121 -0
  5. package/dist/cbom-merge.js.map +1 -0
  6. package/dist/cbom.d.ts +5 -0
  7. package/dist/cbom.d.ts.map +1 -1
  8. package/dist/cbom.js +132 -37
  9. package/dist/cbom.js.map +1 -1
  10. package/dist/comments.d.ts +6 -26
  11. package/dist/comments.d.ts.map +1 -1
  12. package/dist/comments.js +77 -38
  13. package/dist/comments.js.map +1 -1
  14. package/dist/dependencies.d.ts.map +1 -1
  15. package/dist/dependencies.js +1 -1
  16. package/dist/dependencies.js.map +1 -1
  17. package/dist/detect-utils.d.ts +59 -21
  18. package/dist/detect-utils.d.ts.map +1 -1
  19. package/dist/detect-utils.js +143 -8
  20. package/dist/detect-utils.js.map +1 -1
  21. package/dist/detectors/age.d.ts +16 -0
  22. package/dist/detectors/age.d.ts.map +1 -0
  23. package/dist/detectors/age.js +40 -0
  24. package/dist/detectors/age.js.map +1 -0
  25. package/dist/detectors/ansible.d.ts +13 -0
  26. package/dist/detectors/ansible.d.ts.map +1 -0
  27. package/dist/detectors/ansible.js +108 -0
  28. package/dist/detectors/ansible.js.map +1 -0
  29. package/dist/detectors/bicep.d.ts +20 -0
  30. package/dist/detectors/bicep.d.ts.map +1 -0
  31. package/dist/detectors/bicep.js +74 -0
  32. package/dist/detectors/bicep.js.map +1 -0
  33. package/dist/detectors/c.d.ts +4 -2
  34. package/dist/detectors/c.d.ts.map +1 -1
  35. package/dist/detectors/c.js +222 -4
  36. package/dist/detectors/c.js.map +1 -1
  37. package/dist/detectors/cicd.d.ts +27 -0
  38. package/dist/detectors/cicd.d.ts.map +1 -0
  39. package/dist/detectors/cicd.js +129 -0
  40. package/dist/detectors/cicd.js.map +1 -0
  41. package/dist/detectors/cloud-kms.d.ts +24 -0
  42. package/dist/detectors/cloud-kms.d.ts.map +1 -0
  43. package/dist/detectors/cloud-kms.js +99 -0
  44. package/dist/detectors/cloud-kms.js.map +1 -0
  45. package/dist/detectors/cloudformation.d.ts +38 -0
  46. package/dist/detectors/cloudformation.d.ts.map +1 -0
  47. package/dist/detectors/cloudformation.js +201 -0
  48. package/dist/detectors/cloudformation.js.map +1 -0
  49. package/dist/detectors/codesign.d.ts +50 -0
  50. package/dist/detectors/codesign.d.ts.map +1 -0
  51. package/dist/detectors/codesign.js +143 -0
  52. package/dist/detectors/codesign.js.map +1 -0
  53. package/dist/detectors/csharp.d.ts.map +1 -1
  54. package/dist/detectors/csharp.js +13 -3
  55. package/dist/detectors/csharp.js.map +1 -1
  56. package/dist/detectors/dart.d.ts +35 -0
  57. package/dist/detectors/dart.d.ts.map +1 -0
  58. package/dist/detectors/dart.js +177 -0
  59. package/dist/detectors/dart.js.map +1 -0
  60. package/dist/detectors/database.d.ts +19 -0
  61. package/dist/detectors/database.d.ts.map +1 -0
  62. package/dist/detectors/database.js +97 -0
  63. package/dist/detectors/database.js.map +1 -0
  64. package/dist/detectors/dkim.d.ts +54 -0
  65. package/dist/detectors/dkim.d.ts.map +1 -0
  66. package/dist/detectors/dkim.js +92 -0
  67. package/dist/detectors/dkim.js.map +1 -0
  68. package/dist/detectors/dnssec.d.ts +55 -0
  69. package/dist/detectors/dnssec.d.ts.map +1 -0
  70. package/dist/detectors/dnssec.js +116 -0
  71. package/dist/detectors/dnssec.js.map +1 -0
  72. package/dist/detectors/elixir.d.ts +20 -0
  73. package/dist/detectors/elixir.d.ts.map +1 -0
  74. package/dist/detectors/elixir.js +227 -0
  75. package/dist/detectors/elixir.js.map +1 -0
  76. package/dist/detectors/java.d.ts.map +1 -1
  77. package/dist/detectors/java.js +34 -4
  78. package/dist/detectors/java.js.map +1 -1
  79. package/dist/detectors/jose.d.ts +20 -0
  80. package/dist/detectors/jose.d.ts.map +1 -0
  81. package/dist/detectors/jose.js +72 -0
  82. package/dist/detectors/jose.js.map +1 -0
  83. package/dist/detectors/jwk.d.ts +25 -0
  84. package/dist/detectors/jwk.d.ts.map +1 -0
  85. package/dist/detectors/jwk.js +131 -0
  86. package/dist/detectors/jwk.js.map +1 -0
  87. package/dist/detectors/k8s.d.ts +17 -0
  88. package/dist/detectors/k8s.d.ts.map +1 -0
  89. package/dist/detectors/k8s.js +88 -0
  90. package/dist/detectors/k8s.js.map +1 -0
  91. package/dist/detectors/keystore.d.ts +19 -0
  92. package/dist/detectors/keystore.d.ts.map +1 -0
  93. package/dist/detectors/keystore.js +76 -0
  94. package/dist/detectors/keystore.js.map +1 -0
  95. package/dist/detectors/mesh.d.ts +34 -0
  96. package/dist/detectors/mesh.d.ts.map +1 -0
  97. package/dist/detectors/mesh.js +91 -0
  98. package/dist/detectors/mesh.js.map +1 -0
  99. package/dist/detectors/messaging.d.ts +16 -0
  100. package/dist/detectors/messaging.d.ts.map +1 -0
  101. package/dist/detectors/messaging.js +79 -0
  102. package/dist/detectors/messaging.js.map +1 -0
  103. package/dist/detectors/objc.d.ts +41 -0
  104. package/dist/detectors/objc.d.ts.map +1 -0
  105. package/dist/detectors/objc.js +146 -0
  106. package/dist/detectors/objc.js.map +1 -0
  107. package/dist/detectors/openpgp.d.ts +20 -0
  108. package/dist/detectors/openpgp.d.ts.map +1 -0
  109. package/dist/detectors/openpgp.js +151 -0
  110. package/dist/detectors/openpgp.js.map +1 -0
  111. package/dist/detectors/pem.d.ts.map +1 -1
  112. package/dist/detectors/pem.js +33 -1
  113. package/dist/detectors/pem.js.map +1 -1
  114. package/dist/detectors/php.d.ts +25 -0
  115. package/dist/detectors/php.d.ts.map +1 -0
  116. package/dist/detectors/php.js +171 -0
  117. package/dist/detectors/php.js.map +1 -0
  118. package/dist/detectors/pkcs11.d.ts +17 -0
  119. package/dist/detectors/pkcs11.d.ts.map +1 -0
  120. package/dist/detectors/pkcs11.js +87 -0
  121. package/dist/detectors/pkcs11.js.map +1 -0
  122. package/dist/detectors/proxy.d.ts +52 -0
  123. package/dist/detectors/proxy.d.ts.map +1 -0
  124. package/dist/detectors/proxy.js +157 -0
  125. package/dist/detectors/proxy.js.map +1 -0
  126. package/dist/detectors/pulumi.d.ts +19 -0
  127. package/dist/detectors/pulumi.d.ts.map +1 -0
  128. package/dist/detectors/pulumi.js +80 -0
  129. package/dist/detectors/pulumi.js.map +1 -0
  130. package/dist/detectors/python.d.ts.map +1 -1
  131. package/dist/detectors/python.js +70 -0
  132. package/dist/detectors/python.js.map +1 -1
  133. package/dist/detectors/rust.d.ts.map +1 -1
  134. package/dist/detectors/rust.js +73 -0
  135. package/dist/detectors/rust.js.map +1 -1
  136. package/dist/detectors/secrets.d.ts +24 -0
  137. package/dist/detectors/secrets.d.ts.map +1 -0
  138. package/dist/detectors/secrets.js +63 -0
  139. package/dist/detectors/secrets.js.map +1 -0
  140. package/dist/detectors/solidity.d.ts +50 -0
  141. package/dist/detectors/solidity.d.ts.map +1 -0
  142. package/dist/detectors/solidity.js +108 -0
  143. package/dist/detectors/solidity.js.map +1 -0
  144. package/dist/detectors/source.d.ts.map +1 -1
  145. package/dist/detectors/source.js +243 -53
  146. package/dist/detectors/source.js.map +1 -1
  147. package/dist/detectors/spire.d.ts +49 -0
  148. package/dist/detectors/spire.d.ts.map +1 -0
  149. package/dist/detectors/spire.js +77 -0
  150. package/dist/detectors/spire.js.map +1 -0
  151. package/dist/detectors/ssh-ca.d.ts +72 -0
  152. package/dist/detectors/ssh-ca.d.ts.map +1 -0
  153. package/dist/detectors/ssh-ca.js +119 -0
  154. package/dist/detectors/ssh-ca.js.map +1 -0
  155. package/dist/detectors/stateful-hbs.d.ts.map +1 -1
  156. package/dist/detectors/stateful-hbs.js +9 -6
  157. package/dist/detectors/stateful-hbs.js.map +1 -1
  158. package/dist/detectors/supply-chain.d.ts +17 -0
  159. package/dist/detectors/supply-chain.d.ts.map +1 -0
  160. package/dist/detectors/supply-chain.js +89 -0
  161. package/dist/detectors/supply-chain.js.map +1 -0
  162. package/dist/detectors/swift.d.ts +22 -0
  163. package/dist/detectors/swift.d.ts.map +1 -0
  164. package/dist/detectors/swift.js +120 -0
  165. package/dist/detectors/swift.js.map +1 -0
  166. package/dist/detectors/terraform.d.ts +21 -0
  167. package/dist/detectors/terraform.d.ts.map +1 -0
  168. package/dist/detectors/terraform.js +150 -0
  169. package/dist/detectors/terraform.js.map +1 -0
  170. package/dist/detectors/vault.d.ts +15 -0
  171. package/dist/detectors/vault.d.ts.map +1 -0
  172. package/dist/detectors/vault.js +97 -0
  173. package/dist/detectors/vault.js.map +1 -0
  174. package/dist/detectors/vpn.d.ts +22 -0
  175. package/dist/detectors/vpn.d.ts.map +1 -0
  176. package/dist/detectors/vpn.js +73 -0
  177. package/dist/detectors/vpn.js.map +1 -0
  178. package/dist/detectors/weak-hash.d.ts +71 -0
  179. package/dist/detectors/weak-hash.d.ts.map +1 -0
  180. package/dist/detectors/weak-hash.js +116 -0
  181. package/dist/detectors/weak-hash.js.map +1 -0
  182. package/dist/detectors/webauthn.d.ts +61 -0
  183. package/dist/detectors/webauthn.d.ts.map +1 -0
  184. package/dist/detectors/webauthn.js +109 -0
  185. package/dist/detectors/webauthn.js.map +1 -0
  186. package/dist/detectors/xmldsig.d.ts +23 -0
  187. package/dist/detectors/xmldsig.d.ts.map +1 -0
  188. package/dist/detectors/xmldsig.js +84 -0
  189. package/dist/detectors/xmldsig.js.map +1 -0
  190. package/dist/evidence.d.ts +79 -4
  191. package/dist/evidence.d.ts.map +1 -1
  192. package/dist/evidence.js +76 -0
  193. package/dist/evidence.js.map +1 -1
  194. package/dist/index.d.ts +17 -9
  195. package/dist/index.d.ts.map +1 -1
  196. package/dist/index.js +21 -9
  197. package/dist/index.js.map +1 -1
  198. package/dist/inventory.d.ts +0 -2
  199. package/dist/inventory.d.ts.map +1 -1
  200. package/dist/inventory.js +15 -6
  201. package/dist/inventory.js.map +1 -1
  202. package/dist/policy.d.ts +67 -0
  203. package/dist/policy.d.ts.map +1 -0
  204. package/dist/policy.js +115 -0
  205. package/dist/policy.js.map +1 -0
  206. package/dist/registry.d.ts +9 -4
  207. package/dist/registry.d.ts.map +1 -1
  208. package/dist/registry.js +81 -4
  209. package/dist/registry.js.map +1 -1
  210. package/dist/remediate-pipeline.d.ts +0 -3
  211. package/dist/remediate-pipeline.d.ts.map +1 -1
  212. package/dist/remediate-pipeline.js +23 -2
  213. package/dist/remediate-pipeline.js.map +1 -1
  214. package/dist/remediation.d.ts +10 -0
  215. package/dist/remediation.d.ts.map +1 -1
  216. package/dist/remediation.js +32 -0
  217. package/dist/remediation.js.map +1 -1
  218. package/dist/report.d.ts +9 -0
  219. package/dist/report.d.ts.map +1 -1
  220. package/dist/report.js +38 -1
  221. package/dist/report.js.map +1 -1
  222. package/dist/scan-worker.js +5 -3
  223. package/dist/scan-worker.js.map +1 -1
  224. package/dist/scan.d.ts.map +1 -1
  225. package/dist/scan.js +35 -6
  226. package/dist/scan.js.map +1 -1
  227. package/dist/standards-profiles.d.ts +58 -0
  228. package/dist/standards-profiles.d.ts.map +1 -0
  229. package/dist/standards-profiles.js +100 -0
  230. package/dist/standards-profiles.js.map +1 -0
  231. package/dist/standards.d.ts +96 -0
  232. package/dist/standards.d.ts.map +1 -0
  233. package/dist/standards.js +107 -0
  234. package/dist/standards.js.map +1 -0
  235. package/dist/types.d.ts +6 -5
  236. package/dist/types.d.ts.map +1 -1
  237. package/dist/types.js.map +1 -1
  238. package/dist/version.d.ts +1 -1
  239. package/dist/version.js +1 -1
  240. package/dist/version.js.map +1 -1
  241. package/dist/vex.d.ts +39 -0
  242. package/dist/vex.d.ts.map +1 -0
  243. package/dist/vex.js +87 -0
  244. package/dist/vex.js.map +1 -0
  245. package/dist/walk.d.ts +8 -3
  246. package/dist/walk.d.ts.map +1 -1
  247. package/dist/walk.js +32 -1
  248. package/dist/walk.js.map +1 -1
  249. package/package.json +1 -1
  250. package/src/cbom-merge.ts +146 -0
  251. package/src/cbom.ts +153 -39
  252. package/src/comments.ts +84 -39
  253. package/src/dependencies.ts +2 -1
  254. package/src/detect-utils.ts +146 -12
  255. package/src/detectors/age.ts +60 -0
  256. package/src/detectors/ansible.ts +140 -0
  257. package/src/detectors/bicep.ts +107 -0
  258. package/src/detectors/c.ts +240 -6
  259. package/src/detectors/cicd.ts +171 -0
  260. package/src/detectors/cloud-kms.ts +141 -0
  261. package/src/detectors/cloudformation.ts +250 -0
  262. package/src/detectors/codesign.ts +214 -0
  263. package/src/detectors/csharp.ts +13 -3
  264. package/src/detectors/dart.ts +237 -0
  265. package/src/detectors/database.ts +141 -0
  266. package/src/detectors/dkim.ts +172 -0
  267. package/src/detectors/dnssec.ts +195 -0
  268. package/src/detectors/elixir.ts +268 -0
  269. package/src/detectors/java.ts +39 -4
  270. package/src/detectors/jose.ts +110 -0
  271. package/src/detectors/jwk.ts +170 -0
  272. package/src/detectors/k8s.ts +115 -0
  273. package/src/detectors/keystore.ts +109 -0
  274. package/src/detectors/mesh.ts +130 -0
  275. package/src/detectors/messaging.ts +106 -0
  276. package/src/detectors/objc.ts +209 -0
  277. package/src/detectors/openpgp.ts +194 -0
  278. package/src/detectors/pem.ts +31 -1
  279. package/src/detectors/php.ts +230 -0
  280. package/src/detectors/pkcs11.ts +113 -0
  281. package/src/detectors/proxy.ts +236 -0
  282. package/src/detectors/pulumi.ts +106 -0
  283. package/src/detectors/python.ts +71 -0
  284. package/src/detectors/rust.ts +76 -0
  285. package/src/detectors/secrets.ts +103 -0
  286. package/src/detectors/solidity.ts +176 -0
  287. package/src/detectors/source.ts +283 -73
  288. package/src/detectors/spire.ts +143 -0
  289. package/src/detectors/ssh-ca.ts +215 -0
  290. package/src/detectors/stateful-hbs.ts +9 -6
  291. package/src/detectors/supply-chain.ts +117 -0
  292. package/src/detectors/swift.ts +149 -0
  293. package/src/detectors/terraform.ts +188 -0
  294. package/src/detectors/vault.ts +120 -0
  295. package/src/detectors/vpn.ts +103 -0
  296. package/src/detectors/weak-hash.ts +217 -0
  297. package/src/detectors/webauthn.ts +205 -0
  298. package/src/detectors/xmldsig.ts +115 -0
  299. package/src/evidence.ts +138 -4
  300. package/src/index.ts +50 -12
  301. package/src/inventory.ts +15 -7
  302. package/src/policy.ts +180 -0
  303. package/src/registry.ts +81 -4
  304. package/src/remediate-pipeline.ts +26 -3
  305. package/src/remediation.ts +39 -0
  306. package/src/report.ts +43 -1
  307. package/src/scan-worker.ts +5 -3
  308. package/src/scan.ts +41 -6
  309. package/src/standards-profiles.ts +136 -0
  310. package/src/standards.ts +186 -0
  311. package/src/types.ts +11 -4
  312. package/src/version.ts +1 -1
  313. package/src/vex.ts +122 -0
  314. package/src/walk.ts +33 -2
@@ -0,0 +1,97 @@
1
+ import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension, maskCommentLines, } from "../detect-utils.js";
2
+ import { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION } from "../cwe.js";
3
+ const RE_PGCRYPTO = /\bpgp_pub_(?:encrypt|decrypt)\b/g;
4
+ // Matches the no-verification TLS modes across libpq (`sslmode=require`), the
5
+ // `PGSSLMODE` env var, MySQL (`ssl-mode=PREFERRED`/`REQUIRED`, hyphenated), and YAML
6
+ // connection config (`sslmode: require`) — i.e. an optional `pg` prefix, `-`/`_` or no
7
+ // separator, `:` or `=`, and the libpq/MySQL value spellings.
8
+ const RE_WEAK_SSLMODE = /\b(?:pg)?ssl[-_]?mode\s*[:=]\s*["']?(?:allow|prefer(?:red)?|require[d]?)\b/gi;
9
+ // SQL Server TDE: the database encryption key is protected by a classical RSA
10
+ // asymmetric key / certificate. `CREATE ASYMMETRIC KEY … WITH ALGORITHM = RSA_2048`
11
+ // is the distinctive form; the span between the two clauses is lazily bounded to keep
12
+ // matching linear.
13
+ const RE_MSSQL_TDE_RSA = /\bCREATE\s+ASYMMETRIC\s+KEY\b[\s\S]{0,300}?\bALGORITHM\s*=\s*RSA_\d+/gi;
14
+ const RULE_PGCRYPTO = {
15
+ id: "db-pgcrypto-pubkey",
16
+ title: "pgcrypto public-key encryption",
17
+ description: "Postgres pgcrypto pgp_pub_encrypt / pgp_pub_decrypt (RSA/ElGamal) on stored data",
18
+ category: "kem",
19
+ severity: "high",
20
+ confidence: "high",
21
+ algorithm: "RSA",
22
+ hndl: true,
23
+ cwe: CWE_BROKEN_CRYPTO,
24
+ message: "Column data is encrypted with pgcrypto public-key crypto (classical RSA/ElGamal); stored ciphertext is harvest-now-decrypt-later exposed.",
25
+ remediation: "Plan migration to a post-quantum KEM (ML-KEM-768) envelope for at-rest data; re-encrypt long-lived rows.",
26
+ };
27
+ const RULE_MSSQL_TDE_RSA = {
28
+ id: "db-tde-rsa",
29
+ title: "SQL Server TDE RSA key",
30
+ description: "SQL Server CREATE ASYMMETRIC KEY ... WITH ALGORITHM = RSA_* (TDE key protection)",
31
+ category: "kem",
32
+ severity: "high",
33
+ confidence: "high",
34
+ algorithm: "RSA",
35
+ hndl: true,
36
+ cwe: CWE_BROKEN_CRYPTO,
37
+ message: "Transparent Data Encryption protects the database encryption key with a classical RSA asymmetric key; the at-rest data is harvest-now-decrypt-later exposed once the RSA key falls.",
38
+ remediation: "Plan migration to a PQC KEM (ML-KEM-768) for TDE key protection as the database engine adds support; re-key long-lived encrypted databases.",
39
+ };
40
+ const RULE_WEAK_SSLMODE = {
41
+ id: "db-weak-sslmode",
42
+ title: "Database sslmode without verification",
43
+ description: "libpq sslmode is allow/prefer/require (no certificate verification)",
44
+ category: "tls",
45
+ severity: "medium",
46
+ confidence: "high",
47
+ hndl: false,
48
+ cwe: CWE_CERT_VALIDATION,
49
+ message: "Database sslmode does not verify the server certificate; the classical TLS session is MITM-able and its key exchange is harvestable.",
50
+ remediation: "Use sslmode=verify-full and TLS 1.3; track PQC-hybrid KEX (X25519MLKEM768) for database transport.",
51
+ };
52
+ /** Detects classical database at-rest / transport crypto. */
53
+ export const databaseDetector = {
54
+ id: "database-crypto",
55
+ description: "Classical crypto in database usage (pgcrypto public-key, SQL Server TDE, weak client sslmode)",
56
+ scope: "config",
57
+ language: "any",
58
+ rules: [RULE_PGCRYPTO, RULE_MSSQL_TDE_RSA, RULE_WEAK_SSLMODE],
59
+ // Skip prose/docs: a README showing `sslmode=require` is not a live connection string.
60
+ appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),
61
+ detect({ file, content }) {
62
+ const findings = [];
63
+ // Mask SQL (`--`, single-line `/* … */`), shell/ini (`#`), and ini (`;`) line
64
+ // comments: a commented `-- pgp_pub_encrypt` or `; sslmode=require` is not active.
65
+ const scan = maskCommentLines(content, ["#", "--", ";", "/*"]);
66
+ if (file.toLowerCase().endsWith(".sql") && content.includes("pgp_pub_")) {
67
+ eachMatch(RE_PGCRYPTO, scan, (m) => findings.push(findingFromRule(RULE_PGCRYPTO, {
68
+ file,
69
+ content,
70
+ index: m.index,
71
+ matchLength: m[0].length,
72
+ })));
73
+ }
74
+ const lc = content.toLowerCase();
75
+ // Cheap prefilter; matches the separators the RE_WEAK_SSLMODE regex accepts
76
+ // (`sslmode`, `ssl-mode`, `ssl_mode`) so no supported form is gated out.
77
+ if (lc.includes("sslmode") || lc.includes("ssl-mode") || lc.includes("ssl_mode")) {
78
+ eachMatch(RE_WEAK_SSLMODE, scan, (m) => findings.push(findingFromRule(RULE_WEAK_SSLMODE, {
79
+ file,
80
+ content,
81
+ index: m.index,
82
+ matchLength: m[0].length,
83
+ })));
84
+ }
85
+ // SQL Server TDE: a classical RSA asymmetric key protecting the DEK.
86
+ if (lc.includes("asymmetric key")) {
87
+ eachMatch(RE_MSSQL_TDE_RSA, scan, (m) => findings.push(findingFromRule(RULE_MSSQL_TDE_RSA, {
88
+ file,
89
+ content,
90
+ index: m.index,
91
+ matchLength: m[0].length,
92
+ })));
93
+ }
94
+ return findings;
95
+ },
96
+ };
97
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/detectors/database.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical crypto in database usage — public-key encryption of\n * stored data (pgcrypto) and weak client TLS posture. Data-at-rest is the sharpest\n * harvest-now-decrypt-later target: a column encrypted to an RSA key today is a\n * time capsule an adversary can hold until key recovery is cheap.\n *\n * Covered:\n * - pgcrypto `pgp_pub_encrypt` / `pgp_pub_decrypt` in `.sql` (RSA/ElGamal public-key\n * encryption of column data) → `hndl:true`.\n * - libpq `sslmode = allow | prefer | require` in any config/connection file — a\n * mode that does NOT verify the server certificate, so the classical TLS session\n * it negotiates is both MITM-able and harvestable. `disable` is excluded: it\n * negotiates no TLS at all, so there is no key exchange to harvest (a plaintext\n * concern, out of scope for a PQC-readiness scanner).\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n DOC_EXTENSIONS,\n eachMatch,\n findingFromRule,\n hasExtension,\n maskCommentLines,\n} from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO, CWE_CERT_VALIDATION } from \"../cwe.js\";\n\nconst RE_PGCRYPTO = /\\bpgp_pub_(?:encrypt|decrypt)\\b/g;\n// Matches the no-verification TLS modes across libpq (`sslmode=require`), the\n// `PGSSLMODE` env var, MySQL (`ssl-mode=PREFERRED`/`REQUIRED`, hyphenated), and YAML\n// connection config (`sslmode: require`) — i.e. an optional `pg` prefix, `-`/`_` or no\n// separator, `:` or `=`, and the libpq/MySQL value spellings.\nconst RE_WEAK_SSLMODE =\n /\\b(?:pg)?ssl[-_]?mode\\s*[:=]\\s*[\"']?(?:allow|prefer(?:red)?|require[d]?)\\b/gi;\n// SQL Server TDE: the database encryption key is protected by a classical RSA\n// asymmetric key / certificate. `CREATE ASYMMETRIC KEY … WITH ALGORITHM = RSA_2048`\n// is the distinctive form; the span between the two clauses is lazily bounded to keep\n// matching linear.\nconst RE_MSSQL_TDE_RSA = /\\bCREATE\\s+ASYMMETRIC\\s+KEY\\b[\\s\\S]{0,300}?\\bALGORITHM\\s*=\\s*RSA_\\d+/gi;\n\nconst RULE_PGCRYPTO: RuleMeta = {\n id: \"db-pgcrypto-pubkey\",\n title: \"pgcrypto public-key encryption\",\n description: \"Postgres pgcrypto pgp_pub_encrypt / pgp_pub_decrypt (RSA/ElGamal) on stored data\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Column data is encrypted with pgcrypto public-key crypto (classical RSA/ElGamal); stored ciphertext is harvest-now-decrypt-later exposed.\",\n remediation:\n \"Plan migration to a post-quantum KEM (ML-KEM-768) envelope for at-rest data; re-encrypt long-lived rows.\",\n};\nconst RULE_MSSQL_TDE_RSA: RuleMeta = {\n id: \"db-tde-rsa\",\n title: \"SQL Server TDE RSA key\",\n description: \"SQL Server CREATE ASYMMETRIC KEY ... WITH ALGORITHM = RSA_* (TDE key protection)\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Transparent Data Encryption protects the database encryption key with a classical RSA asymmetric key; the at-rest data is harvest-now-decrypt-later exposed once the RSA key falls.\",\n remediation:\n \"Plan migration to a PQC KEM (ML-KEM-768) for TDE key protection as the database engine adds support; re-key long-lived encrypted databases.\",\n};\nconst RULE_WEAK_SSLMODE: RuleMeta = {\n id: \"db-weak-sslmode\",\n title: \"Database sslmode without verification\",\n description: \"libpq sslmode is allow/prefer/require (no certificate verification)\",\n category: \"tls\",\n severity: \"medium\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_CERT_VALIDATION,\n message:\n \"Database sslmode does not verify the server certificate; the classical TLS session is MITM-able and its key exchange is harvestable.\",\n remediation:\n \"Use sslmode=verify-full and TLS 1.3; track PQC-hybrid KEX (X25519MLKEM768) for database transport.\",\n};\n\n/** Detects classical database at-rest / transport crypto. */\nexport const databaseDetector: Detector = {\n id: \"database-crypto\",\n description:\n \"Classical crypto in database usage (pgcrypto public-key, SQL Server TDE, weak client sslmode)\",\n scope: \"config\",\n language: \"any\",\n rules: [RULE_PGCRYPTO, RULE_MSSQL_TDE_RSA, RULE_WEAK_SSLMODE],\n // Skip prose/docs: a README showing `sslmode=require` is not a live connection string.\n appliesTo: (f) => !hasExtension(f, DOC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n const findings: Finding[] = [];\n // Mask SQL (`--`, single-line `/* … */`), shell/ini (`#`), and ini (`;`) line\n // comments: a commented `-- pgp_pub_encrypt` or `; sslmode=require` is not active.\n const scan = maskCommentLines(content, [\"#\", \"--\", \";\", \"/*\"]);\n if (file.toLowerCase().endsWith(\".sql\") && content.includes(\"pgp_pub_\")) {\n eachMatch(RE_PGCRYPTO, scan, (m) =>\n findings.push(\n findingFromRule(RULE_PGCRYPTO, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n }\n const lc = content.toLowerCase();\n // Cheap prefilter; matches the separators the RE_WEAK_SSLMODE regex accepts\n // (`sslmode`, `ssl-mode`, `ssl_mode`) so no supported form is gated out.\n if (lc.includes(\"sslmode\") || lc.includes(\"ssl-mode\") || lc.includes(\"ssl_mode\")) {\n eachMatch(RE_WEAK_SSLMODE, scan, (m) =>\n findings.push(\n findingFromRule(RULE_WEAK_SSLMODE, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n }\n // SQL Server TDE: a classical RSA asymmetric key protecting the DEK.\n if (lc.includes(\"asymmetric key\")) {\n eachMatch(RE_MSSQL_TDE_RSA, scan, (m) =>\n findings.push(\n findingFromRule(RULE_MSSQL_TDE_RSA, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n }\n return findings;\n },\n};\n"]}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Config detector: classical DKIM email-signing keys and algorithms in DNS
3
+ * zone files, mail-server signer config (OpenDKIM), and captured
4
+ * `DKIM-Signature:` header fields (RFC 6376 + RFC 8463).
5
+ *
6
+ * DKIM (DomainKeys Identified Mail) authenticates the *origin* of email: the
7
+ * sending MTA signs selected header/body content with a private key, publishes
8
+ * the matching public key as a DNS TXT record at
9
+ * `<selector>._domainkey.<domain>`, and receivers verify the `DKIM-Signature`
10
+ * header against that published key. Like DNSSEC (see `dnssec.ts`), this is a
11
+ * pure *signature* surface: there is no confidentiality to harvest, so a DKIM
12
+ * key is NOT harvest-now-decrypt-later exposed (`hndl: false`). What breaks
13
+ * once a cryptographically-relevant quantum computer (CRQC) exists is
14
+ * unforgeability — an attacker who can derive the private key from the
15
+ * published public key can mint valid `DKIM-Signature` headers and spoof mail
16
+ * that passes DKIM/DMARC. So these findings are `category: "signature"`.
17
+ *
18
+ * Crypto surface, mapped to {@link AlgorithmFamily}:
19
+ * - `k=rsa` DNS key-type tag / `a=rsa-sha256` / `a=rsa-sha1` (DKIM-Signature
20
+ * algorithm tag) / OpenDKIM `SigningAlgorithm rsa-sha256|rsa-sha1` → RSA
21
+ * - `k=ed25519` DNS key-type tag / `a=ed25519-sha256` / OpenDKIM
22
+ * `SigningAlgorithm ed25519-sha256` (RFC 8463) → EdDSA
23
+ *
24
+ * Ed25519 DKIM (RFC 8463) is MODERN but still CLASSICAL: rotating an RSA DKIM
25
+ * key to Ed25519 shrinks the DNS record and is good hygiene, but it is NOT a
26
+ * post-quantum fix — an Ed25519 signature is just as forgeable under a CRQC.
27
+ * The remediation text says so honestly: there is no standardized PQC DKIM yet;
28
+ * the only real action is to track IETF work.
29
+ *
30
+ * Two match shapes are covered per family, both gated to a small config
31
+ * extension set AND to a file-level DKIM marker (see `hasDkimMarker`) so the
32
+ * short tag tokens (`k=rsa`, `a=rsa-sha256`) can't fire on unrelated config:
33
+ *
34
+ * 1. DNS KEY-TYPE tag — `k=rsa` / `k=ed25519` inside a published DKIM TXT
35
+ * record (`v=DKIM1; k=rsa; p=MIGf...`). Whitespace around `=` is tolerated.
36
+ * 2. SIGNING-ALGORITHM tag — the `a=` tag of a `DKIM-Signature` header
37
+ * (`a=rsa-sha256`, `a=ed25519-sha256`) and the OpenDKIM `SigningAlgorithm`
38
+ * directive (`SigningAlgorithm rsa-sha256`). The RSA variant deliberately
39
+ * also matches the legacy `rsa-sha1`.
40
+ *
41
+ * Fast reject: `detect()` bails unless the file carries a DKIM-specific marker
42
+ * (`v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, an OpenDKIM
43
+ * `SigningAlgorithm` directive, or a bare `k=rsa`/`k=ed25519` key-type tag).
44
+ * This stops the generic-looking `k=rsa` / `a=rsa-sha256` tokens from firing on
45
+ * an unrelated `.conf` that happens to contain an `a=`/`k=` assignment for its
46
+ * own reasons. Prose is excluded twice over: `appliesTo` restricts to config
47
+ * extensions (README/`.md` never applies), and `detect()` re-guards against
48
+ * {@link DOC_EXTENSIONS} defensively so a docs file that slipped through a
49
+ * broader gate could never be flagged for mentioning `k=rsa` in a sentence.
50
+ */
51
+ import type { Detector } from "../types.js";
52
+ /** Detects classical DKIM signing keys/algorithms in zone files and mail signer config. */
53
+ export declare const dkimDetector: Detector;
54
+ //# sourceMappingURL=dkim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dkim.d.ts","sourceRoot":"","sources":["../../src/detectors/dkim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA2F/D,2FAA2F;AAC3F,eAAO,MAAM,YAAY,EAAE,QA6B1B,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { DOC_EXTENSIONS, eachMatch, findingFromRule, hasExtension, maskCommentLines, } from "../detect-utils.js";
2
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
3
+ // DKIM records live in DNS zone files (`.zone`/`.db`), mail-server signer config
4
+ // (`.conf`), and captured header/TXT fixtures (`.txt`). Gated further by
5
+ // `hasDkimMarker` so the short tag tokens can't fire on unrelated config.
6
+ const DKIM_EXTENSIONS = [".zone", ".db", ".conf", ".txt"];
7
+ // --- RSA (k=rsa / rsa-sha256 / rsa-sha1) ---
8
+ // DNS key-type tag: `k=rsa` in a published DKIM TXT record. Whitespace tolerated
9
+ // around `=`; case-insensitive since tags are conventionally lowercase but not
10
+ // required to be. `\b` after `rsa` keeps it from matching `rsa-...` substrings.
11
+ const RE_RSA_KEYTAG = /\bk\s*=\s*rsa\b/gi;
12
+ // DKIM-Signature `a=` tag: `a=rsa-sha256` / `a=rsa-sha1`.
13
+ const RE_RSA_ALGTAG = /\ba\s*=\s*rsa-sha(?:256|1)\b/gi;
14
+ // OpenDKIM signer directive: `SigningAlgorithm rsa-sha256` / `rsa-sha1`.
15
+ const RE_RSA_OPENDKIM = /\bSigningAlgorithm\s+rsa-sha(?:256|1)\b/gi;
16
+ // --- EdDSA (k=ed25519 / ed25519-sha256, RFC 8463) ---
17
+ const RE_EDDSA_KEYTAG = /\bk\s*=\s*ed25519\b/gi;
18
+ const RE_EDDSA_ALGTAG = /\ba\s*=\s*ed25519-sha256\b/gi;
19
+ const RE_EDDSA_OPENDKIM = /\bSigningAlgorithm\s+ed25519-sha256\b/gi;
20
+ const RULE_DKIM_RSA = {
21
+ id: "dkim-rsa-key",
22
+ title: "DKIM RSA signing key/algorithm",
23
+ description: "DKIM email signing configured with a classical RSA key/algorithm (k=rsa, a=rsa-sha256/rsa-sha1)",
24
+ category: "signature",
25
+ severity: "medium",
26
+ confidence: "high",
27
+ algorithm: "RSA",
28
+ hndl: false,
29
+ cwe: CWE_BROKEN_CRYPTO,
30
+ message: "DKIM email signing uses a classical RSA key/algorithm (k=rsa / rsa-sha256 / rsa-sha1); DKIM-Signature headers become forgeable once a CRQC exists, allowing DKIM/DMARC-passing spoofed mail.",
31
+ remediation: "There is no standardized post-quantum DKIM algorithm yet — track IETF work; note that rotating RSA to Ed25519 (RFC 8463) is good hygiene but NOT a PQC fix, since Ed25519 is still classical and equally forgeable under a CRQC.",
32
+ };
33
+ const RULE_DKIM_EDDSA = {
34
+ id: "dkim-ed25519-key",
35
+ title: "DKIM Ed25519 signing key/algorithm",
36
+ description: "DKIM email signing configured with a classical Ed25519 key/algorithm (k=ed25519, a=ed25519-sha256, RFC 8463)",
37
+ category: "signature",
38
+ severity: "medium",
39
+ confidence: "high",
40
+ algorithm: "EdDSA",
41
+ hndl: false,
42
+ cwe: CWE_BROKEN_CRYPTO,
43
+ message: "DKIM email signing uses an Ed25519 key/algorithm (k=ed25519 / ed25519-sha256, RFC 8463) — modern but still classical; DKIM-Signature headers become forgeable once a CRQC exists.",
44
+ remediation: "There is no standardized post-quantum DKIM algorithm yet — track IETF work. Ed25519 is already the smaller/modern DKIM choice but is NOT post-quantum: it stays forgeable under a CRQC, so no rotation today resolves the quantum exposure.",
45
+ };
46
+ /**
47
+ * True when `content` carries a DKIM-specific marker. Deliberately does NOT accept
48
+ * a bare `k=rsa` / `k=ed25519` tag as its own marker: that tag is exactly what the
49
+ * RSA/EdDSA rules match, so treating it as the gate would be self-satisfying and
50
+ * fire on unrelated prose ("set `k=rsa` for the legacy importer"). A real DKIM
51
+ * record always also carries `v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, or
52
+ * the OpenDKIM `SigningAlgorithm` directive — one of those must be present.
53
+ */
54
+ function hasDkimMarker(content) {
55
+ return (/\bDKIM1\b/i.test(content) ||
56
+ content.includes("_domainkey") ||
57
+ /DKIM-Signature/i.test(content) ||
58
+ /\bSigningAlgorithm\b/i.test(content));
59
+ }
60
+ const DKIM_RULES = [
61
+ { meta: RULE_DKIM_RSA, res: [RE_RSA_KEYTAG, RE_RSA_ALGTAG, RE_RSA_OPENDKIM] },
62
+ { meta: RULE_DKIM_EDDSA, res: [RE_EDDSA_KEYTAG, RE_EDDSA_ALGTAG, RE_EDDSA_OPENDKIM] },
63
+ ];
64
+ /** Detects classical DKIM signing keys/algorithms in zone files and mail signer config. */
65
+ export const dkimDetector = {
66
+ id: "dkim-crypto",
67
+ description: "Classical DKIM email signing keys/algorithms in zone files / mail signer config",
68
+ scope: "config",
69
+ language: "any",
70
+ rules: DKIM_RULES.map((r) => r.meta),
71
+ appliesTo: (f) => hasExtension(f, DKIM_EXTENSIONS) && !hasExtension(f, DOC_EXTENSIONS),
72
+ detect({ file, content }) {
73
+ // Defensive doc guard: even if a broader gate ever let a prose file through,
74
+ // a README mentioning `k=rsa` in a sentence must never be flagged.
75
+ if (hasExtension(file, DOC_EXTENSIONS))
76
+ return [];
77
+ if (!hasDkimMarker(content))
78
+ return [];
79
+ // Mask whole comment lines so a commented-out record can't fire: zone files
80
+ // use `;`, mail/OpenDKIM config uses `#` and `//`. Offsets are preserved so
81
+ // finding line/column/snippet stay exact for the live lines that remain.
82
+ const scan = maskCommentLines(content, [";", "#", "//"]);
83
+ const findings = [];
84
+ for (const { meta, res } of DKIM_RULES) {
85
+ for (const re of res) {
86
+ eachMatch(re, scan, (m) => findings.push(findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length })));
87
+ }
88
+ }
89
+ return findings;
90
+ },
91
+ };
92
+ //# sourceMappingURL=dkim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dkim.js","sourceRoot":"","sources":["../../src/detectors/dkim.ts"],"names":[],"mappings":"AAmDA,OAAO,EACL,cAAc,EACd,SAAS,EACT,eAAe,EACf,YAAY,EACZ,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,iFAAiF;AACjF,yEAAyE;AACzE,0EAA0E;AAC1E,MAAM,eAAe,GAAsB,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAE7E,8CAA8C;AAC9C,iFAAiF;AACjF,+EAA+E;AAC/E,gFAAgF;AAChF,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAC1C,0DAA0D;AAC1D,MAAM,aAAa,GAAG,gCAAgC,CAAC;AACvD,yEAAyE;AACzE,MAAM,eAAe,GAAG,2CAA2C,CAAC;AAEpE,uDAAuD;AACvD,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAChD,MAAM,eAAe,GAAG,8BAA8B,CAAC;AACvD,MAAM,iBAAiB,GAAG,yCAAyC,CAAC;AAEpE,MAAM,aAAa,GAAa;IAC9B,EAAE,EAAE,cAAc;IAClB,KAAK,EAAE,gCAAgC;IACvC,WAAW,EACT,iGAAiG;IACnG,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,KAAK;IAChB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,8LAA8L;IAChM,WAAW,EACT,kOAAkO;CACrO,CAAC;AAEF,MAAM,eAAe,GAAa;IAChC,EAAE,EAAE,kBAAkB;IACtB,KAAK,EAAE,oCAAoC;IAC3C,WAAW,EACT,8GAA8G;IAChH,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,mLAAmL;IACrL,WAAW,EACT,6OAA6O;CAChP,CAAC;AAEF;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,CACL,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;QAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC;QAC/B,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CACtC,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,GAAwB;IACtC,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE,aAAa,EAAE,eAAe,CAAC,EAAE;IAC7E,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE,eAAe,EAAE,iBAAiB,CAAC,EAAE;CACtF,CAAC;AAEF,2FAA2F;AAC3F,MAAM,CAAC,MAAM,YAAY,GAAa;IACpC,EAAE,EAAE,aAAa;IACjB,WAAW,EAAE,iFAAiF;IAC9F,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,cAAc,CAAC;IACtF,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;QACtB,6EAA6E;QAC7E,mEAAmE;QACnE,IAAI,YAAY,CAAC,IAAI,EAAE,cAAc,CAAC;YAAE,OAAO,EAAE,CAAC;QAClD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAEvC,4EAA4E;QAC5E,4EAA4E;QAC5E,yEAAyE;QACzE,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,UAAU,EAAE,CAAC;YACvC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBACrB,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CACxB,QAAQ,CAAC,IAAI,CACX,eAAe,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CACnF,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC","sourcesContent":["/**\n * Config detector: classical DKIM email-signing keys and algorithms in DNS\n * zone files, mail-server signer config (OpenDKIM), and captured\n * `DKIM-Signature:` header fields (RFC 6376 + RFC 8463).\n *\n * DKIM (DomainKeys Identified Mail) authenticates the *origin* of email: the\n * sending MTA signs selected header/body content with a private key, publishes\n * the matching public key as a DNS TXT record at\n * `<selector>._domainkey.<domain>`, and receivers verify the `DKIM-Signature`\n * header against that published key. Like DNSSEC (see `dnssec.ts`), this is a\n * pure *signature* surface: there is no confidentiality to harvest, so a DKIM\n * key is NOT harvest-now-decrypt-later exposed (`hndl: false`). What breaks\n * once a cryptographically-relevant quantum computer (CRQC) exists is\n * unforgeability — an attacker who can derive the private key from the\n * published public key can mint valid `DKIM-Signature` headers and spoof mail\n * that passes DKIM/DMARC. So these findings are `category: \"signature\"`.\n *\n * Crypto surface, mapped to {@link AlgorithmFamily}:\n * - `k=rsa` DNS key-type tag / `a=rsa-sha256` / `a=rsa-sha1` (DKIM-Signature\n * algorithm tag) / OpenDKIM `SigningAlgorithm rsa-sha256|rsa-sha1` → RSA\n * - `k=ed25519` DNS key-type tag / `a=ed25519-sha256` / OpenDKIM\n * `SigningAlgorithm ed25519-sha256` (RFC 8463) → EdDSA\n *\n * Ed25519 DKIM (RFC 8463) is MODERN but still CLASSICAL: rotating an RSA DKIM\n * key to Ed25519 shrinks the DNS record and is good hygiene, but it is NOT a\n * post-quantum fix — an Ed25519 signature is just as forgeable under a CRQC.\n * The remediation text says so honestly: there is no standardized PQC DKIM yet;\n * the only real action is to track IETF work.\n *\n * Two match shapes are covered per family, both gated to a small config\n * extension set AND to a file-level DKIM marker (see `hasDkimMarker`) so the\n * short tag tokens (`k=rsa`, `a=rsa-sha256`) can't fire on unrelated config:\n *\n * 1. DNS KEY-TYPE tag — `k=rsa` / `k=ed25519` inside a published DKIM TXT\n * record (`v=DKIM1; k=rsa; p=MIGf...`). Whitespace around `=` is tolerated.\n * 2. SIGNING-ALGORITHM tag — the `a=` tag of a `DKIM-Signature` header\n * (`a=rsa-sha256`, `a=ed25519-sha256`) and the OpenDKIM `SigningAlgorithm`\n * directive (`SigningAlgorithm rsa-sha256`). The RSA variant deliberately\n * also matches the legacy `rsa-sha1`.\n *\n * Fast reject: `detect()` bails unless the file carries a DKIM-specific marker\n * (`v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, an OpenDKIM\n * `SigningAlgorithm` directive, or a bare `k=rsa`/`k=ed25519` key-type tag).\n * This stops the generic-looking `k=rsa` / `a=rsa-sha256` tokens from firing on\n * an unrelated `.conf` that happens to contain an `a=`/`k=` assignment for its\n * own reasons. Prose is excluded twice over: `appliesTo` restricts to config\n * extensions (README/`.md` never applies), and `detect()` re-guards against\n * {@link DOC_EXTENSIONS} defensively so a docs file that slipped through a\n * broader gate could never be flagged for mentioning `k=rsa` in a sentence.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n DOC_EXTENSIONS,\n eachMatch,\n findingFromRule,\n hasExtension,\n maskCommentLines,\n} from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\n// DKIM records live in DNS zone files (`.zone`/`.db`), mail-server signer config\n// (`.conf`), and captured header/TXT fixtures (`.txt`). Gated further by\n// `hasDkimMarker` so the short tag tokens can't fire on unrelated config.\nconst DKIM_EXTENSIONS: readonly string[] = [\".zone\", \".db\", \".conf\", \".txt\"];\n\n// --- RSA (k=rsa / rsa-sha256 / rsa-sha1) ---\n// DNS key-type tag: `k=rsa` in a published DKIM TXT record. Whitespace tolerated\n// around `=`; case-insensitive since tags are conventionally lowercase but not\n// required to be. `\\b` after `rsa` keeps it from matching `rsa-...` substrings.\nconst RE_RSA_KEYTAG = /\\bk\\s*=\\s*rsa\\b/gi;\n// DKIM-Signature `a=` tag: `a=rsa-sha256` / `a=rsa-sha1`.\nconst RE_RSA_ALGTAG = /\\ba\\s*=\\s*rsa-sha(?:256|1)\\b/gi;\n// OpenDKIM signer directive: `SigningAlgorithm rsa-sha256` / `rsa-sha1`.\nconst RE_RSA_OPENDKIM = /\\bSigningAlgorithm\\s+rsa-sha(?:256|1)\\b/gi;\n\n// --- EdDSA (k=ed25519 / ed25519-sha256, RFC 8463) ---\nconst RE_EDDSA_KEYTAG = /\\bk\\s*=\\s*ed25519\\b/gi;\nconst RE_EDDSA_ALGTAG = /\\ba\\s*=\\s*ed25519-sha256\\b/gi;\nconst RE_EDDSA_OPENDKIM = /\\bSigningAlgorithm\\s+ed25519-sha256\\b/gi;\n\nconst RULE_DKIM_RSA: RuleMeta = {\n id: \"dkim-rsa-key\",\n title: \"DKIM RSA signing key/algorithm\",\n description:\n \"DKIM email signing configured with a classical RSA key/algorithm (k=rsa, a=rsa-sha256/rsa-sha1)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DKIM email signing uses a classical RSA key/algorithm (k=rsa / rsa-sha256 / rsa-sha1); DKIM-Signature headers become forgeable once a CRQC exists, allowing DKIM/DMARC-passing spoofed mail.\",\n remediation:\n \"There is no standardized post-quantum DKIM algorithm yet — track IETF work; note that rotating RSA to Ed25519 (RFC 8463) is good hygiene but NOT a PQC fix, since Ed25519 is still classical and equally forgeable under a CRQC.\",\n};\n\nconst RULE_DKIM_EDDSA: RuleMeta = {\n id: \"dkim-ed25519-key\",\n title: \"DKIM Ed25519 signing key/algorithm\",\n description:\n \"DKIM email signing configured with a classical Ed25519 key/algorithm (k=ed25519, a=ed25519-sha256, RFC 8463)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DKIM email signing uses an Ed25519 key/algorithm (k=ed25519 / ed25519-sha256, RFC 8463) — modern but still classical; DKIM-Signature headers become forgeable once a CRQC exists.\",\n remediation:\n \"There is no standardized post-quantum DKIM algorithm yet — track IETF work. Ed25519 is already the smaller/modern DKIM choice but is NOT post-quantum: it stays forgeable under a CRQC, so no rotation today resolves the quantum exposure.\",\n};\n\n/**\n * True when `content` carries a DKIM-specific marker. Deliberately does NOT accept\n * a bare `k=rsa` / `k=ed25519` tag as its own marker: that tag is exactly what the\n * RSA/EdDSA rules match, so treating it as the gate would be self-satisfying and\n * fire on unrelated prose (\"set `k=rsa` for the legacy importer\"). A real DKIM\n * record always also carries `v=DKIM1`, `_domainkey`, a `DKIM-Signature` field, or\n * the OpenDKIM `SigningAlgorithm` directive — one of those must be present.\n */\nfunction hasDkimMarker(content: string): boolean {\n return (\n /\\bDKIM1\\b/i.test(content) ||\n content.includes(\"_domainkey\") ||\n /DKIM-Signature/i.test(content) ||\n /\\bSigningAlgorithm\\b/i.test(content)\n );\n}\n\ninterface DkimRule {\n meta: RuleMeta;\n res: readonly RegExp[];\n}\n\nconst DKIM_RULES: readonly DkimRule[] = [\n { meta: RULE_DKIM_RSA, res: [RE_RSA_KEYTAG, RE_RSA_ALGTAG, RE_RSA_OPENDKIM] },\n { meta: RULE_DKIM_EDDSA, res: [RE_EDDSA_KEYTAG, RE_EDDSA_ALGTAG, RE_EDDSA_OPENDKIM] },\n];\n\n/** Detects classical DKIM signing keys/algorithms in zone files and mail signer config. */\nexport const dkimDetector: Detector = {\n id: \"dkim-crypto\",\n description: \"Classical DKIM email signing keys/algorithms in zone files / mail signer config\",\n scope: \"config\",\n language: \"any\",\n rules: DKIM_RULES.map((r) => r.meta),\n appliesTo: (f) => hasExtension(f, DKIM_EXTENSIONS) && !hasExtension(f, DOC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n // Defensive doc guard: even if a broader gate ever let a prose file through,\n // a README mentioning `k=rsa` in a sentence must never be flagged.\n if (hasExtension(file, DOC_EXTENSIONS)) return [];\n if (!hasDkimMarker(content)) return [];\n\n // Mask whole comment lines so a commented-out record can't fire: zone files\n // use `;`, mail/OpenDKIM config uses `#` and `//`. Offsets are preserved so\n // finding line/column/snippet stay exact for the live lines that remain.\n const scan = maskCommentLines(content, [\";\", \"#\", \"//\"]);\n const findings: Finding[] = [];\n for (const { meta, res } of DKIM_RULES) {\n for (const re of res) {\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n }\n }\n return findings;\n },\n};\n"]}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Config detector: classical DNSSEC signing algorithms in zone files and
3
+ * signer/resolver config (BIND, Knot DNS, PowerDNS, `ldns-signzone` /
4
+ * `dnssec-signzone` invocations recorded in ops config).
5
+ *
6
+ * DNSSEC is "communication between things" infrastructure: resolvers verify
7
+ * DNSKEY/RRSIG chains to authenticate zone data. Unlike a KEM or key-exchange
8
+ * finding, a DNSSEC signing key is not harvest-now-decrypt-later exposed —
9
+ * there is no confidentiality to harvest — but every classical signature it
10
+ * produces becomes FORGEABLE the moment a cryptographically-relevant quantum
11
+ * computer (CRQC) exists. A forged DNSKEY/RRSIG chain lets an attacker spoof
12
+ * DNSSEC-validated records (cache poisoning that a validating resolver would
13
+ * otherwise reject). So these findings are `category: "signature"`,
14
+ * `hndl: false`.
15
+ *
16
+ * RFC 8624 / IANA "DNSSEC Algorithm Numbers" families covered, mapped to
17
+ * {@link AlgorithmFamily}:
18
+ * - RSASHA1 (5), RSASHA1-NSEC3-SHA1 (7), RSASHA256 (8), RSASHA512 (10) → RSA
19
+ * - ECDSAP256SHA256 (13), ECDSAP384SHA384 (14) → ECDSA
20
+ * - ED25519 (15), ED448 (16) → EdDSA
21
+ * - DSA (3), DSA-NSEC3-SHA1 (6) → DSA
22
+ *
23
+ * Two independent match shapes per family, both gated to `.zone` / `.db` /
24
+ * `.conf` files AND to a file-level DNSSEC marker (see `hasDnssecMarker`
25
+ * below) so the algorithm tokens can't fire on unrelated config:
26
+ *
27
+ * 1. NAMED form — the algorithm's mnemonic name, as written in signer/policy
28
+ * config (Knot `algorithm RSASHA256;`, a `dnssec-policy` block, BIND
29
+ * `algorithm: ecdsap256sha256;`) or on an `ldns-signzone -a ECDSAP256SHA256`
30
+ * / `dnssec-signzone -a ECDSAP256SHA256` command line. These mnemonics
31
+ * (`RSASHA256`, `ECDSAP384SHA384`, `ED25519`, …) are distinctive enough to
32
+ * match bare, EXCEPT plain `DSA`, which is too generic a token on its own
33
+ * (used well outside DNSSEC); that one is only matched immediately after
34
+ * an `algorithm` keyword.
35
+ * 2. STRUCTURAL form — the DNSKEY presentation-format RDATA, e.g.
36
+ * `example.com. IN DNSKEY 257 3 8 AwEAAd...`, where the fields after the
37
+ * `DNSKEY` keyword are `flags protocol algorithm public-key` (RFC 4034
38
+ * §2). Anchoring on the literal `DNSKEY` keyword AND the literal
39
+ * protocol value `3` (always 3 for DNSSEC) before a restricted algorithm
40
+ * number keeps this precise despite being numeric. Bare algorithm
41
+ * numbers with no such anchor are deliberately NOT matched anywhere —
42
+ * `algorithm 8;` alone is far too ambiguous to key off of.
43
+ *
44
+ * Fast reject: `detect()` bails unless the file contains `DNSKEY`, `RRSIG`,
45
+ * a case-insensitive `dnssec` substring (present in `dnssec-policy`,
46
+ * `dnssec-signing: on`, etc. in any real signer config), or an
47
+ * `ldns-signzone` / `dnssec-signzone` invocation. This stops the DSA/EdDSA
48
+ * named-form tokens — which are individually generic outside DNSSEC — from
49
+ * firing on, say, an unrelated SSH or TLS `.conf` file that happens to
50
+ * mention `ED25519` or `DSA` for its own reasons.
51
+ */
52
+ import type { Detector } from "../types.js";
53
+ /** Detects classical DNSSEC signing algorithms in zone files and signer config. */
54
+ export declare const dnssecDetector: Detector;
55
+ //# sourceMappingURL=dnssec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dnssec.d.ts","sourceRoot":"","sources":["../../src/detectors/dnssec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAoH/D,mFAAmF;AACnF,eAAO,MAAM,cAAc,EAAE,QA0B5B,CAAC"}
@@ -0,0 +1,116 @@
1
+ import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
2
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
3
+ const DNSSEC_EXTENSIONS = [".zone", ".db", ".conf"];
4
+ // IANA DNSSEC algorithm numbers, grouped by family.
5
+ const NUM_RSA = "5|7|8|10"; // RSASHA1, RSASHA1-NSEC3-SHA1, RSASHA256, RSASHA512
6
+ const NUM_ECDSA = "13|14"; // ECDSAP256SHA256, ECDSAP384SHA384
7
+ const NUM_EDDSA = "15|16"; // ED25519, ED448
8
+ const NUM_DSA = "3|6"; // DSA, DSA-NSEC3-SHA1
9
+ // --- NAMED form: mnemonic algorithm names in signer/policy config or CLI args. ---
10
+ // Case-insensitive: BIND `dnssec-policy` and Knot config conventionally write the
11
+ // mnemonics in lowercase (`algorithm rsasha256;`, `algorithm ed25519;`), so a
12
+ // case-sensitive match would miss the primary real-world signer-config syntax. Still
13
+ // gated by `hasDnssecMarker` + the DNSSEC extensions, so generic tokens can't fire.
14
+ const RE_NAMED_RSA = /\bRSASHA(?:256|512|1(?:-NSEC3-SHA1)?)\b/gi;
15
+ const RE_NAMED_ECDSA = /\bECDSAP(?:256SHA256|384SHA384)\b/gi;
16
+ const RE_NAMED_EDDSA = /\bED(?:25519|448)\b/gi;
17
+ // Bare "DSA" is too generic to key off alone; require it right after an
18
+ // `algorithm` keyword (Knot/BIND config / dnssec-policy style), optionally
19
+ // quoted or separated by `:`/`=`.
20
+ const RE_NAMED_DSA = /\balgorithm\s*[:=]?\s*"?DSA(?:-NSEC3-SHA1)?"?\b/gi;
21
+ // --- STRUCTURAL form: `[C]DNSKEY <flags> 3 <algorithm>` presentation RDATA (RFC 4034
22
+ // §2). `C?` also matches CDNSKEY (child-zone key with identical RDATA, RFC 7344). ---
23
+ const RE_DNSKEY_RSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_RSA})\\b`, "g");
24
+ const RE_DNSKEY_ECDSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_ECDSA})\\b`, "g");
25
+ const RE_DNSKEY_EDDSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_EDDSA})\\b`, "g");
26
+ const RE_DNSKEY_DSA = new RegExp(`\\bC?DNSKEY\\s+\\d+\\s+3\\s+(?:${NUM_DSA})\\b`, "g");
27
+ const RULE_DNSSEC_RSA = {
28
+ id: "dnssec-rsa-sig",
29
+ title: "DNSSEC RSA signing algorithm",
30
+ description: "DNSSEC zone signed with a classical RSA algorithm (RSASHA1/256/512)",
31
+ category: "signature",
32
+ severity: "medium",
33
+ confidence: "high",
34
+ algorithm: "RSA",
35
+ hndl: false,
36
+ cwe: CWE_BROKEN_CRYPTO,
37
+ message: "DNSSEC zone is signed with a classical RSA algorithm (RSASHA1/RSASHA1-NSEC3-SHA1/RSASHA256/RSASHA512); DNSKEY/RRSIG signatures become forgeable once a CRQC exists.",
38
+ remediation: "Track IETF dnsop post-quantum DNSSEC signing work (ML-DSA); plan re-signing with a PQC algorithm once assigned an IANA DNSSEC algorithm number.",
39
+ };
40
+ const RULE_DNSSEC_ECDSA = {
41
+ id: "dnssec-ecdsa-sig",
42
+ title: "DNSSEC ECDSA signing algorithm",
43
+ description: "DNSSEC zone signed with a classical ECDSA algorithm (ECDSAP256SHA256/384SHA384)",
44
+ category: "signature",
45
+ severity: "medium",
46
+ confidence: "high",
47
+ algorithm: "ECDSA",
48
+ hndl: false,
49
+ cwe: CWE_BROKEN_CRYPTO,
50
+ message: "DNSSEC zone is signed with a classical ECDSA algorithm (ECDSAP256SHA256/ECDSAP384SHA384); DNSKEY/RRSIG signatures become forgeable once a CRQC exists.",
51
+ remediation: "Track IETF dnsop post-quantum DNSSEC signing work (ML-DSA); plan re-signing with a PQC algorithm once assigned an IANA DNSSEC algorithm number.",
52
+ };
53
+ const RULE_DNSSEC_EDDSA = {
54
+ id: "dnssec-eddsa-sig",
55
+ title: "DNSSEC EdDSA signing algorithm",
56
+ description: "DNSSEC zone signed with a classical EdDSA algorithm (ED25519/ED448)",
57
+ category: "signature",
58
+ severity: "medium",
59
+ confidence: "high",
60
+ algorithm: "EdDSA",
61
+ hndl: false,
62
+ cwe: CWE_BROKEN_CRYPTO,
63
+ message: "DNSSEC zone is signed with a classical EdDSA algorithm (ED25519/ED448); modern but still classical — DNSKEY/RRSIG signatures become forgeable once a CRQC exists.",
64
+ remediation: "Track IETF dnsop post-quantum DNSSEC signing work (ML-DSA); plan re-signing with a PQC algorithm once assigned an IANA DNSSEC algorithm number.",
65
+ };
66
+ const RULE_DNSSEC_DSA = {
67
+ id: "dnssec-dsa-sig",
68
+ title: "DNSSEC DSA signing algorithm (deprecated)",
69
+ description: "DNSSEC zone signed with the deprecated classical DSA algorithm (DSA/DSA-NSEC3-SHA1)",
70
+ category: "signature",
71
+ severity: "medium",
72
+ confidence: "high",
73
+ algorithm: "DSA",
74
+ hndl: false,
75
+ cwe: CWE_BROKEN_CRYPTO,
76
+ message: "DNSSEC zone is signed with DSA/DSA-NSEC3-SHA1 — deprecated by RFC 8624 (MUST NOT sign) and, independent of that, forgeable once a CRQC exists.",
77
+ remediation: "Re-sign with a non-deprecated algorithm today (RFC 8624); track IETF dnsop post-quantum DNSSEC signing work (ML-DSA) for the eventual PQC migration.",
78
+ };
79
+ /** True when `content` carries some DNSSEC-specific marker (not just a bare algorithm token). */
80
+ function hasDnssecMarker(content) {
81
+ return (content.includes("DNSKEY") ||
82
+ content.includes("RRSIG") ||
83
+ /dnssec/i.test(content) ||
84
+ /ldns-signzone|dnssec-signzone/.test(content));
85
+ }
86
+ const DNSSEC_RULES = [
87
+ { meta: RULE_DNSSEC_RSA, res: [RE_NAMED_RSA, RE_DNSKEY_RSA] },
88
+ { meta: RULE_DNSSEC_ECDSA, res: [RE_NAMED_ECDSA, RE_DNSKEY_ECDSA] },
89
+ { meta: RULE_DNSSEC_EDDSA, res: [RE_NAMED_EDDSA, RE_DNSKEY_EDDSA] },
90
+ { meta: RULE_DNSSEC_DSA, res: [RE_NAMED_DSA, RE_DNSKEY_DSA] },
91
+ ];
92
+ /** Detects classical DNSSEC signing algorithms in zone files and signer config. */
93
+ export const dnssecDetector = {
94
+ id: "dnssec-crypto",
95
+ description: "Classical DNSSEC signing algorithms in zone files / signer config",
96
+ scope: "config",
97
+ language: "any",
98
+ rules: DNSSEC_RULES.map((r) => r.meta),
99
+ appliesTo: (f) => hasExtension(f, DNSSEC_EXTENSIONS),
100
+ detect({ file, content }) {
101
+ if (!hasDnssecMarker(content))
102
+ return [];
103
+ // Mask whole comment lines so a commented-out algorithm can't fire: zone files
104
+ // use `;`, named.conf uses `#` and `//`. These extensions are not centrally
105
+ // stripped; offsets are preserved so finding locations stay exact.
106
+ const scan = maskCommentLines(content, [";", "#", "//"]);
107
+ const findings = [];
108
+ for (const { meta, res } of DNSSEC_RULES) {
109
+ for (const re of res) {
110
+ eachMatch(re, scan, (m) => findings.push(findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length })));
111
+ }
112
+ }
113
+ return findings;
114
+ },
115
+ };
116
+ //# sourceMappingURL=dnssec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dnssec.js","sourceRoot":"","sources":["../../src/detectors/dnssec.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical DNSSEC signing algorithms in zone files and\n * signer/resolver config (BIND, Knot DNS, PowerDNS, `ldns-signzone` /\n * `dnssec-signzone` invocations recorded in ops config).\n *\n * DNSSEC is \"communication between things\" infrastructure: resolvers verify\n * DNSKEY/RRSIG chains to authenticate zone data. Unlike a KEM or key-exchange\n * finding, a DNSSEC signing key is not harvest-now-decrypt-later exposed —\n * there is no confidentiality to harvest — but every classical signature it\n * produces becomes FORGEABLE the moment a cryptographically-relevant quantum\n * computer (CRQC) exists. A forged DNSKEY/RRSIG chain lets an attacker spoof\n * DNSSEC-validated records (cache poisoning that a validating resolver would\n * otherwise reject). So these findings are `category: \"signature\"`,\n * `hndl: false`.\n *\n * RFC 8624 / IANA \"DNSSEC Algorithm Numbers\" families covered, mapped to\n * {@link AlgorithmFamily}:\n * - RSASHA1 (5), RSASHA1-NSEC3-SHA1 (7), RSASHA256 (8), RSASHA512 (10) → RSA\n * - ECDSAP256SHA256 (13), ECDSAP384SHA384 (14) → ECDSA\n * - ED25519 (15), ED448 (16) → EdDSA\n * - DSA (3), DSA-NSEC3-SHA1 (6) → DSA\n *\n * Two independent match shapes per family, both gated to `.zone` / `.db` /\n * `.conf` files AND to a file-level DNSSEC marker (see `hasDnssecMarker`\n * below) so the algorithm tokens can't fire on unrelated config:\n *\n * 1. NAMED form — the algorithm's mnemonic name, as written in signer/policy\n * config (Knot `algorithm RSASHA256;`, a `dnssec-policy` block, BIND\n * `algorithm: ecdsap256sha256;`) or on an `ldns-signzone -a ECDSAP256SHA256`\n * / `dnssec-signzone -a ECDSAP256SHA256` command line. These mnemonics\n * (`RSASHA256`, `ECDSAP384SHA384`, `ED25519`, …) are distinctive enough to\n * match bare, EXCEPT plain `DSA`, which is too generic a token on its own\n * (used well outside DNSSEC); that one is only matched immediately after\n * an `algorithm` keyword.\n * 2. STRUCTURAL form — the DNSKEY presentation-format RDATA, e.g.\n * `example.com. IN DNSKEY 257 3 8 AwEAAd...`, where the fields after the\n * `DNSKEY` keyword are `flags protocol algorithm public-key` (RFC 4034\n * §2). Anchoring on the literal `DNSKEY` keyword AND the literal\n * protocol value `3` (always 3 for DNSSEC) before a restricted algorithm\n * number keeps this precise despite being numeric. Bare algorithm\n * numbers with no such anchor are deliberately NOT matched anywhere —\n * `algorithm 8;` alone is far too ambiguous to key off of.\n *\n * Fast reject: `detect()` bails unless the file contains `DNSKEY`, `RRSIG`,\n * a case-insensitive `dnssec` substring (present in `dnssec-policy`,\n * `dnssec-signing: on`, etc. in any real signer config), or an\n * `ldns-signzone` / `dnssec-signzone` invocation. This stops the DSA/EdDSA\n * named-form tokens — which are individually generic outside DNSSEC — from\n * firing on, say, an unrelated SSH or TLS `.conf` file that happens to\n * mention `ED25519` or `DSA` for its own reasons.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule, hasExtension, maskCommentLines } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\nconst DNSSEC_EXTENSIONS: readonly string[] = [\".zone\", \".db\", \".conf\"];\n\n// IANA DNSSEC algorithm numbers, grouped by family.\nconst NUM_RSA = \"5|7|8|10\"; // RSASHA1, RSASHA1-NSEC3-SHA1, RSASHA256, RSASHA512\nconst NUM_ECDSA = \"13|14\"; // ECDSAP256SHA256, ECDSAP384SHA384\nconst NUM_EDDSA = \"15|16\"; // ED25519, ED448\nconst NUM_DSA = \"3|6\"; // DSA, DSA-NSEC3-SHA1\n\n// --- NAMED form: mnemonic algorithm names in signer/policy config or CLI args. ---\n// Case-insensitive: BIND `dnssec-policy` and Knot config conventionally write the\n// mnemonics in lowercase (`algorithm rsasha256;`, `algorithm ed25519;`), so a\n// case-sensitive match would miss the primary real-world signer-config syntax. Still\n// gated by `hasDnssecMarker` + the DNSSEC extensions, so generic tokens can't fire.\nconst RE_NAMED_RSA = /\\bRSASHA(?:256|512|1(?:-NSEC3-SHA1)?)\\b/gi;\nconst RE_NAMED_ECDSA = /\\bECDSAP(?:256SHA256|384SHA384)\\b/gi;\nconst RE_NAMED_EDDSA = /\\bED(?:25519|448)\\b/gi;\n// Bare \"DSA\" is too generic to key off alone; require it right after an\n// `algorithm` keyword (Knot/BIND config / dnssec-policy style), optionally\n// quoted or separated by `:`/`=`.\nconst RE_NAMED_DSA = /\\balgorithm\\s*[:=]?\\s*\"?DSA(?:-NSEC3-SHA1)?\"?\\b/gi;\n\n// --- STRUCTURAL form: `[C]DNSKEY <flags> 3 <algorithm>` presentation RDATA (RFC 4034\n// §2). `C?` also matches CDNSKEY (child-zone key with identical RDATA, RFC 7344). ---\nconst RE_DNSKEY_RSA = new RegExp(`\\\\bC?DNSKEY\\\\s+\\\\d+\\\\s+3\\\\s+(?:${NUM_RSA})\\\\b`, \"g\");\nconst RE_DNSKEY_ECDSA = new RegExp(`\\\\bC?DNSKEY\\\\s+\\\\d+\\\\s+3\\\\s+(?:${NUM_ECDSA})\\\\b`, \"g\");\nconst RE_DNSKEY_EDDSA = new RegExp(`\\\\bC?DNSKEY\\\\s+\\\\d+\\\\s+3\\\\s+(?:${NUM_EDDSA})\\\\b`, \"g\");\nconst RE_DNSKEY_DSA = new RegExp(`\\\\bC?DNSKEY\\\\s+\\\\d+\\\\s+3\\\\s+(?:${NUM_DSA})\\\\b`, \"g\");\n\nconst RULE_DNSSEC_RSA: RuleMeta = {\n id: \"dnssec-rsa-sig\",\n title: \"DNSSEC RSA signing algorithm\",\n description: \"DNSSEC zone signed with a classical RSA algorithm (RSASHA1/256/512)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DNSSEC zone is signed with a classical RSA algorithm (RSASHA1/RSASHA1-NSEC3-SHA1/RSASHA256/RSASHA512); DNSKEY/RRSIG signatures become forgeable once a CRQC exists.\",\n remediation:\n \"Track IETF dnsop post-quantum DNSSEC signing work (ML-DSA); plan re-signing with a PQC algorithm once assigned an IANA DNSSEC algorithm number.\",\n};\nconst RULE_DNSSEC_ECDSA: RuleMeta = {\n id: \"dnssec-ecdsa-sig\",\n title: \"DNSSEC ECDSA signing algorithm\",\n description: \"DNSSEC zone signed with a classical ECDSA algorithm (ECDSAP256SHA256/384SHA384)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DNSSEC zone is signed with a classical ECDSA algorithm (ECDSAP256SHA256/ECDSAP384SHA384); DNSKEY/RRSIG signatures become forgeable once a CRQC exists.\",\n remediation:\n \"Track IETF dnsop post-quantum DNSSEC signing work (ML-DSA); plan re-signing with a PQC algorithm once assigned an IANA DNSSEC algorithm number.\",\n};\nconst RULE_DNSSEC_EDDSA: RuleMeta = {\n id: \"dnssec-eddsa-sig\",\n title: \"DNSSEC EdDSA signing algorithm\",\n description: \"DNSSEC zone signed with a classical EdDSA algorithm (ED25519/ED448)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DNSSEC zone is signed with a classical EdDSA algorithm (ED25519/ED448); modern but still classical — DNSKEY/RRSIG signatures become forgeable once a CRQC exists.\",\n remediation:\n \"Track IETF dnsop post-quantum DNSSEC signing work (ML-DSA); plan re-signing with a PQC algorithm once assigned an IANA DNSSEC algorithm number.\",\n};\nconst RULE_DNSSEC_DSA: RuleMeta = {\n id: \"dnssec-dsa-sig\",\n title: \"DNSSEC DSA signing algorithm (deprecated)\",\n description:\n \"DNSSEC zone signed with the deprecated classical DSA algorithm (DSA/DSA-NSEC3-SHA1)\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"DSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"DNSSEC zone is signed with DSA/DSA-NSEC3-SHA1 — deprecated by RFC 8624 (MUST NOT sign) and, independent of that, forgeable once a CRQC exists.\",\n remediation:\n \"Re-sign with a non-deprecated algorithm today (RFC 8624); track IETF dnsop post-quantum DNSSEC signing work (ML-DSA) for the eventual PQC migration.\",\n};\n\n/** True when `content` carries some DNSSEC-specific marker (not just a bare algorithm token). */\nfunction hasDnssecMarker(content: string): boolean {\n return (\n content.includes(\"DNSKEY\") ||\n content.includes(\"RRSIG\") ||\n /dnssec/i.test(content) ||\n /ldns-signzone|dnssec-signzone/.test(content)\n );\n}\n\ninterface DnssecRule {\n meta: RuleMeta;\n res: readonly RegExp[];\n}\n\nconst DNSSEC_RULES: readonly DnssecRule[] = [\n { meta: RULE_DNSSEC_RSA, res: [RE_NAMED_RSA, RE_DNSKEY_RSA] },\n { meta: RULE_DNSSEC_ECDSA, res: [RE_NAMED_ECDSA, RE_DNSKEY_ECDSA] },\n { meta: RULE_DNSSEC_EDDSA, res: [RE_NAMED_EDDSA, RE_DNSKEY_EDDSA] },\n { meta: RULE_DNSSEC_DSA, res: [RE_NAMED_DSA, RE_DNSKEY_DSA] },\n];\n\n/** Detects classical DNSSEC signing algorithms in zone files and signer config. */\nexport const dnssecDetector: Detector = {\n id: \"dnssec-crypto\",\n description: \"Classical DNSSEC signing algorithms in zone files / signer config\",\n scope: \"config\",\n language: \"any\",\n rules: DNSSEC_RULES.map((r) => r.meta),\n appliesTo: (f) => hasExtension(f, DNSSEC_EXTENSIONS),\n detect({ file, content }): Finding[] {\n if (!hasDnssecMarker(content)) return [];\n\n // Mask whole comment lines so a commented-out algorithm can't fire: zone files\n // use `;`, named.conf uses `#` and `//`. These extensions are not centrally\n // stripped; offsets are preserved so finding locations stay exact.\n const scan = maskCommentLines(content, [\";\", \"#\", \"//\"]);\n const findings: Finding[] = [];\n for (const { meta, res } of DNSSEC_RULES) {\n for (const re of res) {\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(meta, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n }\n }\n return findings;\n },\n};\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Source-code detector for classical asymmetric cryptography in Elixir (the BEAM
3
+ * / Phoenix ecosystem), previously uncovered. Handles the three common surfaces:
4
+ *
5
+ * - **Erlang `:crypto`** — `:crypto.generate_key(:rsa|:ecdh|:dh|:eddsa, …)`
6
+ * (the curve atom disambiguates X25519/X448 from a generic ECDH curve) and
7
+ * `:crypto.sign/:crypto.verify(:rsa|:ecdsa|:eddsa, …)`.
8
+ * - **`X509`** (the `x509` hex package) — `X509.PrivateKey.new_rsa` / `new_ec`.
9
+ * - **`JOSE`** (erlang-jose) — `JOSE.JWK.generate_key({:rsa|:ec|:okp, …})`.
10
+ *
11
+ * Lexical, like the other packs; the `:crypto.` atom-module syntax and the
12
+ * `X509.` / `JOSE.` module paths are distinctive, so the false-positive rate is low.
13
+ *
14
+ * HNDL: RSA and (EC/X)DH key agreement are harvest-now-decrypt-later exposed
15
+ * (hndl:true); ECDSA / EdDSA signatures are hndl:false but forgeable.
16
+ */
17
+ import type { Detector } from "../types.js";
18
+ /** Detects classical asymmetric crypto in Elixir (:crypto, X509, JOSE). */
19
+ export declare const elixirDetector: Detector;
20
+ //# sourceMappingURL=elixir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elixir.d.ts","sourceRoot":"","sources":["../../src/detectors/elixir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAmB,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA4IhF,2EAA2E;AAC3E,eAAO,MAAM,cAAc,EAAE,QA8G5B,CAAC"}