@quantakrypto/core 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/dist/cbom-merge.d.ts +9 -0
  2. package/dist/cbom-merge.d.ts.map +1 -0
  3. package/dist/cbom-merge.js +99 -0
  4. package/dist/cbom-merge.js.map +1 -0
  5. package/dist/comments.d.ts +4 -3
  6. package/dist/comments.d.ts.map +1 -1
  7. package/dist/comments.js +53 -33
  8. package/dist/comments.js.map +1 -1
  9. package/dist/dependencies.d.ts.map +1 -1
  10. package/dist/dependencies.js +1 -1
  11. package/dist/dependencies.js.map +1 -1
  12. package/dist/detect-utils.d.ts +5 -1
  13. package/dist/detect-utils.d.ts.map +1 -1
  14. package/dist/detect-utils.js +11 -2
  15. package/dist/detect-utils.js.map +1 -1
  16. package/dist/detectors/c.d.ts +4 -2
  17. package/dist/detectors/c.d.ts.map +1 -1
  18. package/dist/detectors/c.js +192 -1
  19. package/dist/detectors/c.js.map +1 -1
  20. package/dist/detectors/cicd.d.ts +27 -0
  21. package/dist/detectors/cicd.d.ts.map +1 -0
  22. package/dist/detectors/cicd.js +116 -0
  23. package/dist/detectors/cicd.js.map +1 -0
  24. package/dist/detectors/cloud-kms.d.ts +21 -0
  25. package/dist/detectors/cloud-kms.d.ts.map +1 -0
  26. package/dist/detectors/cloud-kms.js +57 -0
  27. package/dist/detectors/cloud-kms.js.map +1 -0
  28. package/dist/detectors/cloudformation.d.ts +30 -0
  29. package/dist/detectors/cloudformation.d.ts.map +1 -0
  30. package/dist/detectors/cloudformation.js +170 -0
  31. package/dist/detectors/cloudformation.js.map +1 -0
  32. package/dist/detectors/database.d.ts +17 -0
  33. package/dist/detectors/database.d.ts.map +1 -0
  34. package/dist/detectors/database.js +60 -0
  35. package/dist/detectors/database.js.map +1 -0
  36. package/dist/detectors/dnssec.d.ts +55 -0
  37. package/dist/detectors/dnssec.d.ts.map +1 -0
  38. package/dist/detectors/dnssec.js +107 -0
  39. package/dist/detectors/dnssec.js.map +1 -0
  40. package/dist/detectors/elixir.d.ts +20 -0
  41. package/dist/detectors/elixir.d.ts.map +1 -0
  42. package/dist/detectors/elixir.js +195 -0
  43. package/dist/detectors/elixir.js.map +1 -0
  44. package/dist/detectors/java.d.ts.map +1 -1
  45. package/dist/detectors/java.js +1 -1
  46. package/dist/detectors/java.js.map +1 -1
  47. package/dist/detectors/jose.d.ts +20 -0
  48. package/dist/detectors/jose.d.ts.map +1 -0
  49. package/dist/detectors/jose.js +62 -0
  50. package/dist/detectors/jose.js.map +1 -0
  51. package/dist/detectors/jwk.d.ts +25 -0
  52. package/dist/detectors/jwk.d.ts.map +1 -0
  53. package/dist/detectors/jwk.js +95 -0
  54. package/dist/detectors/jwk.js.map +1 -0
  55. package/dist/detectors/k8s.d.ts +17 -0
  56. package/dist/detectors/k8s.d.ts.map +1 -0
  57. package/dist/detectors/k8s.js +85 -0
  58. package/dist/detectors/k8s.js.map +1 -0
  59. package/dist/detectors/mesh.d.ts +35 -0
  60. package/dist/detectors/mesh.d.ts.map +1 -0
  61. package/dist/detectors/mesh.js +109 -0
  62. package/dist/detectors/mesh.js.map +1 -0
  63. package/dist/detectors/messaging.d.ts +16 -0
  64. package/dist/detectors/messaging.d.ts.map +1 -0
  65. package/dist/detectors/messaging.js +72 -0
  66. package/dist/detectors/messaging.js.map +1 -0
  67. package/dist/detectors/pem.js +1 -1
  68. package/dist/detectors/pem.js.map +1 -1
  69. package/dist/detectors/php.d.ts +24 -0
  70. package/dist/detectors/php.d.ts.map +1 -0
  71. package/dist/detectors/php.js +167 -0
  72. package/dist/detectors/php.js.map +1 -0
  73. package/dist/detectors/python.d.ts.map +1 -1
  74. package/dist/detectors/python.js +70 -0
  75. package/dist/detectors/python.js.map +1 -1
  76. package/dist/detectors/rust.d.ts.map +1 -1
  77. package/dist/detectors/rust.js +68 -0
  78. package/dist/detectors/rust.js.map +1 -1
  79. package/dist/detectors/secrets.d.ts +21 -0
  80. package/dist/detectors/secrets.d.ts.map +1 -0
  81. package/dist/detectors/secrets.js +79 -0
  82. package/dist/detectors/secrets.js.map +1 -0
  83. package/dist/detectors/source.d.ts.map +1 -1
  84. package/dist/detectors/source.js +190 -47
  85. package/dist/detectors/source.js.map +1 -1
  86. package/dist/detectors/stateful-hbs.d.ts.map +1 -1
  87. package/dist/detectors/stateful-hbs.js +9 -6
  88. package/dist/detectors/stateful-hbs.js.map +1 -1
  89. package/dist/detectors/terraform.d.ts +21 -0
  90. package/dist/detectors/terraform.d.ts.map +1 -0
  91. package/dist/detectors/terraform.js +123 -0
  92. package/dist/detectors/terraform.js.map +1 -0
  93. package/dist/detectors/vpn.d.ts +22 -0
  94. package/dist/detectors/vpn.d.ts.map +1 -0
  95. package/dist/detectors/vpn.js +99 -0
  96. package/dist/detectors/vpn.js.map +1 -0
  97. package/dist/evidence.d.ts +5 -0
  98. package/dist/evidence.d.ts.map +1 -1
  99. package/dist/evidence.js +5 -0
  100. package/dist/evidence.js.map +1 -1
  101. package/dist/index.d.ts +5 -0
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +6 -0
  104. package/dist/index.js.map +1 -1
  105. package/dist/policy.d.ts +67 -0
  106. package/dist/policy.d.ts.map +1 -0
  107. package/dist/policy.js +115 -0
  108. package/dist/policy.js.map +1 -0
  109. package/dist/registry.d.ts.map +1 -1
  110. package/dist/registry.js +30 -0
  111. package/dist/registry.js.map +1 -1
  112. package/dist/remediate-pipeline.d.ts.map +1 -1
  113. package/dist/remediate-pipeline.js +22 -1
  114. package/dist/remediate-pipeline.js.map +1 -1
  115. package/dist/report.d.ts.map +1 -1
  116. package/dist/report.js +7 -0
  117. package/dist/report.js.map +1 -1
  118. package/dist/standards.d.ts +96 -0
  119. package/dist/standards.d.ts.map +1 -0
  120. package/dist/standards.js +107 -0
  121. package/dist/standards.js.map +1 -0
  122. package/dist/types.d.ts +1 -1
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/types.js.map +1 -1
  125. package/dist/version.d.ts +1 -1
  126. package/dist/version.js +1 -1
  127. package/dist/version.js.map +1 -1
  128. package/package.json +1 -1
  129. package/src/cbom-merge.ts +116 -0
  130. package/src/comments.ts +59 -33
  131. package/src/dependencies.ts +2 -1
  132. package/src/detect-utils.ts +14 -2
  133. package/src/detectors/c.ts +209 -3
  134. package/src/detectors/cicd.ts +158 -0
  135. package/src/detectors/cloud-kms.ts +87 -0
  136. package/src/detectors/cloudformation.ts +215 -0
  137. package/src/detectors/database.ts +88 -0
  138. package/src/detectors/dnssec.ts +186 -0
  139. package/src/detectors/elixir.ts +237 -0
  140. package/src/detectors/java.ts +2 -1
  141. package/src/detectors/jose.ts +94 -0
  142. package/src/detectors/jwk.ts +128 -0
  143. package/src/detectors/k8s.ts +112 -0
  144. package/src/detectors/mesh.ts +154 -0
  145. package/src/detectors/messaging.ts +98 -0
  146. package/src/detectors/pem.ts +1 -1
  147. package/src/detectors/php.ts +225 -0
  148. package/src/detectors/python.ts +71 -0
  149. package/src/detectors/rust.ts +71 -0
  150. package/src/detectors/secrets.ts +114 -0
  151. package/src/detectors/source.ts +235 -68
  152. package/src/detectors/stateful-hbs.ts +9 -6
  153. package/src/detectors/terraform.ts +154 -0
  154. package/src/detectors/vpn.ts +131 -0
  155. package/src/evidence.ts +11 -0
  156. package/src/index.ts +10 -0
  157. package/src/policy.ts +180 -0
  158. package/src/registry.ts +30 -0
  159. package/src/remediate-pipeline.ts +25 -2
  160. package/src/report.ts +7 -0
  161. package/src/standards.ts +186 -0
  162. package/src/types.ts +2 -0
  163. package/src/version.ts +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terraform.js","sourceRoot":"","sources":["../../src/detectors/terraform.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IaC detector: classical asymmetric cryptography provisioned by Terraform /\n * OpenTofu (`.tf`, `.tf.json`). Infrastructure-as-code mints real keys and CMKs\n * that never appear in application source, so this is a distinct surface from the\n * language packs.\n *\n * Covered:\n * - hashicorp/tls `tls_private_key`: `algorithm = \"RSA\" | \"ECDSA\"`\n * - Google Cloud KMS `version_template`: `algorithm = \"RSA_SIGN_…\" | \"EC_SIGN_…\"`\n * - AWS KMS `aws_kms_key`: `customer_master_key_spec = \"RSA_…\" | \"ECC_…\"`\n * - Azure `azurerm_key_vault_key`: `key_type = \"RSA\" | \"EC\"` (+ HSM variants)\n *\n * The HCL assignment forms (`algorithm = \"…\"`, `customer_master_key_spec = \"…\"`,\n * `key_type = \"…\"`) are specific enough inside `.tf` files that the false-positive\n * risk is low. EC keys are classified conservatively as key-agreement-capable\n * (hndl:true) since a provisioned EC key can feed ECDH.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule, hasExtension } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\nconst TF_EXTENSIONS: readonly string[] = [\".tf\", \".tf.json\"];\n\n// Each attribute is matched in BOTH HCL (`algorithm = \"RSA\"`) and the `.tf.json`\n// variant (`\"algorithm\": \"RSA\"`): an optional quote around the key, and `:` or\n// `=` as the separator. The `(?<![\\w\"-])` lookbehind stops a longer attribute\n// name (`my_algorithm`) from matching on its suffix.\n// RSA: tls_private_key `\"RSA\"` plus the Google KMS `RSA_SIGN_…` / `RSA_DECRYPT_…`.\nconst RE_TF_RSA = /(?<![\\w\"-])\"?algorithm\"?\\s*[:=]\\s*\"RSA(?:_[A-Z0-9_]+)?\"/g;\n// ECDSA / EC signing: tls_private_key `\"ECDSA\"` and Google KMS `\"EC_SIGN_…\"`.\nconst RE_TF_ECDSA = /(?<![\\w\"-])\"?algorithm\"?\\s*[:=]\\s*\"(?:ECDSA|EC_SIGN_[A-Z0-9_]+)\"/g;\n// AWS KMS customer master key specs.\nconst RE_TF_KMS_RSA = /(?<![\\w\"-])\"?customer_master_key_spec\"?\\s*[:=]\\s*\"RSA_\\d+\"/g;\nconst RE_TF_KMS_EC = /(?<![\\w\"-])\"?customer_master_key_spec\"?\\s*[:=]\\s*\"ECC_[A-Z0-9_]+\"/g;\n// Azure Key Vault key type (incl. the `-HSM` variants).\nconst RE_TF_AZ_RSA = /(?<![\\w\"-])\"?key_type\"?\\s*[:=]\\s*\"RSA(?:-HSM)?\"/g;\nconst RE_TF_AZ_EC = /(?<![\\w\"-])\"?key_type\"?\\s*[:=]\\s*\"EC(?:-HSM)?\"/g;\n\nconst RULE_TF_RSA: RuleMeta = {\n id: \"tf-rsa-key\",\n title: \"Terraform RSA key\",\n description: \"Terraform tls_private_key / KMS RSA key material\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Terraform provisions a classical RSA key, which is not quantum-safe.\",\n remediation: \"Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).\",\n};\nconst RULE_TF_ECDSA: RuleMeta = {\n id: \"tf-ecdsa-key\",\n title: \"Terraform ECDSA key\",\n description: \"Terraform tls_private_key / KMS EC signing key\",\n category: \"signature\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Terraform provisions a classical ECDSA key, forgeable by a quantum attacker.\",\n remediation: \"ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).\",\n};\nconst RULE_TF_KMS_RSA: RuleMeta = {\n id: \"tf-kms-rsa\",\n title: \"Terraform AWS KMS RSA CMK\",\n description: 'Terraform aws_kms_key customer_master_key_spec = \"RSA_*\"',\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Terraform provisions a classical RSA KMS customer master key (harvest-now-decrypt-later exposed for encryption CMKs).\",\n remediation: \"Plan migration to PQC as cloud KMS adds ML-KEM / ML-DSA key specs.\",\n};\nconst RULE_TF_KMS_EC: RuleMeta = {\n id: \"tf-kms-ec\",\n title: \"Terraform AWS KMS EC CMK\",\n description: 'Terraform aws_kms_key customer_master_key_spec = \"ECC_*\"',\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Terraform provisions a classical EC KMS customer master key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).\",\n remediation:\n \"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).\",\n};\nconst RULE_TF_AZ_RSA: RuleMeta = {\n id: \"tf-keyvault-rsa\",\n title: \"Terraform Azure Key Vault RSA key\",\n description: 'Terraform azurerm_key_vault_key key_type = \"RSA\"',\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Terraform provisions a classical RSA Azure Key Vault key, which is not quantum-safe.\",\n remediation: \"Plan migration to PQC (ML-KEM-768 / ML-DSA-65).\",\n};\nconst RULE_TF_AZ_EC: RuleMeta = {\n id: \"tf-keyvault-ec\",\n title: \"Terraform Azure Key Vault EC key\",\n description: 'Terraform azurerm_key_vault_key key_type = \"EC\"',\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Terraform provisions a classical EC Azure Key Vault key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).\",\n remediation:\n \"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).\",\n};\n\n/** Detects classical asymmetric crypto provisioned in Terraform / OpenTofu. */\nexport const terraformDetector: Detector = {\n id: \"terraform-crypto\",\n description: \"Classical asymmetric crypto provisioned by Terraform / OpenTofu (IaC)\",\n scope: \"config\",\n language: \"any\",\n rules: [\n RULE_TF_RSA,\n RULE_TF_ECDSA,\n RULE_TF_KMS_RSA,\n RULE_TF_KMS_EC,\n RULE_TF_AZ_RSA,\n RULE_TF_AZ_EC,\n ],\n appliesTo: (f) => hasExtension(f, TF_EXTENSIONS),\n detect({ file, content }): Finding[] {\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta) =>\n eachMatch(re, content, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n add(RE_TF_RSA, RULE_TF_RSA);\n add(RE_TF_ECDSA, RULE_TF_ECDSA);\n add(RE_TF_KMS_RSA, RULE_TF_KMS_RSA);\n add(RE_TF_KMS_EC, RULE_TF_KMS_EC);\n add(RE_TF_AZ_RSA, RULE_TF_AZ_RSA);\n add(RE_TF_AZ_EC, RULE_TF_AZ_EC);\n return findings;\n },\n};\n"]}
@@ -0,0 +1,22 @@
1
+ /**
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).
7
+ *
8
+ * Covered (each gated so it only fires inside the relevant config):
9
+ * - **WireGuard** — `[Interface]`/`[Peer]` sections with `PrivateKey`/`PublicKey`.
10
+ * WireGuard's Noise handshake is Curve25519 with NO standard PQC option, so this
11
+ * is an especially sharp finding: it cannot be made quantum-safe without a
12
+ * tunnel-wrapping layer. The private key is treated as sensitive key material.
13
+ * - **IPsec / strongSwan** — IKE/ESP proposals naming classical DH groups
14
+ * (`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.
18
+ */
19
+ import type { Detector } from "../types.js";
20
+ /** Detects classical key exchange in WireGuard / IPsec / sshd network config. */
21
+ export declare const vpnDetector: Detector;
22
+ //# sourceMappingURL=vpn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpn.d.ts","sourceRoot":"","sources":["../../src/detectors/vpn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA2E/D,iFAAiF;AACjF,eAAO,MAAM,WAAW,EAAE,QAoCzB,CAAC"}
@@ -0,0 +1,99 @@
1
+ import { eachMatch, findingFromRule } from "../detect-utils.js";
2
+ import { CWE_BROKEN_CRYPTO } from "../cwe.js";
3
+ const RE_WG_KEY = /\b(?:PrivateKey|PublicKey)\s*=\s*[A-Za-z0-9+/]{42,}=/g;
4
+ const RE_IPSEC_MODP = /\bmodp\d+\b/gi;
5
+ const RE_IPSEC_ECP = /\becp\d+(?:bp)?\b/gi;
6
+ const RE_SSHD_KEX = /^[ \t]*KexAlgorithms[ \t]+([^\n]*)/gm;
7
+ const RULE_WG = {
8
+ id: "net-wireguard-x25519",
9
+ title: "WireGuard Curve25519 key",
10
+ description: "WireGuard [Interface]/[Peer] key — Curve25519 Noise handshake (no PQC option)",
11
+ category: "key-exchange",
12
+ severity: "high",
13
+ confidence: "high",
14
+ algorithm: "X25519",
15
+ hndl: true,
16
+ cwe: CWE_BROKEN_CRYPTO,
17
+ sensitive: true,
18
+ message: "WireGuard tunnel keyed by classical Curve25519 (Noise); WireGuard has no standard post-quantum KEM, so the tunnel is harvest-now-decrypt-later exposed until wrapped by a PQC layer.",
19
+ remediation: "Wrap the tunnel in a PQC-hybrid transport (e.g. a TLS 1.3 X25519MLKEM768 layer) or track WireGuard PQC proposals; rotate keys when available.",
20
+ };
21
+ const RULE_IPSEC_DH = {
22
+ id: "net-ipsec-modp-dh",
23
+ title: "IPsec classical DH group (modp)",
24
+ description: "IPsec/strongSwan IKE/ESP proposal names a finite-field DH group (modp*)",
25
+ category: "key-exchange",
26
+ severity: "medium",
27
+ confidence: "high",
28
+ algorithm: "DH",
29
+ hndl: true,
30
+ cwe: CWE_BROKEN_CRYPTO,
31
+ message: "IPsec proposal uses a classical finite-field Diffie-Hellman group (modp*); the tunnel key exchange is harvest-now-decrypt-later exposed.",
32
+ remediation: "Add a PQC/hybrid IKE proposal (ML-KEM) as your IPsec stack supports it.",
33
+ };
34
+ const RULE_IPSEC_EC = {
35
+ id: "net-ipsec-ecp-ecdh",
36
+ title: "IPsec classical ECDH group (ecp)",
37
+ description: "IPsec/strongSwan IKE/ESP proposal names an elliptic-curve DH group (ecp*)",
38
+ category: "key-exchange",
39
+ severity: "medium",
40
+ confidence: "high",
41
+ algorithm: "ECDH",
42
+ hndl: true,
43
+ cwe: CWE_BROKEN_CRYPTO,
44
+ message: "IPsec proposal uses a classical elliptic-curve Diffie-Hellman group (ecp*); the tunnel key exchange is harvest-now-decrypt-later exposed.",
45
+ remediation: "Add a PQC/hybrid IKE proposal (X25519MLKEM768 / ML-KEM) as your IPsec stack supports it.",
46
+ };
47
+ const RULE_SSHD_KEX = {
48
+ id: "net-sshd-classical-kex",
49
+ title: "SSH server offers no PQC key exchange",
50
+ description: "sshd_config KexAlgorithms lists only classical key exchange (no sntrup761/mlkem768)",
51
+ category: "key-exchange",
52
+ severity: "medium",
53
+ confidence: "high",
54
+ algorithm: "ECDH",
55
+ hndl: true,
56
+ cwe: CWE_BROKEN_CRYPTO,
57
+ message: "SSH server's KexAlgorithms offers only classical key exchange (no sntrup761x25519 / mlkem768x25519); sessions are harvest-now-decrypt-later exposed.",
58
+ remediation: "Add a PQC hybrid KEX: sntrup761x25519-sha512@openssh.com or mlkem768x25519-sha256.",
59
+ };
60
+ /** True when a KexAlgorithms value already offers a post-quantum hybrid KEX. */
61
+ function offersPqKex(kexLine) {
62
+ return /sntrup761x25519|mlkem768/.test(kexLine);
63
+ }
64
+ /** Detects classical key exchange in WireGuard / IPsec / sshd network config. */
65
+ export const vpnDetector = {
66
+ id: "network-transport-crypto",
67
+ description: "Classical key exchange in network transport / VPN config (WireGuard, IPsec, sshd)",
68
+ scope: "config",
69
+ language: "any",
70
+ rules: [RULE_WG, RULE_IPSEC_DH, RULE_IPSEC_EC, RULE_SSHD_KEX],
71
+ appliesTo: (f) => {
72
+ const lower = f.toLowerCase();
73
+ const base = lower.split("/").pop() ?? lower;
74
+ return lower.endsWith(".conf") || base === "sshd_config" || base === "ssh_config";
75
+ },
76
+ detect({ file, content }) {
77
+ const findings = [];
78
+ const push = (rule, index, length) => findings.push(findingFromRule(rule, { file, content, index, matchLength: length }));
79
+ // WireGuard: gated to a WireGuard config section.
80
+ if (content.includes("[Interface]") || content.includes("[Peer]")) {
81
+ eachMatch(RE_WG_KEY, content, (m) => push(RULE_WG, m.index, m[0].length));
82
+ }
83
+ // IPsec / strongSwan: gated to a proposal assignment.
84
+ if (/\b(?:ike|esp|proposals?|keyexchange)\s*=/i.test(content)) {
85
+ eachMatch(RE_IPSEC_MODP, content, (m) => push(RULE_IPSEC_DH, m.index, m[0].length));
86
+ eachMatch(RE_IPSEC_ECP, content, (m) => push(RULE_IPSEC_EC, m.index, m[0].length));
87
+ }
88
+ // sshd_config / ssh_config: KexAlgorithms line with no PQC hybrid offered.
89
+ const base = file.toLowerCase().split("/").pop() ?? "";
90
+ if (base === "sshd_config" || base === "ssh_config") {
91
+ eachMatch(RE_SSHD_KEX, content, (m) => {
92
+ if (!offersPqKex(m[1]))
93
+ push(RULE_SSHD_KEX, m.index, m[0].length);
94
+ });
95
+ }
96
+ return findings;
97
+ },
98
+ };
99
+ //# sourceMappingURL=vpn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpn.js","sourceRoot":"","sources":["../../src/detectors/vpn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config detector: classical key exchange in network transport / VPN config —\n * WireGuard, IPsec (strongSwan), and OpenSSH server config. These are the tunnels\n * carrying \"communication between things\"; their key exchange is classical and so\n * harvest-now-decrypt-later exposed (a recorded tunnel is decryptable once a CRQC\n * exists).\n *\n * Covered (each gated so it only fires inside the relevant config):\n * - **WireGuard** — `[Interface]`/`[Peer]` sections with `PrivateKey`/`PublicKey`.\n * WireGuard's Noise handshake is Curve25519 with NO standard PQC option, so this\n * is an especially sharp finding: it cannot be made quantum-safe without a\n * tunnel-wrapping layer. The private key is treated as sensitive key material.\n * - **IPsec / strongSwan** — IKE/ESP proposals naming classical DH groups\n * (`modp*` = finite-field DH, `ecp*` = ECDH).\n * - **sshd_config / ssh_config** — a `KexAlgorithms` line that offers no PQC hybrid\n * KEX (`sntrup761x25519-*`, `mlkem768x25519-*`). If a PQC KEX IS listed the line\n * is left alone, so a hardened server stays silent.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\nconst RE_WG_KEY = /\\b(?:PrivateKey|PublicKey)\\s*=\\s*[A-Za-z0-9+/]{42,}=/g;\nconst RE_IPSEC_MODP = /\\bmodp\\d+\\b/gi;\nconst RE_IPSEC_ECP = /\\becp\\d+(?:bp)?\\b/gi;\nconst RE_SSHD_KEX = /^[ \\t]*KexAlgorithms[ \\t]+([^\\n]*)/gm;\n\nconst RULE_WG: RuleMeta = {\n id: \"net-wireguard-x25519\",\n title: \"WireGuard Curve25519 key\",\n description: \"WireGuard [Interface]/[Peer] key — Curve25519 Noise handshake (no PQC option)\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"X25519\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n sensitive: true,\n message:\n \"WireGuard tunnel keyed by classical Curve25519 (Noise); WireGuard has no standard post-quantum KEM, so the tunnel is harvest-now-decrypt-later exposed until wrapped by a PQC layer.\",\n remediation:\n \"Wrap the tunnel in a PQC-hybrid transport (e.g. a TLS 1.3 X25519MLKEM768 layer) or track WireGuard PQC proposals; rotate keys when available.\",\n};\nconst RULE_IPSEC_DH: RuleMeta = {\n id: \"net-ipsec-modp-dh\",\n title: \"IPsec classical DH group (modp)\",\n description: \"IPsec/strongSwan IKE/ESP proposal names a finite-field DH group (modp*)\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"DH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"IPsec proposal uses a classical finite-field Diffie-Hellman group (modp*); the tunnel key exchange is harvest-now-decrypt-later exposed.\",\n remediation: \"Add a PQC/hybrid IKE proposal (ML-KEM) as your IPsec stack supports it.\",\n};\nconst RULE_IPSEC_EC: RuleMeta = {\n id: \"net-ipsec-ecp-ecdh\",\n title: \"IPsec classical ECDH group (ecp)\",\n description: \"IPsec/strongSwan IKE/ESP proposal names an elliptic-curve DH group (ecp*)\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"IPsec proposal uses a classical elliptic-curve Diffie-Hellman group (ecp*); the tunnel key exchange is harvest-now-decrypt-later exposed.\",\n remediation:\n \"Add a PQC/hybrid IKE proposal (X25519MLKEM768 / ML-KEM) as your IPsec stack supports it.\",\n};\nconst RULE_SSHD_KEX: RuleMeta = {\n id: \"net-sshd-classical-kex\",\n title: \"SSH server offers no PQC key exchange\",\n description:\n \"sshd_config KexAlgorithms lists only classical key exchange (no sntrup761/mlkem768)\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"SSH server's KexAlgorithms offers only classical key exchange (no sntrup761x25519 / mlkem768x25519); sessions are harvest-now-decrypt-later exposed.\",\n remediation: \"Add a PQC hybrid KEX: sntrup761x25519-sha512@openssh.com or mlkem768x25519-sha256.\",\n};\n\n/** True when a KexAlgorithms value already offers a post-quantum hybrid KEX. */\nfunction offersPqKex(kexLine: string): boolean {\n return /sntrup761x25519|mlkem768/.test(kexLine);\n}\n\n/** Detects classical key exchange in WireGuard / IPsec / sshd network config. */\nexport const vpnDetector: Detector = {\n id: \"network-transport-crypto\",\n description: \"Classical key exchange in network transport / VPN config (WireGuard, IPsec, sshd)\",\n scope: \"config\",\n language: \"any\",\n rules: [RULE_WG, RULE_IPSEC_DH, RULE_IPSEC_EC, RULE_SSHD_KEX],\n appliesTo: (f) => {\n const lower = f.toLowerCase();\n const base = lower.split(\"/\").pop() ?? lower;\n return lower.endsWith(\".conf\") || base === \"sshd_config\" || base === \"ssh_config\";\n },\n detect({ file, content }): Finding[] {\n const findings: Finding[] = [];\n const push = (rule: RuleMeta, index: number, length: number) =>\n findings.push(findingFromRule(rule, { file, content, index, matchLength: length }));\n\n // WireGuard: gated to a WireGuard config section.\n if (content.includes(\"[Interface]\") || content.includes(\"[Peer]\")) {\n eachMatch(RE_WG_KEY, content, (m) => push(RULE_WG, m.index, m[0].length));\n }\n\n // IPsec / strongSwan: gated to a proposal assignment.\n if (/\\b(?:ike|esp|proposals?|keyexchange)\\s*=/i.test(content)) {\n eachMatch(RE_IPSEC_MODP, content, (m) => push(RULE_IPSEC_DH, m.index, m[0].length));\n eachMatch(RE_IPSEC_ECP, content, (m) => push(RULE_IPSEC_EC, m.index, m[0].length));\n }\n\n // sshd_config / ssh_config: KexAlgorithms line with no PQC hybrid offered.\n const base = file.toLowerCase().split(\"/\").pop() ?? \"\";\n if (base === \"sshd_config\" || base === \"ssh_config\") {\n eachMatch(RE_SSHD_KEX, content, (m) => {\n if (!offersPqKex(m[1])) push(RULE_SSHD_KEX, m.index, m[0].length);\n });\n }\n return findings;\n },\n};\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { ScanResult } from "./types.js";
2
+ import type { CryptoPolicy, PolicyMapping } from "./policy.js";
2
3
  /** Stable per-finding record for the evidence body (deterministic per commit). */
3
4
  export interface EvidenceFinding {
4
5
  ruleId: string;
@@ -23,6 +24,8 @@ export interface ReadinessReport {
23
24
  };
24
25
  inventory: ScanResult["inventory"];
25
26
  findings: EvidenceFinding[];
27
+ /** §4 policy verdicts, present only when a crypto policy was supplied. */
28
+ policyMapping?: PolicyMapping;
26
29
  cbom: unknown;
27
30
  attestation: {
28
31
  /** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */
@@ -38,6 +41,8 @@ export interface ReadinessReportOptions {
38
41
  repository?: string;
39
42
  /** Full commit SHA (e.g. from `GITHUB_SHA`); omitted → null. */
40
43
  commit?: string;
44
+ /** Optional org cryptography policy — adds the §4 `policyMapping` verdicts. */
45
+ policy?: CryptoPolicy;
41
46
  }
42
47
  /**
43
48
  * Build the A.8.24 readiness report for a scan result. The attestation's
@@ -1 +1 @@
1
- {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,wBAAwB,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC;IACf,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IACnC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE;QACX,+EAA+E;QAC/E,WAAW,EAAE,MAAM,CAAC;QACpB,wEAAwE;QACxE,SAAS,EAAE,IAAI,CAAC;QAChB,4EAA4E;QAC5E,SAAS,EAAE,IAAI,CAAC;KACjB,CAAC;CACH;AAeD,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE,sBAA2B,GAChC,eAAe,CAsCjB"}
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,wBAAwB,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC;IACf,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IACnC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE;QACX,+EAA+E;QAC/E,WAAW,EAAE,MAAM,CAAC;QACpB,wEAAwE;QACxE,SAAS,EAAE,IAAI,CAAC;QAChB,4EAA4E;QAC5E,SAAS,EAAE,IAAI,CAAC;KACjB,CAAC;CACH;AAeD,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE,sBAA2B,GAChC,eAAe,CA2CjB"}
package/dist/evidence.js CHANGED
@@ -19,6 +19,7 @@
19
19
  import { createHash } from "node:crypto";
20
20
  import { toCbom } from "./cbom.js";
21
21
  import { VERSION } from "./version.js";
22
+ import { buildPolicyMapping } from "./policy.js";
22
23
  /** Canonical JSON: object keys sorted recursively, so the hash is reproducible. */
23
24
  function canonicalize(value) {
24
25
  if (Array.isArray(value))
@@ -50,6 +51,9 @@ export function buildReadinessReport(result, opts = {}) {
50
51
  // its CycloneDX envelope carries a volatile timestamp/serial — so it is
51
52
  // EXCLUDED from the hashed body (its integrity follows from its hashed inputs)
52
53
  // to keep the content hash reproducible across scan runs on the same commit.
54
+ // §4: if the org supplied a crypto policy, attest the per-finding verdicts too.
55
+ // Deterministic (same findings + policy → same mapping), so it is hashed.
56
+ const policyMapping = opts.policy ? buildPolicyMapping(result.findings, opts.policy) : undefined;
53
57
  const hashableBody = {
54
58
  reportType: "quantakrypto-readiness",
55
59
  specVersion: 1,
@@ -61,6 +65,7 @@ export function buildReadinessReport(result, opts = {}) {
61
65
  tool: { name: "qScan", version: VERSION },
62
66
  inventory: result.inventory,
63
67
  findings,
68
+ ...(policyMapping ? { policyMapping } : {}),
64
69
  };
65
70
  const contentHash = "sha256:" +
66
71
  createHash("sha256")
@@ -1 +1 @@
1
- {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAmCvC,mFAAmF;AACnF,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACzD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACrE,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,CAAE,KAAiC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AASD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAkB,EAClB,OAA+B,EAAE;IAEjC,MAAM,QAAQ,GAAsB,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9D,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI;QACrB,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI;KACtB,CAAC,CAAC,CAAC;IAEJ,+EAA+E;IAC/E,wEAAwE;IACxE,+EAA+E;IAC/E,6EAA6E;IAC7E,MAAM,YAAY,GAAG;QACnB,UAAU,EAAE,wBAAwB;QACpC,WAAW,EAAE,CAAC;QACd,OAAO,EAAE;YACP,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;YACnC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;YAC3B,WAAW,EAAE,MAAM,CAAC,IAAI;SACzB;QACD,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;QACzC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,QAAQ;KACT,CAAC;IACF,MAAM,WAAW,GACf,SAAS;QACT,UAAU,CAAC,QAAQ,CAAC;aACjB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC;aAClD,MAAM,CAAC,KAAK,CAAC,CAAC;IAEnB,OAAO;QACL,GAAG,YAAY;QACf,OAAO,EAAE,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,EAAE;QACpE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;KAC5C,CAAC;AACvB,CAAC","sourcesContent":["/**\n * ISO/IEC 27001:2022 Annex A 8.24 evidence-chain export\n * (docs/compliance/iso27001-a8.24-evidence.md).\n *\n * Emits a self-describing readiness report bundling the scan result, crypto\n * inventory, and CycloneDX CBOM, plus an attestation carrying a DETERMINISTIC\n * content hash — the same scan over the same commit + config yields the same\n * hash (the volatile scan timestamp is deliberately excluded from the hashed\n * body). Signing + RFC-3161 timestamping are left to an EXTERNAL, vetted signer\n * (ADR-0004: this project performs no cryptography itself — it orchestrates a\n * signer, it does not implement one). SHA-256 here is an integrity hash (a Node\n * built-in), not an asymmetric primitive.\n *\n * Honesty boundary: this artifact is EVIDENCE for A.8.24, not the control. The\n * organization still owns the cryptography policy, key management, and the\n * conformance judgment. A clean scan is the absence of detected candidates, not\n * proof of quantum-safety (qScan is lexical). See docs/COMPLIANCE.md §3.\n */\nimport { createHash } from \"node:crypto\";\n\nimport type { ScanResult } from \"./types.js\";\nimport { toCbom } from \"./cbom.js\";\nimport { VERSION } from \"./version.js\";\n\n/** Stable per-finding record for the evidence body (deterministic per commit). */\nexport interface EvidenceFinding {\n ruleId: string;\n algorithm?: string;\n severity: string;\n hndl: boolean;\n file: string;\n line: number;\n}\n\nexport interface ReadinessReport {\n reportType: \"quantakrypto-readiness\";\n specVersion: 1;\n subject: {\n repository: string | null;\n commit: string | null;\n scannedRoot: string;\n scanTimeUtc: string;\n };\n tool: { name: \"qScan\"; version: string };\n inventory: ScanResult[\"inventory\"];\n findings: EvidenceFinding[];\n cbom: unknown;\n attestation: {\n /** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */\n contentHash: string;\n /** RFC-3161 / transparency-log token — filled by an external signer. */\n timestamp: null;\n /** Detached signature over `contentHash` — filled by an external signer. */\n signature: null;\n };\n}\n\n/** Canonical JSON: object keys sorted recursively, so the hash is reproducible. */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n out[k] = canonicalize((value as Record<string, unknown>)[k]);\n }\n return out;\n }\n return value;\n}\n\nexport interface ReadinessReportOptions {\n /** Repository URL (e.g. from `GITHUB_REPOSITORY`); omitted → null. */\n repository?: string;\n /** Full commit SHA (e.g. from `GITHUB_SHA`); omitted → null. */\n commit?: string;\n}\n\n/**\n * Build the A.8.24 readiness report for a scan result. The attestation's\n * `contentHash` covers everything EXCEPT the scan timestamp and the attestation\n * block itself, so re-running the same scan on the same commit is verifiable.\n */\nexport function buildReadinessReport(\n result: ScanResult,\n opts: ReadinessReportOptions = {},\n): ReadinessReport {\n const findings: EvidenceFinding[] = result.findings.map((f) => ({\n ruleId: f.ruleId,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n severity: f.severity,\n hndl: f.hndl,\n file: f.location.file,\n line: f.location.line,\n }));\n\n // The CBOM is a deterministic *view* of the (hashed) findings + inventory, but\n // its CycloneDX envelope carries a volatile timestamp/serial — so it is\n // EXCLUDED from the hashed body (its integrity follows from its hashed inputs)\n // to keep the content hash reproducible across scan runs on the same commit.\n const hashableBody = {\n reportType: \"quantakrypto-readiness\",\n specVersion: 1,\n subject: {\n repository: opts.repository ?? null,\n commit: opts.commit ?? null,\n scannedRoot: result.root,\n },\n tool: { name: \"qScan\", version: VERSION },\n inventory: result.inventory,\n findings,\n };\n const contentHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n\n return {\n ...hashableBody,\n subject: { ...hashableBody.subject, scanTimeUtc: result.finishedAt },\n cbom: toCbom(result),\n attestation: { contentHash, timestamp: null, signature: null },\n } as ReadinessReport;\n}\n"]}
1
+ {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../src/evidence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ISO/IEC 27001:2022 Annex A 8.24 evidence-chain export\n * (docs/compliance/iso27001-a8.24-evidence.md).\n *\n * Emits a self-describing readiness report bundling the scan result, crypto\n * inventory, and CycloneDX CBOM, plus an attestation carrying a DETERMINISTIC\n * content hash — the same scan over the same commit + config yields the same\n * hash (the volatile scan timestamp is deliberately excluded from the hashed\n * body). Signing + RFC-3161 timestamping are left to an EXTERNAL, vetted signer\n * (ADR-0004: this project performs no cryptography itself — it orchestrates a\n * signer, it does not implement one). SHA-256 here is an integrity hash (a Node\n * built-in), not an asymmetric primitive.\n *\n * Honesty boundary: this artifact is EVIDENCE for A.8.24, not the control. The\n * organization still owns the cryptography policy, key management, and the\n * conformance judgment. A clean scan is the absence of detected candidates, not\n * proof of quantum-safety (qScan is lexical). See docs/COMPLIANCE.md §3.\n */\nimport { createHash } from \"node:crypto\";\n\nimport type { ScanResult } from \"./types.js\";\nimport { toCbom } from \"./cbom.js\";\nimport { VERSION } from \"./version.js\";\nimport { buildPolicyMapping } from \"./policy.js\";\nimport type { CryptoPolicy, PolicyMapping } from \"./policy.js\";\n\n/** Stable per-finding record for the evidence body (deterministic per commit). */\nexport interface EvidenceFinding {\n ruleId: string;\n algorithm?: string;\n severity: string;\n hndl: boolean;\n file: string;\n line: number;\n}\n\nexport interface ReadinessReport {\n reportType: \"quantakrypto-readiness\";\n specVersion: 1;\n subject: {\n repository: string | null;\n commit: string | null;\n scannedRoot: string;\n scanTimeUtc: string;\n };\n tool: { name: \"qScan\"; version: string };\n inventory: ScanResult[\"inventory\"];\n findings: EvidenceFinding[];\n /** §4 policy verdicts, present only when a crypto policy was supplied. */\n policyMapping?: PolicyMapping;\n cbom: unknown;\n attestation: {\n /** sha256 over the canonicalized deterministic body (excludes scanTimeUtc). */\n contentHash: string;\n /** RFC-3161 / transparency-log token — filled by an external signer. */\n timestamp: null;\n /** Detached signature over `contentHash` — filled by an external signer. */\n signature: null;\n };\n}\n\n/** Canonical JSON: object keys sorted recursively, so the hash is reproducible. */\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n out[k] = canonicalize((value as Record<string, unknown>)[k]);\n }\n return out;\n }\n return value;\n}\n\nexport interface ReadinessReportOptions {\n /** Repository URL (e.g. from `GITHUB_REPOSITORY`); omitted → null. */\n repository?: string;\n /** Full commit SHA (e.g. from `GITHUB_SHA`); omitted → null. */\n commit?: string;\n /** Optional org cryptography policy — adds the §4 `policyMapping` verdicts. */\n policy?: CryptoPolicy;\n}\n\n/**\n * Build the A.8.24 readiness report for a scan result. The attestation's\n * `contentHash` covers everything EXCEPT the scan timestamp and the attestation\n * block itself, so re-running the same scan on the same commit is verifiable.\n */\nexport function buildReadinessReport(\n result: ScanResult,\n opts: ReadinessReportOptions = {},\n): ReadinessReport {\n const findings: EvidenceFinding[] = result.findings.map((f) => ({\n ruleId: f.ruleId,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n severity: f.severity,\n hndl: f.hndl,\n file: f.location.file,\n line: f.location.line,\n }));\n\n // The CBOM is a deterministic *view* of the (hashed) findings + inventory, but\n // its CycloneDX envelope carries a volatile timestamp/serial — so it is\n // EXCLUDED from the hashed body (its integrity follows from its hashed inputs)\n // to keep the content hash reproducible across scan runs on the same commit.\n // §4: if the org supplied a crypto policy, attest the per-finding verdicts too.\n // Deterministic (same findings + policy → same mapping), so it is hashed.\n const policyMapping = opts.policy ? buildPolicyMapping(result.findings, opts.policy) : undefined;\n\n const hashableBody = {\n reportType: \"quantakrypto-readiness\",\n specVersion: 1,\n subject: {\n repository: opts.repository ?? null,\n commit: opts.commit ?? null,\n scannedRoot: result.root,\n },\n tool: { name: \"qScan\", version: VERSION },\n inventory: result.inventory,\n findings,\n ...(policyMapping ? { policyMapping } : {}),\n };\n const contentHash =\n \"sha256:\" +\n createHash(\"sha256\")\n .update(JSON.stringify(canonicalize(hashableBody)))\n .digest(\"hex\");\n\n return {\n ...hashableBody,\n subject: { ...hashableBody.subject, scanTimeUtc: result.finishedAt },\n cbom: toCbom(result),\n attestation: { contentHash, timestamp: null, signature: null },\n } as ReadinessReport;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -46,9 +46,14 @@ export { toSarif, toJson, formatSummary, formatTierGuidance } from "./report.js"
46
46
  export type { ReportOptions } from "./report.js";
47
47
  export { toCbom } from "./cbom.js";
48
48
  export type { CycloneDxBom, CbomComponent } from "./cbom.js";
49
+ export { mergeCboms } from "./cbom-merge.js";
49
50
  export { buildReadinessReport } from "./evidence.js";
50
51
  export type { ReadinessReport, EvidenceFinding, ReadinessReportOptions } from "./evidence.js";
52
+ export { buildPolicyMapping, parseCryptoPolicy, verdictForAlgorithm } from "./policy.js";
53
+ export type { CryptoPolicy, PolicyVerdict, PolicyMapping, PolicyFindingVerdict } from "./policy.js";
51
54
  export { remediationFor, remediationForTier, TIER_PARAMS, STATEFUL_HBS_NOTE, PQC_TRANSITION_NOTE, statefulHbsApplies, } from "./remediation.js";
52
55
  export type { SecurityTier } from "./remediation.js";
56
+ export { PQC_STANDARDS, standardsReviewStatus } from "./standards.js";
57
+ export type { PqcStandards, StandardsCitation, StandardsReviewStatus } from "./standards.js";
53
58
  export { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH, CWE_CERT_VALIDATION, CWE_HARDCODED_KEY, CWE_RISKY_PRIMITIVE, } from "./cwe.js";
54
59
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAGzE,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,YAAY,EACV,YAAY,EACZ,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACvF,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAG9D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC9E,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGvE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACjF,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG5E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGpF,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKhD,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGhG,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGzF,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAG9F,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACzF,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGpG,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAG7F,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -50,10 +50,16 @@ export { SEVERITY_ORDER, severityRank, meetsThreshold, sarifLevel } from "./seve
50
50
  export { toSarif, toJson, formatSummary, formatTierGuidance } from "./report.js";
51
51
  // CycloneDX 1.6 cryptographic bill of materials (CBOM) export.
52
52
  export { toCbom } from "./cbom.js";
53
+ // Merge multiple CBOMs (code + infra + live endpoints) into one combined BOM.
54
+ export { mergeCboms } from "./cbom-merge.js";
53
55
  // ISO/IEC 27001 A.8.24 evidence-chain readiness report.
54
56
  export { buildReadinessReport } from "./evidence.js";
57
+ // Cryptography policy → per-finding verdicts (A.8.24 evidence §4).
58
+ export { buildPolicyMapping, parseCryptoPolicy, verdictForAlgorithm } from "./policy.js";
55
59
  // Remediation lookup (family + tier-aware) and stateful-HBS guidance.
56
60
  export { remediationFor, remediationForTier, TIER_PARAMS, STATEFUL_HBS_NOTE, PQC_TRANSITION_NOTE, statefulHbsApplies, } from "./remediation.js";
61
+ // Post-quantum standards source of truth + review cadence.
62
+ export { PQC_STANDARDS, standardsReviewStatus } from "./standards.js";
57
63
  // CWE identifier constants.
58
64
  export { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH, CWE_CERT_VALIDATION, CWE_HARDCODED_KEY, CWE_RISKY_PRIMITIVE, } from "./cwe.js";
59
65
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,YAAY,CAAC;AAE3B,qEAAqE;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAKvC,8CAA8C;AAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEzE,2EAA2E;AAC3E,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAW7D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEvF,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAQ5D,0CAA0C;AAC1C,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE9D,sEAAsE;AACtE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG9E,iEAAiE;AACjE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGjF,qDAAqD;AACrD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAGvB,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,yEAAyE;AACzE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGvE,kFAAkF;AAClF,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpF,8EAA8E;AAC9E,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAE3B,+BAA+B;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,sFAAsF;AACtF,6EAA6E;AAC7E,gFAAgF;AAChF,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEhG,+EAA+E;AAC/E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEzF,aAAa;AACb,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjF,+DAA+D;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,wDAAwD;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrD,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,4BAA4B;AAC5B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,UAAU,CAAC","sourcesContent":["/**\n * @quantakrypto/core — public API (LOCKED CONTRACT).\n *\n * The exported NAMES and SIGNATURES below are the stable contract that\n * @quantakrypto/qscan, @quantakrypto/mcp and the GitHub Action depend on — do not change\n * them without updating all consumers. The implementations live in focused\n * modules under src/ and are re-exported here; the public surface is identical\n * to the original stub file.\n */\nexport * from \"./types.js\";\n\n// Tool version, surfaced in reports. Keep in sync with package.json.\nexport { VERSION } from \"./version.js\";\n\n// Minimal SARIF 2.1.0 log shape, defined alongside the reporters.\nexport type { SarifLog } from \"./report.js\";\n\n// Core orchestration + built-in detector set.\nexport { scan, detectors, detectFile, compareFindings } from \"./scan.js\";\n\n// Snippet-level fix verification (shared by MCP verify_fix + remediation).\nexport { verifyFix, languageToExtension } from \"./verify.js\";\nexport type { VerifyResult } from \"./verify.js\";\n\n// Agent-plane shared types + the context redactor (offline; reused by MCP).\nexport type {\n ContextLevel,\n RedactedContext,\n TriageVerdict,\n Patch,\n FixProposal,\n} from \"./agent-types.js\";\nexport { buildContext, renderPreflight } from \"./redact.js\";\nexport { TRIAGE_RUBRIC, TRIAGE_VERDICT_SCHEMA, buildTriageRequest } from \"./triage.js\";\nexport type { TriageRequest } from \"./triage.js\";\nexport {\n REMEDIATE_RUBRIC,\n FIX_REQUEST_SCHEMA,\n buildRemediateRequest,\n} from \"./remediate-request.js\";\nexport type { RemediateRequest } from \"./remediate-request.js\";\nexport { checkPatchPolicy } from \"./patch-policy.js\";\nexport type { PolicyContext, PolicyDecision } from \"./patch-policy.js\";\nexport { withWorktree } from \"./worktree.js\";\nexport { codemodRegistry, codemodFor } from \"./codemods/registry.js\";\nexport type { Codemod } from \"./codemods/registry.js\";\nexport { configToggleCodemod } from \"./codemods/config-toggle.js\";\nexport { remediateFindings } from \"./remediate-pipeline.js\";\nexport type {\n RemediateOptions,\n RemediationResult,\n VerifiedPatch,\n RejectedPatch,\n} from \"./remediate-pipeline.js\";\n\n// Scan cancellation / work-budget errors.\nexport { AbortError, BudgetExceededError } from \"./errors.js\";\n\n// Parallel scanning (worker_threads pool) + pure merge/chunk helpers.\nexport { scanParallel, mergeChunkResults, chunkByBytes } from \"./parallel.js\";\nexport type { ScanChunk, ChunkResult, SizedFile } from \"./parallel.js\";\n\n// Detector registry (plugin point) + helpers + the rule catalog.\nexport { DetectorRegistry, defaultRegistry, detectorScope } from \"./registry.js\";\nexport type { RuleCatalogEntry } from \"./registry.js\";\n\n// Canonical baseline (shared by qScan + the Action).\nexport {\n fingerprintFinding,\n baselineFromFindings,\n applyBaseline,\n loadBaseline,\n saveBaseline,\n BASELINE_VERSION,\n} from \"./baseline.js\";\nexport type { Baseline } from \"./baseline.js\";\n\n// Incremental scanning: changed-files helper (git-aware, tolerant).\nexport { changedFiles } from \"./changed.js\";\n\n// Optional `quantakrypto.config.json` loader (P2-9; see docs/CONFIG.md).\nexport { loadConfig, ConfigError, CONFIG_FILENAME } from \"./config.js\";\nexport type { QuantakryptoFileConfig, LoadConfigResult } from \"./config.js\";\n\n// Filesystem walker (relative POSIX paths, default ignores, size/binary filters).\nexport { walkFiles, isBinaryPath, isGeneratedPath, looksMinified } from \"./walk.js\";\n\n// Analyzable-language coverage (which source languages the scanner inspects).\nexport {\n isAnalyzableSource,\n ANALYZABLE_SOURCE_EXTENSIONS,\n ANALYZABLE_LANGUAGES_LABEL,\n} from \"./detect-utils.js\";\n\n// Inventory + readiness score.\nexport { buildInventory } from \"./inventory.js\";\n\n// Vulnerable-dependency database (the manifest scanner is used internally by scan()).\n// `DEP_VULNERABLE_RULE` is the generic catalog entry for dependency findings\n// (which don't come from a Detector, so aren't in the registry's rule catalog).\nexport { vulnerableDependencies, DEP_VULNERABLE_RULE, isManifestFile } from \"./dependencies.js\";\n\n// Severity utilities (ordering, threshold, SARIF level) — shared across tools.\nexport { SEVERITY_ORDER, severityRank, meetsThreshold, sarifLevel } from \"./severity.js\";\n\n// Reporters.\nexport { toSarif, toJson, formatSummary, formatTierGuidance } from \"./report.js\";\nexport type { ReportOptions } from \"./report.js\";\n\n// CycloneDX 1.6 cryptographic bill of materials (CBOM) export.\nexport { toCbom } from \"./cbom.js\";\nexport type { CycloneDxBom, CbomComponent } from \"./cbom.js\";\n// ISO/IEC 27001 A.8.24 evidence-chain readiness report.\nexport { buildReadinessReport } from \"./evidence.js\";\nexport type { ReadinessReport, EvidenceFinding, ReadinessReportOptions } from \"./evidence.js\";\n\n// Remediation lookup (family + tier-aware) and stateful-HBS guidance.\nexport {\n remediationFor,\n remediationForTier,\n TIER_PARAMS,\n STATEFUL_HBS_NOTE,\n PQC_TRANSITION_NOTE,\n statefulHbsApplies,\n} from \"./remediation.js\";\nexport type { SecurityTier } from \"./remediation.js\";\n\n// CWE identifier constants.\nexport {\n CWE_BROKEN_CRYPTO,\n CWE_WEAK_STRENGTH,\n CWE_CERT_VALIDATION,\n CWE_HARDCODED_KEY,\n CWE_RISKY_PRIMITIVE,\n} from \"./cwe.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @quantakrypto/core — public API (LOCKED CONTRACT).\n *\n * The exported NAMES and SIGNATURES below are the stable contract that\n * @quantakrypto/qscan, @quantakrypto/mcp and the GitHub Action depend on — do not change\n * them without updating all consumers. The implementations live in focused\n * modules under src/ and are re-exported here; the public surface is identical\n * to the original stub file.\n */\nexport * from \"./types.js\";\n\n// Tool version, surfaced in reports. Keep in sync with package.json.\nexport { VERSION } from \"./version.js\";\n\n// Minimal SARIF 2.1.0 log shape, defined alongside the reporters.\nexport type { SarifLog } from \"./report.js\";\n\n// Core orchestration + built-in detector set.\nexport { scan, detectors, detectFile, compareFindings } from \"./scan.js\";\n\n// Snippet-level fix verification (shared by MCP verify_fix + remediation).\nexport { verifyFix, languageToExtension } from \"./verify.js\";\nexport type { VerifyResult } from \"./verify.js\";\n\n// Agent-plane shared types + the context redactor (offline; reused by MCP).\nexport type {\n ContextLevel,\n RedactedContext,\n TriageVerdict,\n Patch,\n FixProposal,\n} from \"./agent-types.js\";\nexport { buildContext, renderPreflight } from \"./redact.js\";\nexport { TRIAGE_RUBRIC, TRIAGE_VERDICT_SCHEMA, buildTriageRequest } from \"./triage.js\";\nexport type { TriageRequest } from \"./triage.js\";\nexport {\n REMEDIATE_RUBRIC,\n FIX_REQUEST_SCHEMA,\n buildRemediateRequest,\n} from \"./remediate-request.js\";\nexport type { RemediateRequest } from \"./remediate-request.js\";\nexport { checkPatchPolicy } from \"./patch-policy.js\";\nexport type { PolicyContext, PolicyDecision } from \"./patch-policy.js\";\nexport { withWorktree } from \"./worktree.js\";\nexport { codemodRegistry, codemodFor } from \"./codemods/registry.js\";\nexport type { Codemod } from \"./codemods/registry.js\";\nexport { configToggleCodemod } from \"./codemods/config-toggle.js\";\nexport { remediateFindings } from \"./remediate-pipeline.js\";\nexport type {\n RemediateOptions,\n RemediationResult,\n VerifiedPatch,\n RejectedPatch,\n} from \"./remediate-pipeline.js\";\n\n// Scan cancellation / work-budget errors.\nexport { AbortError, BudgetExceededError } from \"./errors.js\";\n\n// Parallel scanning (worker_threads pool) + pure merge/chunk helpers.\nexport { scanParallel, mergeChunkResults, chunkByBytes } from \"./parallel.js\";\nexport type { ScanChunk, ChunkResult, SizedFile } from \"./parallel.js\";\n\n// Detector registry (plugin point) + helpers + the rule catalog.\nexport { DetectorRegistry, defaultRegistry, detectorScope } from \"./registry.js\";\nexport type { RuleCatalogEntry } from \"./registry.js\";\n\n// Canonical baseline (shared by qScan + the Action).\nexport {\n fingerprintFinding,\n baselineFromFindings,\n applyBaseline,\n loadBaseline,\n saveBaseline,\n BASELINE_VERSION,\n} from \"./baseline.js\";\nexport type { Baseline } from \"./baseline.js\";\n\n// Incremental scanning: changed-files helper (git-aware, tolerant).\nexport { changedFiles } from \"./changed.js\";\n\n// Optional `quantakrypto.config.json` loader (P2-9; see docs/CONFIG.md).\nexport { loadConfig, ConfigError, CONFIG_FILENAME } from \"./config.js\";\nexport type { QuantakryptoFileConfig, LoadConfigResult } from \"./config.js\";\n\n// Filesystem walker (relative POSIX paths, default ignores, size/binary filters).\nexport { walkFiles, isBinaryPath, isGeneratedPath, looksMinified } from \"./walk.js\";\n\n// Analyzable-language coverage (which source languages the scanner inspects).\nexport {\n isAnalyzableSource,\n ANALYZABLE_SOURCE_EXTENSIONS,\n ANALYZABLE_LANGUAGES_LABEL,\n} from \"./detect-utils.js\";\n\n// Inventory + readiness score.\nexport { buildInventory } from \"./inventory.js\";\n\n// Vulnerable-dependency database (the manifest scanner is used internally by scan()).\n// `DEP_VULNERABLE_RULE` is the generic catalog entry for dependency findings\n// (which don't come from a Detector, so aren't in the registry's rule catalog).\nexport { vulnerableDependencies, DEP_VULNERABLE_RULE, isManifestFile } from \"./dependencies.js\";\n\n// Severity utilities (ordering, threshold, SARIF level) — shared across tools.\nexport { SEVERITY_ORDER, severityRank, meetsThreshold, sarifLevel } from \"./severity.js\";\n\n// Reporters.\nexport { toSarif, toJson, formatSummary, formatTierGuidance } from \"./report.js\";\nexport type { ReportOptions } from \"./report.js\";\n\n// CycloneDX 1.6 cryptographic bill of materials (CBOM) export.\nexport { toCbom } from \"./cbom.js\";\nexport type { CycloneDxBom, CbomComponent } from \"./cbom.js\";\n// Merge multiple CBOMs (code + infra + live endpoints) into one combined BOM.\nexport { mergeCboms } from \"./cbom-merge.js\";\n// ISO/IEC 27001 A.8.24 evidence-chain readiness report.\nexport { buildReadinessReport } from \"./evidence.js\";\nexport type { ReadinessReport, EvidenceFinding, ReadinessReportOptions } from \"./evidence.js\";\n\n// Cryptography policy → per-finding verdicts (A.8.24 evidence §4).\nexport { buildPolicyMapping, parseCryptoPolicy, verdictForAlgorithm } from \"./policy.js\";\nexport type { CryptoPolicy, PolicyVerdict, PolicyMapping, PolicyFindingVerdict } from \"./policy.js\";\n\n// Remediation lookup (family + tier-aware) and stateful-HBS guidance.\nexport {\n remediationFor,\n remediationForTier,\n TIER_PARAMS,\n STATEFUL_HBS_NOTE,\n PQC_TRANSITION_NOTE,\n statefulHbsApplies,\n} from \"./remediation.js\";\nexport type { SecurityTier } from \"./remediation.js\";\n\n// Post-quantum standards source of truth + review cadence.\nexport { PQC_STANDARDS, standardsReviewStatus } from \"./standards.js\";\nexport type { PqcStandards, StandardsCitation, StandardsReviewStatus } from \"./standards.js\";\n\n// CWE identifier constants.\nexport {\n CWE_BROKEN_CRYPTO,\n CWE_WEAK_STRENGTH,\n CWE_CERT_VALIDATION,\n CWE_HARDCODED_KEY,\n CWE_RISKY_PRIMITIVE,\n} from \"./cwe.js\";\n"]}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Machine-readable cryptography policy → per-finding verdicts for the ISO/IEC
3
+ * 27001 A.8.24 evidence report (docs/compliance/iso27001-a8.24-evidence.md §4).
4
+ *
5
+ * An organization supplies a policy listing which classical algorithm families it
6
+ * prohibits, permits, or is actively transitioning off. `buildPolicyMapping` then
7
+ * flags each qScan finding as `conformant` / `violation` / `transition-pending`
8
+ * against that policy — turning the neutral inventory into audit-ready evidence
9
+ * that the org *assesses* its cryptography against a defined policy.
10
+ *
11
+ * Honesty boundary: without a policy the report stays neutral inventory and the
12
+ * ORG owns the conformance judgment (COMPLIANCE.md §3). The policy is a permit-
13
+ * list: a classical family not named anywhere defaults to a violation (unmanaged
14
+ * crypto is a policy gap), overridable via `defaultVerdict`.
15
+ */
16
+ import type { AlgorithmFamily, Finding } from "./types.js";
17
+ /** The three verdicts a finding can carry against a policy. */
18
+ export type PolicyVerdict = "conformant" | "violation" | "transition-pending";
19
+ /** An organization-supplied cryptography policy (from a JSON file). */
20
+ export interface CryptoPolicy {
21
+ /** Human name / version of the policy, recorded in the evidence report. */
22
+ name?: string;
23
+ /** Families the org explicitly accepts (e.g. short-lived signatures) → conformant. */
24
+ permitted?: AlgorithmFamily[];
25
+ /** Families the org prohibits outright → violation. */
26
+ prohibited?: AlgorithmFamily[];
27
+ /** Families being actively migrated, allowed within the window → transition-pending. */
28
+ inTransition?: AlgorithmFamily[];
29
+ /** Optional migration deadline (ISO date / year), recorded for context. */
30
+ transitionDeadline?: string;
31
+ /** Verdict for a family named in none of the lists. Default: `"violation"`. */
32
+ defaultVerdict?: PolicyVerdict;
33
+ }
34
+ /** One finding's verdict against the policy. */
35
+ export interface PolicyFindingVerdict {
36
+ ruleId: string;
37
+ algorithm: AlgorithmFamily | "unknown";
38
+ file: string;
39
+ line: number;
40
+ verdict: PolicyVerdict;
41
+ reason: string;
42
+ }
43
+ /** The `policyMapping` block added to the evidence report. */
44
+ export interface PolicyMapping {
45
+ policyName: string | null;
46
+ transitionDeadline: string | null;
47
+ summary: Record<PolicyVerdict, number>;
48
+ findings: PolicyFindingVerdict[];
49
+ }
50
+ /** Resolve one algorithm family's verdict + a human reason against a policy. */
51
+ export declare function verdictForAlgorithm(algorithm: AlgorithmFamily | undefined, policy: CryptoPolicy): {
52
+ verdict: PolicyVerdict;
53
+ reason: string;
54
+ };
55
+ /**
56
+ * Map every finding to a policy verdict, with per-verdict counts. Deterministic:
57
+ * the same findings + policy always yield the same mapping (safe to hash in the
58
+ * A.8.24 evidence attestation).
59
+ */
60
+ export declare function buildPolicyMapping(findings: readonly Finding[], policy: CryptoPolicy): PolicyMapping;
61
+ /**
62
+ * Validate + normalize a parsed policy object (from an operator's JSON file).
63
+ * Throws a `TypeError` with a clear message on anything malformed — a bad policy
64
+ * file must fail loudly, never silently degrade the evidence verdict.
65
+ */
66
+ export declare function parseCryptoPolicy(raw: unknown): CryptoPolicy;
67
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3D,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,WAAW,GAAG,oBAAoB,CAAC;AAgB9E,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sFAAsF;IACtF,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,uDAAuD;IACvD,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wFAAwF;IACxF,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;IACjC,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,cAAc,CAAC,EAAE,aAAa,CAAC;CAChC;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,eAAe,GAAG,SAAS,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACvC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,eAAe,GAAG,SAAS,EACtC,MAAM,EAAE,YAAY,GACnB;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmB5C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,MAAM,EAAE,YAAY,GACnB,aAAa,CAwBf;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CA+C5D"}
package/dist/policy.js ADDED
@@ -0,0 +1,115 @@
1
+ /** The complete set of algorithm families a policy may reference. */
2
+ const ALGORITHM_FAMILIES = [
3
+ "RSA",
4
+ "ECDH",
5
+ "ECDSA",
6
+ "EdDSA",
7
+ "DH",
8
+ "DSA",
9
+ "X25519",
10
+ "X448",
11
+ "ECIES",
12
+ "unknown",
13
+ ];
14
+ /** Resolve one algorithm family's verdict + a human reason against a policy. */
15
+ export function verdictForAlgorithm(algorithm, policy) {
16
+ const algo = algorithm ?? "unknown";
17
+ if (policy.prohibited?.includes(algo)) {
18
+ return { verdict: "violation", reason: `${algo} is prohibited by the policy.` };
19
+ }
20
+ if (policy.inTransition?.includes(algo)) {
21
+ return {
22
+ verdict: "transition-pending",
23
+ reason: `${algo} is being migrated (in the policy's transition set).`,
24
+ };
25
+ }
26
+ if (policy.permitted?.includes(algo)) {
27
+ return { verdict: "conformant", reason: `${algo} is permitted by the policy.` };
28
+ }
29
+ const fallback = policy.defaultVerdict ?? "violation";
30
+ return {
31
+ verdict: fallback,
32
+ reason: `${algo} is not named in the policy (default verdict: ${fallback}).`,
33
+ };
34
+ }
35
+ /**
36
+ * Map every finding to a policy verdict, with per-verdict counts. Deterministic:
37
+ * the same findings + policy always yield the same mapping (safe to hash in the
38
+ * A.8.24 evidence attestation).
39
+ */
40
+ export function buildPolicyMapping(findings, policy) {
41
+ const summary = {
42
+ conformant: 0,
43
+ violation: 0,
44
+ "transition-pending": 0,
45
+ };
46
+ const mapped = findings.map((f) => {
47
+ const { verdict, reason } = verdictForAlgorithm(f.algorithm, policy);
48
+ summary[verdict]++;
49
+ return {
50
+ ruleId: f.ruleId,
51
+ algorithm: f.algorithm ?? "unknown",
52
+ file: f.location.file,
53
+ line: f.location.line,
54
+ verdict,
55
+ reason,
56
+ };
57
+ });
58
+ return {
59
+ policyName: policy.name ?? null,
60
+ transitionDeadline: policy.transitionDeadline ?? null,
61
+ summary,
62
+ findings: mapped,
63
+ };
64
+ }
65
+ /**
66
+ * Validate + normalize a parsed policy object (from an operator's JSON file).
67
+ * Throws a `TypeError` with a clear message on anything malformed — a bad policy
68
+ * file must fail loudly, never silently degrade the evidence verdict.
69
+ */
70
+ export function parseCryptoPolicy(raw) {
71
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
72
+ throw new TypeError("crypto policy must be a JSON object");
73
+ }
74
+ const obj = raw;
75
+ const validFamilies = new Set(ALGORITHM_FAMILIES);
76
+ const list = (key) => {
77
+ const v = obj[key];
78
+ if (v === undefined)
79
+ return undefined;
80
+ if (!Array.isArray(v))
81
+ throw new TypeError(`policy "${key}" must be an array of algorithm families`);
82
+ for (const item of v) {
83
+ if (typeof item !== "string" || !validFamilies.has(item)) {
84
+ throw new TypeError(`policy "${key}" has an unknown algorithm family ${JSON.stringify(item)}; expected one of ${ALGORITHM_FAMILIES.join(", ")}`);
85
+ }
86
+ }
87
+ return v;
88
+ };
89
+ const verdicts = new Set(["conformant", "violation", "transition-pending"]);
90
+ let defaultVerdict;
91
+ if (obj.defaultVerdict !== undefined) {
92
+ if (typeof obj.defaultVerdict !== "string" ||
93
+ !verdicts.has(obj.defaultVerdict)) {
94
+ throw new TypeError(`policy "defaultVerdict" must be one of ${[...verdicts].join(", ")}`);
95
+ }
96
+ defaultVerdict = obj.defaultVerdict;
97
+ }
98
+ if (obj.name !== undefined && typeof obj.name !== "string") {
99
+ throw new TypeError('policy "name" must be a string');
100
+ }
101
+ if (obj.transitionDeadline !== undefined && typeof obj.transitionDeadline !== "string") {
102
+ throw new TypeError('policy "transitionDeadline" must be a string');
103
+ }
104
+ return {
105
+ ...(obj.name !== undefined ? { name: obj.name } : {}),
106
+ ...(list("permitted") ? { permitted: list("permitted") } : {}),
107
+ ...(list("prohibited") ? { prohibited: list("prohibited") } : {}),
108
+ ...(list("inTransition") ? { inTransition: list("inTransition") } : {}),
109
+ ...(obj.transitionDeadline !== undefined
110
+ ? { transitionDeadline: obj.transitionDeadline }
111
+ : {}),
112
+ ...(defaultVerdict !== undefined ? { defaultVerdict } : {}),
113
+ };
114
+ }
115
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Machine-readable cryptography policy → per-finding verdicts for the ISO/IEC\n * 27001 A.8.24 evidence report (docs/compliance/iso27001-a8.24-evidence.md §4).\n *\n * An organization supplies a policy listing which classical algorithm families it\n * prohibits, permits, or is actively transitioning off. `buildPolicyMapping` then\n * flags each qScan finding as `conformant` / `violation` / `transition-pending`\n * against that policy — turning the neutral inventory into audit-ready evidence\n * that the org *assesses* its cryptography against a defined policy.\n *\n * Honesty boundary: without a policy the report stays neutral inventory and the\n * ORG owns the conformance judgment (COMPLIANCE.md §3). The policy is a permit-\n * list: a classical family not named anywhere defaults to a violation (unmanaged\n * crypto is a policy gap), overridable via `defaultVerdict`.\n */\nimport type { AlgorithmFamily, Finding } from \"./types.js\";\n\n/** The three verdicts a finding can carry against a policy. */\nexport type PolicyVerdict = \"conformant\" | \"violation\" | \"transition-pending\";\n\n/** The complete set of algorithm families a policy may reference. */\nconst ALGORITHM_FAMILIES: readonly AlgorithmFamily[] = [\n \"RSA\",\n \"ECDH\",\n \"ECDSA\",\n \"EdDSA\",\n \"DH\",\n \"DSA\",\n \"X25519\",\n \"X448\",\n \"ECIES\",\n \"unknown\",\n];\n\n/** An organization-supplied cryptography policy (from a JSON file). */\nexport interface CryptoPolicy {\n /** Human name / version of the policy, recorded in the evidence report. */\n name?: string;\n /** Families the org explicitly accepts (e.g. short-lived signatures) → conformant. */\n permitted?: AlgorithmFamily[];\n /** Families the org prohibits outright → violation. */\n prohibited?: AlgorithmFamily[];\n /** Families being actively migrated, allowed within the window → transition-pending. */\n inTransition?: AlgorithmFamily[];\n /** Optional migration deadline (ISO date / year), recorded for context. */\n transitionDeadline?: string;\n /** Verdict for a family named in none of the lists. Default: `\"violation\"`. */\n defaultVerdict?: PolicyVerdict;\n}\n\n/** One finding's verdict against the policy. */\nexport interface PolicyFindingVerdict {\n ruleId: string;\n algorithm: AlgorithmFamily | \"unknown\";\n file: string;\n line: number;\n verdict: PolicyVerdict;\n reason: string;\n}\n\n/** The `policyMapping` block added to the evidence report. */\nexport interface PolicyMapping {\n policyName: string | null;\n transitionDeadline: string | null;\n summary: Record<PolicyVerdict, number>;\n findings: PolicyFindingVerdict[];\n}\n\n/** Resolve one algorithm family's verdict + a human reason against a policy. */\nexport function verdictForAlgorithm(\n algorithm: AlgorithmFamily | undefined,\n policy: CryptoPolicy,\n): { verdict: PolicyVerdict; reason: string } {\n const algo = algorithm ?? \"unknown\";\n if (policy.prohibited?.includes(algo)) {\n return { verdict: \"violation\", reason: `${algo} is prohibited by the policy.` };\n }\n if (policy.inTransition?.includes(algo)) {\n return {\n verdict: \"transition-pending\",\n reason: `${algo} is being migrated (in the policy's transition set).`,\n };\n }\n if (policy.permitted?.includes(algo)) {\n return { verdict: \"conformant\", reason: `${algo} is permitted by the policy.` };\n }\n const fallback = policy.defaultVerdict ?? \"violation\";\n return {\n verdict: fallback,\n reason: `${algo} is not named in the policy (default verdict: ${fallback}).`,\n };\n}\n\n/**\n * Map every finding to a policy verdict, with per-verdict counts. Deterministic:\n * the same findings + policy always yield the same mapping (safe to hash in the\n * A.8.24 evidence attestation).\n */\nexport function buildPolicyMapping(\n findings: readonly Finding[],\n policy: CryptoPolicy,\n): PolicyMapping {\n const summary: Record<PolicyVerdict, number> = {\n conformant: 0,\n violation: 0,\n \"transition-pending\": 0,\n };\n const mapped: PolicyFindingVerdict[] = findings.map((f) => {\n const { verdict, reason } = verdictForAlgorithm(f.algorithm, policy);\n summary[verdict]++;\n return {\n ruleId: f.ruleId,\n algorithm: f.algorithm ?? \"unknown\",\n file: f.location.file,\n line: f.location.line,\n verdict,\n reason,\n };\n });\n return {\n policyName: policy.name ?? null,\n transitionDeadline: policy.transitionDeadline ?? null,\n summary,\n findings: mapped,\n };\n}\n\n/**\n * Validate + normalize a parsed policy object (from an operator's JSON file).\n * Throws a `TypeError` with a clear message on anything malformed — a bad policy\n * file must fail loudly, never silently degrade the evidence verdict.\n */\nexport function parseCryptoPolicy(raw: unknown): CryptoPolicy {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n throw new TypeError(\"crypto policy must be a JSON object\");\n }\n const obj = raw as Record<string, unknown>;\n const validFamilies = new Set<string>(ALGORITHM_FAMILIES);\n const list = (key: string): AlgorithmFamily[] | undefined => {\n const v = obj[key];\n if (v === undefined) return undefined;\n if (!Array.isArray(v))\n throw new TypeError(`policy \"${key}\" must be an array of algorithm families`);\n for (const item of v) {\n if (typeof item !== \"string\" || !validFamilies.has(item)) {\n throw new TypeError(\n `policy \"${key}\" has an unknown algorithm family ${JSON.stringify(item)}; expected one of ${ALGORITHM_FAMILIES.join(\", \")}`,\n );\n }\n }\n return v as AlgorithmFamily[];\n };\n const verdicts = new Set<PolicyVerdict>([\"conformant\", \"violation\", \"transition-pending\"]);\n let defaultVerdict: PolicyVerdict | undefined;\n if (obj.defaultVerdict !== undefined) {\n if (\n typeof obj.defaultVerdict !== \"string\" ||\n !verdicts.has(obj.defaultVerdict as PolicyVerdict)\n ) {\n throw new TypeError(`policy \"defaultVerdict\" must be one of ${[...verdicts].join(\", \")}`);\n }\n defaultVerdict = obj.defaultVerdict as PolicyVerdict;\n }\n if (obj.name !== undefined && typeof obj.name !== \"string\") {\n throw new TypeError('policy \"name\" must be a string');\n }\n if (obj.transitionDeadline !== undefined && typeof obj.transitionDeadline !== \"string\") {\n throw new TypeError('policy \"transitionDeadline\" must be a string');\n }\n return {\n ...(obj.name !== undefined ? { name: obj.name as string } : {}),\n ...(list(\"permitted\") ? { permitted: list(\"permitted\") } : {}),\n ...(list(\"prohibited\") ? { prohibited: list(\"prohibited\") } : {}),\n ...(list(\"inTransition\") ? { inTransition: list(\"inTransition\") } : {}),\n ...(obj.transitionDeadline !== undefined\n ? { transitionDeadline: obj.transitionDeadline as string }\n : {}),\n ...(defaultVerdict !== undefined ? { defaultVerdict } : {}),\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAYpE,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,CAExD;AAED,+FAA+F;AAC/F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IAEtC,4EAA4E;gBAChE,OAAO,GAAE,SAAS,QAAQ,EAAO;IAI7C,kFAAkF;IAClF,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI;IAS3B,wDAAwD;IACxD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAIrC,qDAAqD;IACrD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,uDAAuD;IACvD,GAAG,IAAI,QAAQ,EAAE;IAIjB;;;;;;OAMG;IACH,WAAW,IAAI,QAAQ,EAAE;IAezB,gFAAgF;IAChF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASrD,uEAAuE;IACvE,KAAK,IAAI,gBAAgB;CAG1B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,EAWtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,kBAAyC,CAAC"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA2BpE,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,CAExD;AAED,+FAA+F;AAC/F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IAEtC,4EAA4E;gBAChE,OAAO,GAAE,SAAS,QAAQ,EAAO;IAI7C,kFAAkF;IAClF,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI;IAS3B,wDAAwD;IACxD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAIrC,qDAAqD;IACrD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,uDAAuD;IACvD,GAAG,IAAI,QAAQ,EAAE;IAIjB;;;;;;OAMG;IACH,WAAW,IAAI,QAAQ,EAAE;IAezB,gFAAgF;IAChF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASrD,uEAAuE;IACvE,KAAK,IAAI,gBAAgB;CAG1B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,EA0BtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,kBAAyC,CAAC"}