@quantakrypto/core 0.4.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +76 -27
  2. package/dist/cbom-merge.d.ts.map +1 -1
  3. package/dist/cbom-merge.js +30 -8
  4. package/dist/cbom-merge.js.map +1 -1
  5. package/dist/cbom.d.ts +5 -0
  6. package/dist/cbom.d.ts.map +1 -1
  7. package/dist/cbom.js +132 -37
  8. package/dist/cbom.js.map +1 -1
  9. package/dist/comments.d.ts +2 -23
  10. package/dist/comments.d.ts.map +1 -1
  11. package/dist/comments.js +24 -5
  12. package/dist/comments.js.map +1 -1
  13. package/dist/crypto-agility.d.ts +158 -0
  14. package/dist/crypto-agility.d.ts.map +1 -0
  15. package/dist/crypto-agility.js +285 -0
  16. package/dist/crypto-agility.js.map +1 -0
  17. package/dist/dependencies.d.ts.map +1 -1
  18. package/dist/dependencies.js +180 -18
  19. package/dist/dependencies.js.map +1 -1
  20. package/dist/detect-utils.d.ts +55 -21
  21. package/dist/detect-utils.d.ts.map +1 -1
  22. package/dist/detect-utils.js +133 -7
  23. package/dist/detect-utils.js.map +1 -1
  24. package/dist/detectors/age.d.ts +16 -0
  25. package/dist/detectors/age.d.ts.map +1 -0
  26. package/dist/detectors/age.js +40 -0
  27. package/dist/detectors/age.js.map +1 -0
  28. package/dist/detectors/ansible.d.ts +13 -0
  29. package/dist/detectors/ansible.d.ts.map +1 -0
  30. package/dist/detectors/ansible.js +108 -0
  31. package/dist/detectors/ansible.js.map +1 -0
  32. package/dist/detectors/bicep.d.ts +20 -0
  33. package/dist/detectors/bicep.d.ts.map +1 -0
  34. package/dist/detectors/bicep.js +74 -0
  35. package/dist/detectors/bicep.js.map +1 -0
  36. package/dist/detectors/c.d.ts.map +1 -1
  37. package/dist/detectors/c.js +30 -3
  38. package/dist/detectors/c.js.map +1 -1
  39. package/dist/detectors/cicd.d.ts.map +1 -1
  40. package/dist/detectors/cicd.js +18 -5
  41. package/dist/detectors/cicd.js.map +1 -1
  42. package/dist/detectors/cloud-kms.d.ts +8 -5
  43. package/dist/detectors/cloud-kms.d.ts.map +1 -1
  44. package/dist/detectors/cloud-kms.js +62 -20
  45. package/dist/detectors/cloud-kms.js.map +1 -1
  46. package/dist/detectors/cloudformation.d.ts +8 -0
  47. package/dist/detectors/cloudformation.d.ts.map +1 -1
  48. package/dist/detectors/cloudformation.js +41 -10
  49. package/dist/detectors/cloudformation.js.map +1 -1
  50. package/dist/detectors/codesign.d.ts +50 -0
  51. package/dist/detectors/codesign.d.ts.map +1 -0
  52. package/dist/detectors/codesign.js +143 -0
  53. package/dist/detectors/codesign.js.map +1 -0
  54. package/dist/detectors/csharp.d.ts.map +1 -1
  55. package/dist/detectors/csharp.js +13 -3
  56. package/dist/detectors/csharp.js.map +1 -1
  57. package/dist/detectors/dart.d.ts +35 -0
  58. package/dist/detectors/dart.d.ts.map +1 -0
  59. package/dist/detectors/dart.js +177 -0
  60. package/dist/detectors/dart.js.map +1 -0
  61. package/dist/detectors/database.d.ts +5 -3
  62. package/dist/detectors/database.d.ts.map +1 -1
  63. package/dist/detectors/database.js +45 -8
  64. package/dist/detectors/database.js.map +1 -1
  65. package/dist/detectors/dkim.d.ts +54 -0
  66. package/dist/detectors/dkim.d.ts.map +1 -0
  67. package/dist/detectors/dkim.js +92 -0
  68. package/dist/detectors/dkim.js.map +1 -0
  69. package/dist/detectors/dnssec.d.ts.map +1 -1
  70. package/dist/detectors/dnssec.js +19 -10
  71. package/dist/detectors/dnssec.js.map +1 -1
  72. package/dist/detectors/elixir.d.ts.map +1 -1
  73. package/dist/detectors/elixir.js +34 -2
  74. package/dist/detectors/elixir.js.map +1 -1
  75. package/dist/detectors/java.d.ts.map +1 -1
  76. package/dist/detectors/java.js +33 -3
  77. package/dist/detectors/java.js.map +1 -1
  78. package/dist/detectors/jose.d.ts.map +1 -1
  79. package/dist/detectors/jose.js +13 -3
  80. package/dist/detectors/jose.js.map +1 -1
  81. package/dist/detectors/jwk.d.ts +7 -7
  82. package/dist/detectors/jwk.d.ts.map +1 -1
  83. package/dist/detectors/jwk.js +44 -8
  84. package/dist/detectors/jwk.js.map +1 -1
  85. package/dist/detectors/k8s.d.ts.map +1 -1
  86. package/dist/detectors/k8s.js +5 -2
  87. package/dist/detectors/k8s.js.map +1 -1
  88. package/dist/detectors/keystore.d.ts +19 -0
  89. package/dist/detectors/keystore.d.ts.map +1 -0
  90. package/dist/detectors/keystore.js +76 -0
  91. package/dist/detectors/keystore.js.map +1 -0
  92. package/dist/detectors/mesh.d.ts +7 -8
  93. package/dist/detectors/mesh.d.ts.map +1 -1
  94. package/dist/detectors/mesh.js +17 -35
  95. package/dist/detectors/mesh.js.map +1 -1
  96. package/dist/detectors/messaging.d.ts +2 -2
  97. package/dist/detectors/messaging.d.ts.map +1 -1
  98. package/dist/detectors/messaging.js +19 -12
  99. package/dist/detectors/messaging.js.map +1 -1
  100. package/dist/detectors/objc.d.ts +41 -0
  101. package/dist/detectors/objc.d.ts.map +1 -0
  102. package/dist/detectors/objc.js +146 -0
  103. package/dist/detectors/objc.js.map +1 -0
  104. package/dist/detectors/openpgp.d.ts +20 -0
  105. package/dist/detectors/openpgp.d.ts.map +1 -0
  106. package/dist/detectors/openpgp.js +151 -0
  107. package/dist/detectors/openpgp.js.map +1 -0
  108. package/dist/detectors/pem.d.ts.map +1 -1
  109. package/dist/detectors/pem.js +32 -0
  110. package/dist/detectors/pem.js.map +1 -1
  111. package/dist/detectors/php.d.ts +2 -1
  112. package/dist/detectors/php.d.ts.map +1 -1
  113. package/dist/detectors/php.js +7 -3
  114. package/dist/detectors/php.js.map +1 -1
  115. package/dist/detectors/pkcs11.d.ts +17 -0
  116. package/dist/detectors/pkcs11.d.ts.map +1 -0
  117. package/dist/detectors/pkcs11.js +87 -0
  118. package/dist/detectors/pkcs11.js.map +1 -0
  119. package/dist/detectors/proxy.d.ts +52 -0
  120. package/dist/detectors/proxy.d.ts.map +1 -0
  121. package/dist/detectors/proxy.js +157 -0
  122. package/dist/detectors/proxy.js.map +1 -0
  123. package/dist/detectors/pulumi.d.ts +19 -0
  124. package/dist/detectors/pulumi.d.ts.map +1 -0
  125. package/dist/detectors/pulumi.js +80 -0
  126. package/dist/detectors/pulumi.js.map +1 -0
  127. package/dist/detectors/rust.d.ts.map +1 -1
  128. package/dist/detectors/rust.js +5 -0
  129. package/dist/detectors/rust.js.map +1 -1
  130. package/dist/detectors/secrets.d.ts +4 -1
  131. package/dist/detectors/secrets.d.ts.map +1 -1
  132. package/dist/detectors/secrets.js +5 -21
  133. package/dist/detectors/secrets.js.map +1 -1
  134. package/dist/detectors/solidity.d.ts +50 -0
  135. package/dist/detectors/solidity.d.ts.map +1 -0
  136. package/dist/detectors/solidity.js +108 -0
  137. package/dist/detectors/solidity.js.map +1 -0
  138. package/dist/detectors/source.d.ts.map +1 -1
  139. package/dist/detectors/source.js +105 -6
  140. package/dist/detectors/source.js.map +1 -1
  141. package/dist/detectors/spire.d.ts +49 -0
  142. package/dist/detectors/spire.d.ts.map +1 -0
  143. package/dist/detectors/spire.js +77 -0
  144. package/dist/detectors/spire.js.map +1 -0
  145. package/dist/detectors/ssh-ca.d.ts +72 -0
  146. package/dist/detectors/ssh-ca.d.ts.map +1 -0
  147. package/dist/detectors/ssh-ca.js +119 -0
  148. package/dist/detectors/ssh-ca.js.map +1 -0
  149. package/dist/detectors/supply-chain.d.ts +17 -0
  150. package/dist/detectors/supply-chain.d.ts.map +1 -0
  151. package/dist/detectors/supply-chain.js +89 -0
  152. package/dist/detectors/supply-chain.js.map +1 -0
  153. package/dist/detectors/swift.d.ts +22 -0
  154. package/dist/detectors/swift.d.ts.map +1 -0
  155. package/dist/detectors/swift.js +120 -0
  156. package/dist/detectors/swift.js.map +1 -0
  157. package/dist/detectors/terraform.d.ts.map +1 -1
  158. package/dist/detectors/terraform.js +35 -8
  159. package/dist/detectors/terraform.js.map +1 -1
  160. package/dist/detectors/vault.d.ts +15 -0
  161. package/dist/detectors/vault.d.ts.map +1 -0
  162. package/dist/detectors/vault.js +97 -0
  163. package/dist/detectors/vault.js.map +1 -0
  164. package/dist/detectors/vpn.d.ts +8 -8
  165. package/dist/detectors/vpn.d.ts.map +1 -1
  166. package/dist/detectors/vpn.js +12 -38
  167. package/dist/detectors/vpn.js.map +1 -1
  168. package/dist/detectors/weak-hash.d.ts +71 -0
  169. package/dist/detectors/weak-hash.d.ts.map +1 -0
  170. package/dist/detectors/weak-hash.js +116 -0
  171. package/dist/detectors/weak-hash.js.map +1 -0
  172. package/dist/detectors/webauthn.d.ts +61 -0
  173. package/dist/detectors/webauthn.d.ts.map +1 -0
  174. package/dist/detectors/webauthn.js +109 -0
  175. package/dist/detectors/webauthn.js.map +1 -0
  176. package/dist/detectors/xmldsig.d.ts +23 -0
  177. package/dist/detectors/xmldsig.d.ts.map +1 -0
  178. package/dist/detectors/xmldsig.js +84 -0
  179. package/dist/detectors/xmldsig.js.map +1 -0
  180. package/dist/evidence.d.ts +74 -4
  181. package/dist/evidence.d.ts.map +1 -1
  182. package/dist/evidence.js +71 -0
  183. package/dist/evidence.js.map +1 -1
  184. package/dist/hndl.d.ts +241 -0
  185. package/dist/hndl.d.ts.map +1 -0
  186. package/dist/hndl.js +752 -0
  187. package/dist/hndl.js.map +1 -0
  188. package/dist/index.d.ts +18 -11
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +21 -11
  191. package/dist/index.js.map +1 -1
  192. package/dist/inventory.d.ts +0 -2
  193. package/dist/inventory.d.ts.map +1 -1
  194. package/dist/inventory.js +15 -6
  195. package/dist/inventory.js.map +1 -1
  196. package/dist/parallel.d.ts.map +1 -1
  197. package/dist/parallel.js +22 -23
  198. package/dist/parallel.js.map +1 -1
  199. package/dist/registry.d.ts +9 -4
  200. package/dist/registry.d.ts.map +1 -1
  201. package/dist/registry.js +51 -4
  202. package/dist/registry.js.map +1 -1
  203. package/dist/remediate-pipeline.d.ts +0 -3
  204. package/dist/remediate-pipeline.d.ts.map +1 -1
  205. package/dist/remediate-pipeline.js +1 -1
  206. package/dist/remediate-pipeline.js.map +1 -1
  207. package/dist/remediation.d.ts +10 -0
  208. package/dist/remediation.d.ts.map +1 -1
  209. package/dist/remediation.js +32 -0
  210. package/dist/remediation.js.map +1 -1
  211. package/dist/report.d.ts +17 -0
  212. package/dist/report.d.ts.map +1 -1
  213. package/dist/report.js +106 -20
  214. package/dist/report.js.map +1 -1
  215. package/dist/scan-worker.js +5 -3
  216. package/dist/scan-worker.js.map +1 -1
  217. package/dist/scan.d.ts.map +1 -1
  218. package/dist/scan.js +35 -6
  219. package/dist/scan.js.map +1 -1
  220. package/dist/standards-profiles.d.ts +58 -0
  221. package/dist/standards-profiles.d.ts.map +1 -0
  222. package/dist/standards-profiles.js +100 -0
  223. package/dist/standards-profiles.js.map +1 -0
  224. package/dist/types.d.ts +7 -6
  225. package/dist/types.d.ts.map +1 -1
  226. package/dist/types.js.map +1 -1
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.js +1 -1
  229. package/dist/version.js.map +1 -1
  230. package/dist/vex.d.ts +39 -0
  231. package/dist/vex.d.ts.map +1 -0
  232. package/dist/vex.js +87 -0
  233. package/dist/vex.js.map +1 -0
  234. package/dist/walk.d.ts +39 -4
  235. package/dist/walk.d.ts.map +1 -1
  236. package/dist/walk.js +62 -10
  237. package/dist/walk.js.map +1 -1
  238. package/package.json +1 -1
  239. package/src/cbom-merge.ts +38 -8
  240. package/src/cbom.ts +153 -39
  241. package/src/comments.ts +25 -6
  242. package/src/crypto-agility.ts +407 -0
  243. package/src/dependencies.ts +182 -16
  244. package/src/detect-utils.ts +133 -11
  245. package/src/detectors/age.ts +60 -0
  246. package/src/detectors/ansible.ts +140 -0
  247. package/src/detectors/bicep.ts +107 -0
  248. package/src/detectors/c.ts +31 -3
  249. package/src/detectors/cicd.ts +18 -5
  250. package/src/detectors/cloud-kms.ts +79 -25
  251. package/src/detectors/cloudformation.ts +45 -10
  252. package/src/detectors/codesign.ts +214 -0
  253. package/src/detectors/csharp.ts +13 -3
  254. package/src/detectors/dart.ts +237 -0
  255. package/src/detectors/database.ts +64 -11
  256. package/src/detectors/dkim.ts +172 -0
  257. package/src/detectors/dnssec.ts +19 -10
  258. package/src/detectors/elixir.ts +33 -2
  259. package/src/detectors/java.ts +37 -3
  260. package/src/detectors/jose.ts +19 -3
  261. package/src/detectors/jwk.ts +59 -17
  262. package/src/detectors/k8s.ts +5 -2
  263. package/src/detectors/keystore.ts +109 -0
  264. package/src/detectors/mesh.ts +21 -45
  265. package/src/detectors/messaging.ts +22 -14
  266. package/src/detectors/objc.ts +209 -0
  267. package/src/detectors/openpgp.ts +194 -0
  268. package/src/detectors/pem.ts +30 -0
  269. package/src/detectors/php.ts +9 -4
  270. package/src/detectors/pkcs11.ts +113 -0
  271. package/src/detectors/proxy.ts +236 -0
  272. package/src/detectors/pulumi.ts +106 -0
  273. package/src/detectors/rust.ts +5 -0
  274. package/src/detectors/secrets.ts +15 -26
  275. package/src/detectors/solidity.ts +176 -0
  276. package/src/detectors/source.ts +110 -5
  277. package/src/detectors/spire.ts +143 -0
  278. package/src/detectors/ssh-ca.ts +215 -0
  279. package/src/detectors/supply-chain.ts +117 -0
  280. package/src/detectors/swift.ts +149 -0
  281. package/src/detectors/terraform.ts +42 -8
  282. package/src/detectors/vault.ts +120 -0
  283. package/src/detectors/vpn.ts +20 -48
  284. package/src/detectors/weak-hash.ts +217 -0
  285. package/src/detectors/webauthn.ts +205 -0
  286. package/src/detectors/xmldsig.ts +115 -0
  287. package/src/evidence.ts +127 -4
  288. package/src/hndl.ts +1012 -0
  289. package/src/index.ts +93 -13
  290. package/src/inventory.ts +15 -7
  291. package/src/parallel.ts +21 -20
  292. package/src/registry.ts +51 -4
  293. package/src/remediate-pipeline.ts +1 -1
  294. package/src/remediation.ts +39 -0
  295. package/src/report.ts +120 -20
  296. package/src/scan-worker.ts +5 -3
  297. package/src/scan.ts +41 -6
  298. package/src/standards-profiles.ts +136 -0
  299. package/src/types.ts +18 -5
  300. package/src/version.ts +1 -1
  301. package/src/vex.ts +122 -0
  302. package/src/walk.ts +80 -12
package/dist/report.js CHANGED
@@ -1,8 +1,24 @@
1
1
  import { VERSION } from "./version.js";
2
2
  import { SEVERITY_ORDER, sarifLevel } from "./severity.js";
3
3
  import { ANALYZABLE_LANGUAGES_LABEL } from "./detect-utils.js";
4
- import { remediationFor, remediationForTier } from "./remediation.js";
4
+ import { remediationFor, remediationForTier, remediationForProfile } from "./remediation.js";
5
5
  import { fingerprintFinding } from "./baseline.js";
6
+ import { findingFingerprint } from "./hndl.js";
7
+ /** The per-finding exposure block emitted in JSON / SARIF, or undefined. */
8
+ function exposureFor(f, hndl) {
9
+ if (!hndl)
10
+ return undefined;
11
+ return hndl.byFingerprint.get(findingFingerprint(f));
12
+ }
13
+ /** The repo HNDL summary block shared by JSON output and the SARIF run. */
14
+ function hndlSummaryBlock(hndl) {
15
+ return {
16
+ modelVersion: hndl.modelVersion,
17
+ horizon: hndl.horizon,
18
+ summary: hndl.summary,
19
+ assets: hndl.assets,
20
+ };
21
+ }
6
22
  const SARIF_SCHEMA = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
7
23
  const INFORMATION_URI = "https://github.com/quantakrypto/pqc-tools";
8
24
  /**
@@ -55,6 +71,16 @@ function sarifRule(spec) {
55
71
  : {}),
56
72
  };
57
73
  }
74
+ /** SARIF result.properties fragment for a finding's HNDL exposure, or empty. */
75
+ function exposureProperties(exposure) {
76
+ if (!exposure)
77
+ return {};
78
+ return {
79
+ exposureScore: exposure.exposureScore,
80
+ dataAsset: exposure.dataAsset,
81
+ exposureRationale: exposure.rationale,
82
+ };
83
+ }
58
84
  /** Serialize a scan result as SARIF 2.1.0. */
59
85
  export function toSarif(result, opts) {
60
86
  const redactSnippets = opts?.redactSnippets ?? false;
@@ -119,6 +145,10 @@ export function toSarif(result, opts) {
119
145
  // edit above it. `quantakrypto/v1` names our scheme.
120
146
  partialFingerprints: { "quantakrypto/v1": fingerprintFinding(f) },
121
147
  properties: {
148
+ // Same stable identity mirrored into properties so non-GitHub SARIF
149
+ // consumers (our platform ingest) can read one uniform `fingerprint`
150
+ // field across JSON and SARIF without reaching into partialFingerprints.
151
+ fingerprint: fingerprintFinding(f),
122
152
  category: f.category,
123
153
  severity: f.severity,
124
154
  confidence: f.confidence,
@@ -126,6 +156,7 @@ export function toSarif(result, opts) {
126
156
  ...(f.algorithm ? { algorithm: f.algorithm } : {}),
127
157
  ...(f.remediation ? { remediation: f.remediation } : {}),
128
158
  ...(f.cwe ? { cwe: f.cwe } : {}),
159
+ ...exposureProperties(exposureFor(f, opts?.hndl)),
129
160
  },
130
161
  ...(f.cwe
131
162
  ? {
@@ -178,6 +209,7 @@ export function toSarif(result, opts) {
178
209
  },
179
210
  },
180
211
  ...(taxonomies.length > 0 ? { taxonomies } : {}),
212
+ ...(opts?.hndl ? { properties: { hndl: hndlSummaryBlock(opts.hndl) } } : {}),
181
213
  results,
182
214
  },
183
215
  ],
@@ -201,6 +233,7 @@ function securitySeverity(severity) {
201
233
  /** Serialize a scan result as a plain JSON-friendly object. */
202
234
  export function toJson(result, opts) {
203
235
  const redactSnippets = opts?.redactSnippets ?? false;
236
+ const hndl = opts?.hndl;
204
237
  return {
205
238
  toolVersion: result.toolVersion,
206
239
  root: result.root,
@@ -216,25 +249,48 @@ export function toJson(result, opts) {
216
249
  byCategory: result.inventory.byCategory,
217
250
  byAlgorithm: result.inventory.byAlgorithm,
218
251
  },
219
- findings: result.findings.map((f) => ({
220
- ruleId: f.ruleId,
221
- title: f.title,
222
- category: f.category,
223
- severity: f.severity,
224
- confidence: f.confidence,
225
- algorithm: f.algorithm,
226
- hndl: f.hndl,
227
- message: f.message,
228
- remediation: f.remediation,
229
- cwe: f.cwe,
230
- location: {
231
- file: f.location.file,
232
- line: f.location.line,
233
- column: f.location.column,
234
- endLine: f.location.endLine,
235
- snippet: emittedSnippet(f, redactSnippets),
236
- },
237
- })),
252
+ ...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),
253
+ findings: result.findings.map((f) => {
254
+ const exposure = exposureFor(f, hndl);
255
+ return {
256
+ // Stable, line-INSENSITIVE identity of the finding: sha256 of
257
+ // ruleId | normalized-POSIX-repo-relative-path | normalized-snippet
258
+ // (the SARIF partialFingerprints trick, line number deliberately
259
+ // excluded). Reused verbatim from the baseline module so JSON identity,
260
+ // SARIF partialFingerprints, and the baseline suppression set are one and
261
+ // the same value. A line move does NOT change it; when no snippet context
262
+ // exists it falls back to ruleId|path. This is the cross-run identity the
263
+ // platform keys posture drift on.
264
+ fingerprint: fingerprintFinding(f),
265
+ ruleId: f.ruleId,
266
+ title: f.title,
267
+ category: f.category,
268
+ severity: f.severity,
269
+ confidence: f.confidence,
270
+ algorithm: f.algorithm,
271
+ hndl: f.hndl,
272
+ message: f.message,
273
+ remediation: f.remediation,
274
+ cwe: f.cwe,
275
+ location: {
276
+ file: f.location.file,
277
+ line: f.location.line,
278
+ column: f.location.column,
279
+ endLine: f.location.endLine,
280
+ snippet: emittedSnippet(f, redactSnippets),
281
+ },
282
+ ...(exposure
283
+ ? {
284
+ exposure: {
285
+ fingerprint: exposure.fingerprint,
286
+ exposureScore: exposure.exposureScore,
287
+ dataAsset: exposure.dataAsset,
288
+ rationale: exposure.rationale,
289
+ },
290
+ }
291
+ : {}),
292
+ };
293
+ }),
238
294
  };
239
295
  }
240
296
  /* -------------------------------------------------------------------------- */
@@ -369,4 +425,34 @@ export function formatTierGuidance(byAlgorithm, tier) {
369
425
  }
370
426
  return out;
371
427
  }
428
+ /**
429
+ * Per-family migration targets tailored to a selected {@link StandardsProfile}
430
+ * (`--profile`). Unlike {@link formatTierGuidance} (CNSA-tier only), this surfaces the
431
+ * regime's parameter sets AND its hybrid stance — required (ANSSI/BSI) vs recommended
432
+ * (NIST/NCSC) vs optional (CNSA 2.0) — so guidance isn't regime-wrong. Returns plain
433
+ * (un-coloured) lines; the caller styles them.
434
+ */
435
+ export function formatProfileGuidance(byAlgorithm, profile) {
436
+ const out = [`${profile.name} migration targets:`];
437
+ const seen = new Set();
438
+ for (const [k, n] of Object.entries(byAlgorithm)) {
439
+ if (n <= 0)
440
+ continue;
441
+ const fam = k;
442
+ if (fam === "unknown" || !remediationFor(fam))
443
+ continue; // skip unmapped families
444
+ const rem = remediationForProfile(fam, profile);
445
+ if (seen.has(rem.recommendation))
446
+ continue;
447
+ seen.add(rem.recommendation);
448
+ out.push(` ${fam} → ${rem.recommendation}`);
449
+ }
450
+ const stance = profile.hybridStance === "required"
451
+ ? "requires classical+PQC hybridization"
452
+ : profile.hybridStance === "recommended"
453
+ ? "recommends hybridization"
454
+ : "does not require hybridization";
455
+ out.push(` ${profile.authority} ${stance}; classical public-key crypto disallowed after ${profile.disallowAfter} (${profile.citation}).`);
456
+ return out;
457
+ }
372
458
  //# sourceMappingURL=report.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reporters: turn a {@link ScanResult} into SARIF 2.1.0, a clean JSON object,\n * or a human-readable text summary. No third-party dependencies — ANSI colour\n * is emitted with raw escape codes and is off by default.\n */\nimport type { AlgorithmFamily, Finding, RuleMeta, ScanResult, Severity } from \"./types.js\";\nimport { VERSION } from \"./version.js\";\nimport { SEVERITY_ORDER, sarifLevel } from \"./severity.js\";\nimport { ANALYZABLE_LANGUAGES_LABEL } from \"./detect-utils.js\";\nimport { remediationFor, remediationForTier } from \"./remediation.js\";\nimport type { SecurityTier } from \"./remediation.js\";\nimport { fingerprintFinding } from \"./baseline.js\";\n\n/** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */\nexport interface SarifLog {\n $schema: string;\n version: \"2.1.0\";\n runs: unknown[];\n}\n\n/** Options shared by the structured reporters ({@link toSarif} / {@link toJson}). */\nexport interface ReportOptions {\n /**\n * Omit `location.snippet` from every finding in the output. Defaults to false\n * (snippets are included). Snippets of `sensitive` findings (e.g. PEM key\n * blocks, SSH public keys) are ALWAYS omitted regardless of this flag — the\n * snippet there IS the sensitive value.\n */\n redactSnippets?: boolean;\n /**\n * Full rule catalog to advertise in SARIF `tool.driver.rules[]`, even for\n * rules that produced no finding in this run. Pass\n * `defaultRegistry.ruleCatalog()`. When omitted, only the rules that actually\n * fired are emitted (the historical behaviour). SARIF-only; ignored by\n * {@link toJson}.\n */\n catalog?: RuleMeta[];\n}\n\nconst SARIF_SCHEMA =\n \"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json\";\n\nconst INFORMATION_URI = \"https://github.com/quantakrypto/pqc-tools\";\n\n/**\n * Resolve the snippet to emit for a finding, honouring redaction. Sensitive\n * findings (key material) never expose their snippet; otherwise the snippet is\n * dropped only when `redactSnippets` is set.\n */\nfunction emittedSnippet(f: Finding, redactSnippets: boolean): string | undefined {\n if (redactSnippets || f.sensitive) return undefined;\n return f.location.snippet;\n}\n\n/** Map our severity to a SARIF rule-level default (used in rules[].defaultConfiguration). */\nfunction sarifRank(severity: Severity): number {\n switch (severity) {\n case \"critical\":\n return 100;\n case \"high\":\n return 80;\n case \"medium\":\n return 50;\n case \"low\":\n return 20;\n default:\n return 5;\n }\n}\n\n/** Build a SARIF `rules[]` entry from a rule's severity/title/message/etc. */\nfunction sarifRule(spec: {\n id: string;\n title: string;\n message: string;\n severity: Severity;\n category: string;\n algorithm?: string;\n hndl: boolean;\n cwe?: string;\n remediation?: string;\n}): Record<string, unknown> {\n return {\n id: spec.id,\n name: spec.id,\n shortDescription: { text: spec.title },\n fullDescription: { text: spec.message },\n defaultConfiguration: { level: sarifLevel(spec.severity), rank: sarifRank(spec.severity) },\n ...(spec.remediation ? { help: { text: `Remediation: ${spec.remediation}` } } : {}),\n properties: {\n category: spec.category,\n ...(spec.algorithm ? { algorithm: spec.algorithm } : {}),\n hndl: spec.hndl,\n ...(spec.cwe ? { cwe: spec.cwe, \"security-severity\": securitySeverity(spec.severity) } : {}),\n ...(spec.cwe ? { tags: [\"security\", spec.cwe] } : {}),\n },\n ...(spec.cwe\n ? {\n relationships: [\n { target: { id: spec.cwe, toolComponent: { name: \"CWE\" } }, kinds: [\"relevant\"] },\n ],\n }\n : {}),\n };\n}\n\n/** Serialize a scan result as SARIF 2.1.0. */\nexport function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {\n const redactSnippets = opts?.redactSnippets ?? false;\n // Build the rule set and collect the CWE taxa referenced by any rule. When a\n // full catalog is supplied, advertise every rule (even ones that didn't fire);\n // otherwise emit one rule per ruleId encountered (the historical behaviour).\n const ruleIndex = new Map<string, number>();\n const rules: Array<Record<string, unknown>> = [];\n const cweTaxa = new Set<string>();\n\n for (const r of opts?.catalog ?? []) {\n if (ruleIndex.has(r.id)) continue;\n if (r.cwe) cweTaxa.add(r.cwe);\n ruleIndex.set(r.id, rules.length);\n rules.push(\n sarifRule({\n id: r.id,\n title: r.title,\n message: r.message,\n severity: r.severity,\n category: r.category,\n algorithm: r.algorithm,\n hndl: r.hndl,\n cwe: r.cwe,\n remediation: r.remediation,\n }),\n );\n }\n\n for (const f of result.findings) {\n if (f.cwe) cweTaxa.add(f.cwe);\n if (ruleIndex.has(f.ruleId)) continue;\n ruleIndex.set(f.ruleId, rules.length);\n rules.push(\n sarifRule({\n id: f.ruleId,\n title: f.title,\n message: f.message,\n severity: f.severity,\n category: f.category,\n algorithm: f.algorithm,\n hndl: f.hndl,\n cwe: f.cwe,\n remediation: f.remediation,\n }),\n );\n }\n\n const results = result.findings.map((f) => {\n const region: Record<string, number> = { startLine: f.location.line };\n if (typeof f.location.column === \"number\") region.startColumn = f.location.column;\n if (typeof f.location.endLine === \"number\") region.endLine = f.location.endLine;\n const snippet = emittedSnippet(f, redactSnippets);\n\n return {\n ruleId: f.ruleId,\n ruleIndex: ruleIndex.get(f.ruleId),\n level: sarifLevel(f.severity),\n message: { text: f.message },\n // Line-INSENSITIVE fingerprint (the same one the baseline uses:\n // sha256 of ruleId|file|normalizedSnippet). GitHub code scanning keys\n // alert identity + dedup off partialFingerprints, so a finding survives\n // line shifts and reformatting instead of re-alerting as \"new\" on every\n // edit above it. `quantakrypto/v1` names our scheme.\n partialFingerprints: { \"quantakrypto/v1\": fingerprintFinding(f) },\n properties: {\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n hndl: f.hndl,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n ...(f.remediation ? { remediation: f.remediation } : {}),\n ...(f.cwe ? { cwe: f.cwe } : {}),\n },\n ...(f.cwe\n ? {\n taxa: [\n {\n target: { id: f.cwe, toolComponent: { name: \"CWE\" } },\n },\n ],\n }\n : {}),\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: f.location.file },\n region: {\n ...region,\n ...(snippet ? { snippet: { text: snippet } } : {}),\n },\n },\n },\n ],\n };\n });\n\n // CWE taxonomy component (SARIF taxonomies), referenced by rules + results.\n const taxonomies =\n cweTaxa.size > 0\n ? [\n {\n name: \"CWE\",\n informationUri: \"https://cwe.mitre.org/\",\n organization: \"MITRE\",\n shortDescription: { text: \"The MITRE Common Weakness Enumeration\" },\n taxa: [...cweTaxa].sort().map((id) => ({\n id,\n helpUri: `https://cwe.mitre.org/data/definitions/${id.replace(/^CWE-/, \"\")}.html`,\n })),\n },\n ]\n : [];\n\n return {\n $schema: SARIF_SCHEMA,\n version: \"2.1.0\",\n runs: [\n {\n tool: {\n driver: {\n name: \"qScan\",\n informationUri: INFORMATION_URI,\n version: result.toolVersion || VERSION,\n rules,\n },\n },\n ...(taxonomies.length > 0 ? { taxonomies } : {}),\n results,\n },\n ],\n };\n}\n\n/** GitHub-code-scanning `security-severity` (0–10) derived from our severity. */\nfunction securitySeverity(severity: Severity): string {\n switch (severity) {\n case \"critical\":\n return \"9.5\";\n case \"high\":\n return \"8.0\";\n case \"medium\":\n return \"5.0\";\n case \"low\":\n return \"3.0\";\n default:\n return \"1.0\";\n }\n}\n\n/** Serialize a scan result as a plain JSON-friendly object. */\nexport function toJson(result: ScanResult, opts?: ReportOptions): Record<string, unknown> {\n const redactSnippets = opts?.redactSnippets ?? false;\n return {\n toolVersion: result.toolVersion,\n root: result.root,\n startedAt: result.startedAt,\n finishedAt: result.finishedAt,\n filesScanned: result.filesScanned,\n ...(result.analyzedFiles !== undefined ? { analyzedFiles: result.analyzedFiles } : {}),\n ...(result.diagnostics ? { diagnostics: result.diagnostics } : {}),\n inventory: {\n readinessScore: result.inventory.readinessScore,\n hndlCount: result.inventory.hndlCount,\n bySeverity: result.inventory.bySeverity,\n byCategory: result.inventory.byCategory,\n byAlgorithm: result.inventory.byAlgorithm,\n },\n findings: result.findings.map((f) => ({\n ruleId: f.ruleId,\n title: f.title,\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n algorithm: f.algorithm,\n hndl: f.hndl,\n message: f.message,\n remediation: f.remediation,\n cwe: f.cwe,\n location: {\n file: f.location.file,\n line: f.location.line,\n column: f.location.column,\n endLine: f.location.endLine,\n snippet: emittedSnippet(f, redactSnippets),\n },\n })),\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Human-readable summary */\n/* -------------------------------------------------------------------------- */\n\n/** Raw ANSI codes (no chalk). Disabled when colour is off. */\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n} as const;\n\nfunction severityColor(sev: Severity): string {\n switch (sev) {\n case \"critical\":\n return ANSI.magenta;\n case \"high\":\n return ANSI.red;\n case \"medium\":\n return ANSI.yellow;\n case \"low\":\n return ANSI.blue;\n default:\n return ANSI.dim;\n }\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return ANSI.green;\n if (score >= 50) return ANSI.yellow;\n return ANSI.red;\n}\n\n/**\n * Render a human-readable summary of a scan result. Colour is off by default;\n * pass `{ color: true }` to emit ANSI escape codes.\n */\nexport function formatSummary(\n result: ScanResult,\n options?: { color?: boolean; tier?: SecurityTier },\n): string {\n const color = options?.color ?? false;\n const c = (code: string, text: string): string => (color ? `${code}${text}${ANSI.reset}` : text);\n\n const lines: string[] = [];\n const inv = result.inventory;\n\n lines.push(c(ANSI.bold, \"qScan — post-quantum readiness report\"));\n lines.push(c(ANSI.dim, `tool v${result.toolVersion} · root: ${result.root}`));\n lines.push(\"\");\n\n // Readiness score banner.\n lines.push(\n `Readiness score: ${c(`${ANSI.bold}${scoreColor(inv.readinessScore)}`, `${inv.readinessScore}/100`)}`,\n );\n const analyzed =\n result.analyzedFiles !== undefined\n ? ` Analyzed (${ANALYZABLE_LANGUAGES_LABEL}): ${result.analyzedFiles}`\n : \"\";\n lines.push(\n `Files scanned: ${result.filesScanned}${analyzed} Findings: ${result.findings.length} HNDL-exposed: ${c(\n inv.hndlCount > 0 ? ANSI.red : ANSI.green,\n String(inv.hndlCount),\n )}`,\n );\n // Coverage honesty: a score over zero analyzable files is not a clean bill of health.\n if (result.analyzedFiles === 0 && result.filesScanned > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: 0 files were in a supported source language (${ANALYZABLE_LANGUAGES_LABEL}) — the readiness score does not reflect this codebase.`,\n ),\n );\n }\n // Coverage diagnostics: skipped files mean the finding count may be incomplete.\n const diag = result.diagnostics;\n if (diag && (diag.unreadable > 0 || diag.skippedMinified > 0)) {\n lines.push(\n c(\n ANSI.yellow,\n `Coverage: ${diag.unreadable} unreadable, ${diag.skippedMinified} skipped as minified — results may be incomplete.`,\n ),\n );\n }\n lines.push(\"\");\n\n // Severity breakdown.\n const sevParts = SEVERITY_ORDER.filter((s) => inv.bySeverity[s] > 0).map((s) =>\n c(severityColor(s), `${s}: ${inv.bySeverity[s]}`),\n );\n lines.push(`By severity: ${sevParts.length ? sevParts.join(\" \") : c(ANSI.green, \"none\")}`);\n\n // Algorithm breakdown.\n const algoParts = Object.entries(inv.byAlgorithm)\n .sort((a, b) => b[1] - a[1])\n .map(([k, v]) => `${k}: ${v}`);\n if (algoParts.length) lines.push(`By algorithm: ${algoParts.join(\" \")}`);\n lines.push(\"\");\n\n if (result.findings.length === 0) {\n lines.push(c(ANSI.green, \"No classical asymmetric cryptography detected. ✓\"));\n return lines.join(\"\\n\");\n }\n\n // Top findings, grouped by severity (most severe first), capped for readability.\n const sorted = [...result.findings].sort(\n (a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity),\n );\n const MAX_SHOWN = 25;\n lines.push(\n c(ANSI.bold, `Top findings (${Math.min(MAX_SHOWN, sorted.length)} of ${sorted.length}):`),\n );\n\n for (const f of sorted.slice(0, MAX_SHOWN)) {\n const loc = `${f.location.file}:${f.location.line}${\n f.location.column ? `:${f.location.column}` : \"\"\n }`;\n const tag = c(severityColor(f.severity), `[${f.severity}]`);\n const hndl = f.hndl ? c(ANSI.red, \" (HNDL)\") : \"\";\n lines.push(` ${tag} ${f.title}${hndl}`);\n lines.push(c(ANSI.dim, ` ${loc} — ${f.message}`));\n if (f.remediation) lines.push(c(ANSI.cyan, ` → ${f.remediation}`));\n }\n\n if (sorted.length > MAX_SHOWN) {\n lines.push(c(ANSI.dim, ` …and ${sorted.length - MAX_SHOWN} more.`));\n }\n\n lines.push(\"\");\n if (inv.hndlCount > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: ${inv.hndlCount} finding(s) are exposed to \"harvest now, decrypt later\" — ` +\n \"encrypted traffic captured today can be decrypted once a quantum computer exists. \" +\n \"Prioritise migrating key exchange / encryption to hybrid PQC (X25519MLKEM768).\",\n ),\n );\n }\n\n if (options?.tier) {\n lines.push(\"\", ...formatTierGuidance(inv.byAlgorithm, options.tier));\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Per-family migration targets for a CNSA security tier — surfaces the otherwise\n * library-only {@link remediationForTier} in human reports. Category 5 shows the\n * ML-KEM-1024 / ML-DSA-87 sets CNSA 2.0 mandates for national-security systems and\n * long-lived secrets. Returns plain (un-coloured) lines; the caller styles them.\n */\nexport function formatTierGuidance(\n byAlgorithm: Record<string, number>,\n tier: SecurityTier,\n): string[] {\n const label = tier === \"category-5\" ? \"CNSA 2.0 (Category 5)\" : \"Category 3 (commercial)\";\n const out: string[] = [`${label} migration targets:`];\n const seen = new Set<string>();\n for (const [k, n] of Object.entries(byAlgorithm)) {\n if (n <= 0) continue;\n const fam = k as AlgorithmFamily;\n if (fam === \"unknown\" || !remediationFor(fam)) continue; // skip unmapped families\n const rem = remediationForTier(fam, tier);\n if (seen.has(rem.recommendation)) continue;\n seen.add(rem.recommendation);\n out.push(` ${fam} → ${rem.recommendation}`);\n }\n if (tier === \"category-5\") {\n out.push(\n \" CNSA 2.0 mandates ML-KEM-1024 / ML-DSA-87 for national-security systems and long-lived secrets (2030/2033 milestones).\",\n );\n }\n return out;\n}\n"]}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reporters: turn a {@link ScanResult} into SARIF 2.1.0, a clean JSON object,\n * or a human-readable text summary. No third-party dependencies — ANSI colour\n * is emitted with raw escape codes and is off by default.\n */\nimport type { AlgorithmFamily, Finding, RuleMeta, ScanResult, Severity } from \"./types.js\";\nimport { VERSION } from \"./version.js\";\nimport { SEVERITY_ORDER, sarifLevel } from \"./severity.js\";\nimport { ANALYZABLE_LANGUAGES_LABEL } from \"./detect-utils.js\";\nimport { remediationFor, remediationForTier, remediationForProfile } from \"./remediation.js\";\nimport type { SecurityTier } from \"./remediation.js\";\nimport type { StandardsProfile } from \"./standards-profiles.js\";\nimport { fingerprintFinding } from \"./baseline.js\";\nimport { findingFingerprint } from \"./hndl.js\";\nimport type { FindingExposure, HndlReport } from \"./hndl.js\";\n\n/** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */\nexport interface SarifLog {\n $schema: string;\n version: \"2.1.0\";\n runs: unknown[];\n}\n\n/** Options shared by the structured reporters ({@link toSarif} / {@link toJson}). */\nexport interface ReportOptions {\n /**\n * Omit `location.snippet` from every finding in the output. Defaults to false\n * (snippets are included). Snippets of `sensitive` findings (e.g. PEM key\n * blocks, SSH public keys) are ALWAYS omitted regardless of this flag — the\n * snippet there IS the sensitive value.\n */\n redactSnippets?: boolean;\n /**\n * Full rule catalog to advertise in SARIF `tool.driver.rules[]`, even for\n * rules that produced no finding in this run. Pass\n * `defaultRegistry.ruleCatalog()`. When omitted, only the rules that actually\n * fired are emitted (the historical behaviour). SARIF-only; ignored by\n * {@link toJson}.\n */\n catalog?: RuleMeta[];\n /**\n * Optional HNDL exposure analysis ({@link computeHndl}). When supplied, each\n * finding gains its `exposure` fields (score, bound data asset, rationale)\n * keyed by fingerprint, and the report carries the repo-level HNDL summary.\n * Purely additive: it never changes finding identity, ordering, or exit codes.\n */\n hndl?: HndlReport;\n}\n\n/** The per-finding exposure block emitted in JSON / SARIF, or undefined. */\nfunction exposureFor(f: Finding, hndl: HndlReport | undefined): FindingExposure | undefined {\n if (!hndl) return undefined;\n return hndl.byFingerprint.get(findingFingerprint(f));\n}\n\n/** The repo HNDL summary block shared by JSON output and the SARIF run. */\nfunction hndlSummaryBlock(hndl: HndlReport): Record<string, unknown> {\n return {\n modelVersion: hndl.modelVersion,\n horizon: hndl.horizon,\n summary: hndl.summary,\n assets: hndl.assets,\n };\n}\n\nconst SARIF_SCHEMA =\n \"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json\";\n\nconst INFORMATION_URI = \"https://github.com/quantakrypto/pqc-tools\";\n\n/**\n * Resolve the snippet to emit for a finding, honouring redaction. Sensitive\n * findings (key material) never expose their snippet; otherwise the snippet is\n * dropped only when `redactSnippets` is set.\n */\nfunction emittedSnippet(f: Finding, redactSnippets: boolean): string | undefined {\n if (redactSnippets || f.sensitive) return undefined;\n return f.location.snippet;\n}\n\n/** Map our severity to a SARIF rule-level default (used in rules[].defaultConfiguration). */\nfunction sarifRank(severity: Severity): number {\n switch (severity) {\n case \"critical\":\n return 100;\n case \"high\":\n return 80;\n case \"medium\":\n return 50;\n case \"low\":\n return 20;\n default:\n return 5;\n }\n}\n\n/** Build a SARIF `rules[]` entry from a rule's severity/title/message/etc. */\nfunction sarifRule(spec: {\n id: string;\n title: string;\n message: string;\n severity: Severity;\n category: string;\n algorithm?: string;\n hndl: boolean;\n cwe?: string;\n remediation?: string;\n}): Record<string, unknown> {\n return {\n id: spec.id,\n name: spec.id,\n shortDescription: { text: spec.title },\n fullDescription: { text: spec.message },\n defaultConfiguration: { level: sarifLevel(spec.severity), rank: sarifRank(spec.severity) },\n ...(spec.remediation ? { help: { text: `Remediation: ${spec.remediation}` } } : {}),\n properties: {\n category: spec.category,\n ...(spec.algorithm ? { algorithm: spec.algorithm } : {}),\n hndl: spec.hndl,\n ...(spec.cwe ? { cwe: spec.cwe, \"security-severity\": securitySeverity(spec.severity) } : {}),\n ...(spec.cwe ? { tags: [\"security\", spec.cwe] } : {}),\n },\n ...(spec.cwe\n ? {\n relationships: [\n { target: { id: spec.cwe, toolComponent: { name: \"CWE\" } }, kinds: [\"relevant\"] },\n ],\n }\n : {}),\n };\n}\n\n/** SARIF result.properties fragment for a finding's HNDL exposure, or empty. */\nfunction exposureProperties(exposure: FindingExposure | undefined): Record<string, unknown> {\n if (!exposure) return {};\n return {\n exposureScore: exposure.exposureScore,\n dataAsset: exposure.dataAsset,\n exposureRationale: exposure.rationale,\n };\n}\n\n/** Serialize a scan result as SARIF 2.1.0. */\nexport function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {\n const redactSnippets = opts?.redactSnippets ?? false;\n // Build the rule set and collect the CWE taxa referenced by any rule. When a\n // full catalog is supplied, advertise every rule (even ones that didn't fire);\n // otherwise emit one rule per ruleId encountered (the historical behaviour).\n const ruleIndex = new Map<string, number>();\n const rules: Array<Record<string, unknown>> = [];\n const cweTaxa = new Set<string>();\n\n for (const r of opts?.catalog ?? []) {\n if (ruleIndex.has(r.id)) continue;\n if (r.cwe) cweTaxa.add(r.cwe);\n ruleIndex.set(r.id, rules.length);\n rules.push(\n sarifRule({\n id: r.id,\n title: r.title,\n message: r.message,\n severity: r.severity,\n category: r.category,\n algorithm: r.algorithm,\n hndl: r.hndl,\n cwe: r.cwe,\n remediation: r.remediation,\n }),\n );\n }\n\n for (const f of result.findings) {\n if (f.cwe) cweTaxa.add(f.cwe);\n if (ruleIndex.has(f.ruleId)) continue;\n ruleIndex.set(f.ruleId, rules.length);\n rules.push(\n sarifRule({\n id: f.ruleId,\n title: f.title,\n message: f.message,\n severity: f.severity,\n category: f.category,\n algorithm: f.algorithm,\n hndl: f.hndl,\n cwe: f.cwe,\n remediation: f.remediation,\n }),\n );\n }\n\n const results = result.findings.map((f) => {\n const region: Record<string, number> = { startLine: f.location.line };\n if (typeof f.location.column === \"number\") region.startColumn = f.location.column;\n if (typeof f.location.endLine === \"number\") region.endLine = f.location.endLine;\n const snippet = emittedSnippet(f, redactSnippets);\n\n return {\n ruleId: f.ruleId,\n ruleIndex: ruleIndex.get(f.ruleId),\n level: sarifLevel(f.severity),\n message: { text: f.message },\n // Line-INSENSITIVE fingerprint (the same one the baseline uses:\n // sha256 of ruleId|file|normalizedSnippet). GitHub code scanning keys\n // alert identity + dedup off partialFingerprints, so a finding survives\n // line shifts and reformatting instead of re-alerting as \"new\" on every\n // edit above it. `quantakrypto/v1` names our scheme.\n partialFingerprints: { \"quantakrypto/v1\": fingerprintFinding(f) },\n properties: {\n // Same stable identity mirrored into properties so non-GitHub SARIF\n // consumers (our platform ingest) can read one uniform `fingerprint`\n // field across JSON and SARIF without reaching into partialFingerprints.\n fingerprint: fingerprintFinding(f),\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n hndl: f.hndl,\n ...(f.algorithm ? { algorithm: f.algorithm } : {}),\n ...(f.remediation ? { remediation: f.remediation } : {}),\n ...(f.cwe ? { cwe: f.cwe } : {}),\n ...exposureProperties(exposureFor(f, opts?.hndl)),\n },\n ...(f.cwe\n ? {\n taxa: [\n {\n target: { id: f.cwe, toolComponent: { name: \"CWE\" } },\n },\n ],\n }\n : {}),\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: f.location.file },\n region: {\n ...region,\n ...(snippet ? { snippet: { text: snippet } } : {}),\n },\n },\n },\n ],\n };\n });\n\n // CWE taxonomy component (SARIF taxonomies), referenced by rules + results.\n const taxonomies =\n cweTaxa.size > 0\n ? [\n {\n name: \"CWE\",\n informationUri: \"https://cwe.mitre.org/\",\n organization: \"MITRE\",\n shortDescription: { text: \"The MITRE Common Weakness Enumeration\" },\n taxa: [...cweTaxa].sort().map((id) => ({\n id,\n helpUri: `https://cwe.mitre.org/data/definitions/${id.replace(/^CWE-/, \"\")}.html`,\n })),\n },\n ]\n : [];\n\n return {\n $schema: SARIF_SCHEMA,\n version: \"2.1.0\",\n runs: [\n {\n tool: {\n driver: {\n name: \"qScan\",\n informationUri: INFORMATION_URI,\n version: result.toolVersion || VERSION,\n rules,\n },\n },\n ...(taxonomies.length > 0 ? { taxonomies } : {}),\n ...(opts?.hndl ? { properties: { hndl: hndlSummaryBlock(opts.hndl) } } : {}),\n results,\n },\n ],\n };\n}\n\n/** GitHub-code-scanning `security-severity` (0–10) derived from our severity. */\nfunction securitySeverity(severity: Severity): string {\n switch (severity) {\n case \"critical\":\n return \"9.5\";\n case \"high\":\n return \"8.0\";\n case \"medium\":\n return \"5.0\";\n case \"low\":\n return \"3.0\";\n default:\n return \"1.0\";\n }\n}\n\n/** Serialize a scan result as a plain JSON-friendly object. */\nexport function toJson(result: ScanResult, opts?: ReportOptions): Record<string, unknown> {\n const redactSnippets = opts?.redactSnippets ?? false;\n const hndl = opts?.hndl;\n return {\n toolVersion: result.toolVersion,\n root: result.root,\n startedAt: result.startedAt,\n finishedAt: result.finishedAt,\n filesScanned: result.filesScanned,\n ...(result.analyzedFiles !== undefined ? { analyzedFiles: result.analyzedFiles } : {}),\n ...(result.diagnostics ? { diagnostics: result.diagnostics } : {}),\n inventory: {\n readinessScore: result.inventory.readinessScore,\n hndlCount: result.inventory.hndlCount,\n bySeverity: result.inventory.bySeverity,\n byCategory: result.inventory.byCategory,\n byAlgorithm: result.inventory.byAlgorithm,\n },\n ...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),\n findings: result.findings.map((f) => {\n const exposure = exposureFor(f, hndl);\n return {\n // Stable, line-INSENSITIVE identity of the finding: sha256 of\n // ruleId | normalized-POSIX-repo-relative-path | normalized-snippet\n // (the SARIF partialFingerprints trick, line number deliberately\n // excluded). Reused verbatim from the baseline module so JSON identity,\n // SARIF partialFingerprints, and the baseline suppression set are one and\n // the same value. A line move does NOT change it; when no snippet context\n // exists it falls back to ruleId|path. This is the cross-run identity the\n // platform keys posture drift on.\n fingerprint: fingerprintFinding(f),\n ruleId: f.ruleId,\n title: f.title,\n category: f.category,\n severity: f.severity,\n confidence: f.confidence,\n algorithm: f.algorithm,\n hndl: f.hndl,\n message: f.message,\n remediation: f.remediation,\n cwe: f.cwe,\n location: {\n file: f.location.file,\n line: f.location.line,\n column: f.location.column,\n endLine: f.location.endLine,\n snippet: emittedSnippet(f, redactSnippets),\n },\n ...(exposure\n ? {\n exposure: {\n fingerprint: exposure.fingerprint,\n exposureScore: exposure.exposureScore,\n dataAsset: exposure.dataAsset,\n rationale: exposure.rationale,\n },\n }\n : {}),\n };\n }),\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Human-readable summary */\n/* -------------------------------------------------------------------------- */\n\n/** Raw ANSI codes (no chalk). Disabled when colour is off. */\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n} as const;\n\nfunction severityColor(sev: Severity): string {\n switch (sev) {\n case \"critical\":\n return ANSI.magenta;\n case \"high\":\n return ANSI.red;\n case \"medium\":\n return ANSI.yellow;\n case \"low\":\n return ANSI.blue;\n default:\n return ANSI.dim;\n }\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return ANSI.green;\n if (score >= 50) return ANSI.yellow;\n return ANSI.red;\n}\n\n/**\n * Render a human-readable summary of a scan result. Colour is off by default;\n * pass `{ color: true }` to emit ANSI escape codes.\n */\nexport function formatSummary(\n result: ScanResult,\n options?: { color?: boolean; tier?: SecurityTier },\n): string {\n const color = options?.color ?? false;\n const c = (code: string, text: string): string => (color ? `${code}${text}${ANSI.reset}` : text);\n\n const lines: string[] = [];\n const inv = result.inventory;\n\n lines.push(c(ANSI.bold, \"qScan — post-quantum readiness report\"));\n lines.push(c(ANSI.dim, `tool v${result.toolVersion} · root: ${result.root}`));\n lines.push(\"\");\n\n // Readiness score banner.\n lines.push(\n `Readiness score: ${c(`${ANSI.bold}${scoreColor(inv.readinessScore)}`, `${inv.readinessScore}/100`)}`,\n );\n const analyzed =\n result.analyzedFiles !== undefined\n ? ` Analyzed (${ANALYZABLE_LANGUAGES_LABEL}): ${result.analyzedFiles}`\n : \"\";\n lines.push(\n `Files scanned: ${result.filesScanned}${analyzed} Findings: ${result.findings.length} HNDL-exposed: ${c(\n inv.hndlCount > 0 ? ANSI.red : ANSI.green,\n String(inv.hndlCount),\n )}`,\n );\n // Coverage honesty: a score over zero analyzable files is not a clean bill of health.\n if (result.analyzedFiles === 0 && result.filesScanned > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: 0 files were in a supported source language (${ANALYZABLE_LANGUAGES_LABEL}) — the readiness score does not reflect this codebase.`,\n ),\n );\n }\n // Coverage diagnostics: skipped files mean the finding count may be incomplete.\n const diag = result.diagnostics;\n if (diag && (diag.unreadable > 0 || diag.skippedMinified > 0)) {\n lines.push(\n c(\n ANSI.yellow,\n `Coverage: ${diag.unreadable} unreadable, ${diag.skippedMinified} skipped as minified — results may be incomplete.`,\n ),\n );\n }\n lines.push(\"\");\n\n // Severity breakdown.\n const sevParts = SEVERITY_ORDER.filter((s) => inv.bySeverity[s] > 0).map((s) =>\n c(severityColor(s), `${s}: ${inv.bySeverity[s]}`),\n );\n lines.push(`By severity: ${sevParts.length ? sevParts.join(\" \") : c(ANSI.green, \"none\")}`);\n\n // Algorithm breakdown.\n const algoParts = Object.entries(inv.byAlgorithm)\n .sort((a, b) => b[1] - a[1])\n .map(([k, v]) => `${k}: ${v}`);\n if (algoParts.length) lines.push(`By algorithm: ${algoParts.join(\" \")}`);\n lines.push(\"\");\n\n if (result.findings.length === 0) {\n lines.push(c(ANSI.green, \"No classical asymmetric cryptography detected. ✓\"));\n return lines.join(\"\\n\");\n }\n\n // Top findings, grouped by severity (most severe first), capped for readability.\n const sorted = [...result.findings].sort(\n (a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity),\n );\n const MAX_SHOWN = 25;\n lines.push(\n c(ANSI.bold, `Top findings (${Math.min(MAX_SHOWN, sorted.length)} of ${sorted.length}):`),\n );\n\n for (const f of sorted.slice(0, MAX_SHOWN)) {\n const loc = `${f.location.file}:${f.location.line}${\n f.location.column ? `:${f.location.column}` : \"\"\n }`;\n const tag = c(severityColor(f.severity), `[${f.severity}]`);\n const hndl = f.hndl ? c(ANSI.red, \" (HNDL)\") : \"\";\n lines.push(` ${tag} ${f.title}${hndl}`);\n lines.push(c(ANSI.dim, ` ${loc} — ${f.message}`));\n if (f.remediation) lines.push(c(ANSI.cyan, ` → ${f.remediation}`));\n }\n\n if (sorted.length > MAX_SHOWN) {\n lines.push(c(ANSI.dim, ` …and ${sorted.length - MAX_SHOWN} more.`));\n }\n\n lines.push(\"\");\n if (inv.hndlCount > 0) {\n lines.push(\n c(\n ANSI.yellow,\n `Note: ${inv.hndlCount} finding(s) are exposed to \"harvest now, decrypt later\" — ` +\n \"encrypted traffic captured today can be decrypted once a quantum computer exists. \" +\n \"Prioritise migrating key exchange / encryption to hybrid PQC (X25519MLKEM768).\",\n ),\n );\n }\n\n if (options?.tier) {\n lines.push(\"\", ...formatTierGuidance(inv.byAlgorithm, options.tier));\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Per-family migration targets for a CNSA security tier — surfaces the otherwise\n * library-only {@link remediationForTier} in human reports. Category 5 shows the\n * ML-KEM-1024 / ML-DSA-87 sets CNSA 2.0 mandates for national-security systems and\n * long-lived secrets. Returns plain (un-coloured) lines; the caller styles them.\n */\nexport function formatTierGuidance(\n byAlgorithm: Record<string, number>,\n tier: SecurityTier,\n): string[] {\n const label = tier === \"category-5\" ? \"CNSA 2.0 (Category 5)\" : \"Category 3 (commercial)\";\n const out: string[] = [`${label} migration targets:`];\n const seen = new Set<string>();\n for (const [k, n] of Object.entries(byAlgorithm)) {\n if (n <= 0) continue;\n const fam = k as AlgorithmFamily;\n if (fam === \"unknown\" || !remediationFor(fam)) continue; // skip unmapped families\n const rem = remediationForTier(fam, tier);\n if (seen.has(rem.recommendation)) continue;\n seen.add(rem.recommendation);\n out.push(` ${fam} → ${rem.recommendation}`);\n }\n if (tier === \"category-5\") {\n out.push(\n \" CNSA 2.0 mandates ML-KEM-1024 / ML-DSA-87 for national-security systems and long-lived secrets (2030/2033 milestones).\",\n );\n }\n return out;\n}\n\n/**\n * Per-family migration targets tailored to a selected {@link StandardsProfile}\n * (`--profile`). Unlike {@link formatTierGuidance} (CNSA-tier only), this surfaces the\n * regime's parameter sets AND its hybrid stance — required (ANSSI/BSI) vs recommended\n * (NIST/NCSC) vs optional (CNSA 2.0) — so guidance isn't regime-wrong. Returns plain\n * (un-coloured) lines; the caller styles them.\n */\nexport function formatProfileGuidance(\n byAlgorithm: Record<string, number>,\n profile: StandardsProfile,\n): string[] {\n const out: string[] = [`${profile.name} migration targets:`];\n const seen = new Set<string>();\n for (const [k, n] of Object.entries(byAlgorithm)) {\n if (n <= 0) continue;\n const fam = k as AlgorithmFamily;\n if (fam === \"unknown\" || !remediationFor(fam)) continue; // skip unmapped families\n const rem = remediationForProfile(fam, profile);\n if (seen.has(rem.recommendation)) continue;\n seen.add(rem.recommendation);\n out.push(` ${fam} → ${rem.recommendation}`);\n }\n const stance =\n profile.hybridStance === \"required\"\n ? \"requires classical+PQC hybridization\"\n : profile.hybridStance === \"recommended\"\n ? \"recommends hybridization\"\n : \"does not require hybridization\";\n out.push(\n ` ${profile.authority} ${stance}; classical public-key crypto disallowed after ${profile.disallowAfter} (${profile.citation}).`,\n );\n return out;\n}\n"]}
@@ -13,7 +13,7 @@ import * as path from "node:path";
13
13
  import { parentPort, workerData } from "node:worker_threads";
14
14
  import { defaultRegistry } from "./registry.js";
15
15
  import { detectFile } from "./scan.js";
16
- import { looksMinified } from "./walk.js";
16
+ import { isKeystorePath, looksMinified } from "./walk.js";
17
17
  import { isManifestFile } from "./dependencies.js";
18
18
  if (parentPort) {
19
19
  const data = (workerData ?? {});
@@ -30,15 +30,17 @@ if (parentPort) {
30
30
  const scannedNames = [];
31
31
  for (const rel of req.files) {
32
32
  const abs = path.join(baseDir, ...rel.split("/"));
33
+ // Keystores (.jks/.p12/…) are read byte-preserving (latin1); see scan.ts.
34
+ const keystore = isKeystorePath(rel);
33
35
  let content;
34
36
  try {
35
- content = readFileSync(abs, "utf8");
37
+ content = readFileSync(abs, keystore ? "latin1" : "utf8");
36
38
  }
37
39
  catch {
38
40
  unreadable += 1;
39
41
  continue;
40
42
  }
41
- if (!toggles.scanMinified && !isManifestFile(rel) && looksMinified(content)) {
43
+ if (!toggles.scanMinified && !isManifestFile(rel) && !keystore && looksMinified(content)) {
42
44
  skippedMinified += 1;
43
45
  continue;
44
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scan-worker.js","sourceRoot":"","sources":["../src/scan-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG7D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAenD,IAAI,UAAU,EAAE,CAAC;IACf,MAAM,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,CAAgD,CAAC;IAC/E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,UAAU,CAAC;IAExB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAiB,EAAE,EAAE;QACvC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAc,EAAE,CAAC;YAC/B,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,IAAI,eAAe,GAAG,CAAC,CAAC;YACxB,MAAM,YAAY,GAAa,EAAE,CAAC;YAElC,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;gBAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;gBAClD,IAAI,OAAe,CAAC;gBACpB,IAAI,CAAC;oBACH,OAAO,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACtC,CAAC;gBAAC,MAAM,CAAC;oBACP,UAAU,IAAI,CAAC,CAAC;oBAChB,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC5E,eAAe,IAAI,CAAC,CAAC;oBACrB,SAAS;gBACX,CAAC;gBACD,YAAY,IAAI,CAAC,CAAC;gBAClB,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACvB,QAAQ,CAAC,IAAI,CACX,GAAG,UAAU,CACX,GAAG,EACH,OAAO,EACP,IAAI,EACJ;oBACE,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,IAAI,EAAE,OAAO,CAAC,IAAI;iBACnB,EACD,OAAO,CAAC,aAAa,CACtB,CACF,CAAC;YACJ,CAAC;YAED,IAAI,CAAC,WAAW,CAAC;gBACf,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,KAAK,EAAE,YAAY;gBACnB,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE;aAChE,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,WAAW,CAAC;gBACf,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Worker-thread entry for {@link scanParallel}. Built to `dist/scan-worker.js`\n * and spawned by `parallel.ts`. Each worker reads its assigned files and runs\n * the SAME pure detector pipeline as the serial scan (`detectFile`), returning\n * `{ findings, filesScanned }` per chunk. No shared mutable state.\n *\n * This file performs side effects (wires up message handlers) only when it is\n * actually running inside a worker thread, so importing it from the main thread\n * (e.g. for coverage) is harmless.\n */\nimport { readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { parentPort, workerData } from \"node:worker_threads\";\n\nimport type { Finding } from \"./types.js\";\nimport { defaultRegistry } from \"./registry.js\";\nimport { detectFile } from \"./scan.js\";\nimport { looksMinified } from \"./walk.js\";\nimport { isManifestFile } from \"./dependencies.js\";\n\ninterface WorkerToggles {\n source: boolean;\n config: boolean;\n deps: boolean;\n scanMinified: boolean;\n disabledRules?: string[];\n}\n\ninterface ChunkRequest {\n index: number;\n files: string[];\n}\n\nif (parentPort) {\n const data = (workerData ?? {}) as { baseDir: string; toggles: WorkerToggles };\n const baseDir = data.baseDir;\n const toggles = data.toggles;\n const dets = defaultRegistry.all();\n const port = parentPort;\n\n port.on(\"message\", (req: ChunkRequest) => {\n try {\n const findings: Finding[] = [];\n let filesScanned = 0;\n let unreadable = 0;\n let skippedMinified = 0;\n const scannedNames: string[] = [];\n\n for (const rel of req.files) {\n const abs = path.join(baseDir, ...rel.split(\"/\"));\n let content: string;\n try {\n content = readFileSync(abs, \"utf8\");\n } catch {\n unreadable += 1;\n continue;\n }\n if (!toggles.scanMinified && !isManifestFile(rel) && looksMinified(content)) {\n skippedMinified += 1;\n continue;\n }\n filesScanned += 1;\n scannedNames.push(rel);\n findings.push(\n ...detectFile(\n rel,\n content,\n dets,\n {\n source: toggles.source,\n config: toggles.config,\n deps: toggles.deps,\n },\n toggles.disabledRules,\n ),\n );\n }\n\n port.postMessage({\n index: req.index,\n files: scannedNames,\n result: { findings, filesScanned, unreadable, skippedMinified },\n });\n } catch (err) {\n port.postMessage({\n index: req.index,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n });\n}\n"]}
1
+ {"version":3,"file":"scan-worker.js","sourceRoot":"","sources":["../src/scan-worker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Worker-thread entry for {@link scanParallel}. Built to `dist/scan-worker.js`\n * and spawned by `parallel.ts`. Each worker reads its assigned files and runs\n * the SAME pure detector pipeline as the serial scan (`detectFile`), returning\n * `{ findings, filesScanned }` per chunk. No shared mutable state.\n *\n * This file performs side effects (wires up message handlers) only when it is\n * actually running inside a worker thread, so importing it from the main thread\n * (e.g. for coverage) is harmless.\n */\nimport { readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { parentPort, workerData } from \"node:worker_threads\";\n\nimport type { Finding } from \"./types.js\";\nimport { defaultRegistry } from \"./registry.js\";\nimport { detectFile } from \"./scan.js\";\nimport { isKeystorePath, looksMinified } from \"./walk.js\";\nimport { isManifestFile } from \"./dependencies.js\";\n\ninterface WorkerToggles {\n source: boolean;\n config: boolean;\n deps: boolean;\n scanMinified: boolean;\n disabledRules?: string[];\n}\n\ninterface ChunkRequest {\n index: number;\n files: string[];\n}\n\nif (parentPort) {\n const data = (workerData ?? {}) as { baseDir: string; toggles: WorkerToggles };\n const baseDir = data.baseDir;\n const toggles = data.toggles;\n const dets = defaultRegistry.all();\n const port = parentPort;\n\n port.on(\"message\", (req: ChunkRequest) => {\n try {\n const findings: Finding[] = [];\n let filesScanned = 0;\n let unreadable = 0;\n let skippedMinified = 0;\n const scannedNames: string[] = [];\n\n for (const rel of req.files) {\n const abs = path.join(baseDir, ...rel.split(\"/\"));\n // Keystores (.jks/.p12/…) are read byte-preserving (latin1); see scan.ts.\n const keystore = isKeystorePath(rel);\n let content: string;\n try {\n content = readFileSync(abs, keystore ? \"latin1\" : \"utf8\");\n } catch {\n unreadable += 1;\n continue;\n }\n if (!toggles.scanMinified && !isManifestFile(rel) && !keystore && looksMinified(content)) {\n skippedMinified += 1;\n continue;\n }\n filesScanned += 1;\n scannedNames.push(rel);\n findings.push(\n ...detectFile(\n rel,\n content,\n dets,\n {\n source: toggles.source,\n config: toggles.config,\n deps: toggles.deps,\n },\n toggles.disabledRules,\n ),\n );\n }\n\n port.postMessage({\n index: req.index,\n files: scannedNames,\n result: { findings, filesScanned, unreadable, skippedMinified },\n });\n } catch (err) {\n port.postMessage({\n index: req.index,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAuB7E;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,EAAE,QAAQ,EAAqB,CAAC;AAEtD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,MAAM,CAI9D;AAOD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,EAC5D,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAChC,OAAO,EAAE,CA8BX;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA0IpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC,GAChD,MAAM,EAAE,CAmBV"}
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwC7E;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,EAAE,QAAQ,EAAqB,CAAC;AAEtD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,MAAM,CAI9D;AAOD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,EAC5D,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAChC,OAAO,EAAE,CA8BX;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA4JpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC,GAChD,MAAM,EAAE,CAmBV"}
package/dist/scan.js CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { readFile, stat } from "node:fs/promises";
13
13
  import * as path from "node:path";
14
- import { walkFiles, toPosix, isBinaryPath, looksMinified, matchesAny } from "./walk.js";
14
+ import { walkFiles, toPosix, isBinaryPath, isKeystorePath, looksMinified, matchesAny, DEFAULT_MAX_FILE_SIZE, } from "./walk.js";
15
15
  import { isAnalyzableSource } from "./detect-utils.js";
16
16
  import { stripCommentFindings, stripIgnoredFindings, stripStringLiteralFindings, } from "./comments.js";
17
17
  /**
@@ -19,7 +19,16 @@ import { stripCommentFindings, stripIgnoredFindings, stripStringLiteralFindings,
19
19
  * A match inside a string (e.g. `t.Error("SigningMethodPS256 …")`) is prose and
20
20
  * is dropped. Rules that legitimately match quoted tokens are NOT listed here.
21
21
  */
22
- const CODE_ONLY_RULES = new Set(["go-jwt-signingmethod"]);
22
+ const CODE_ONLY_RULES = new Set([
23
+ "go-jwt-signingmethod",
24
+ // Identifier-form JWT alg constants (jjwt `SignatureAlgorithm.RS256`, auth0
25
+ // `Algorithm.RSA256`, C# `SecurityAlgorithms.*`, Rust `Algorithm::RS256`). Like the
26
+ // Go rule, these are only meaningful as code; the SAME token inside a string literal
27
+ // (an error message that names/forbids the alg) is prose, not a usage.
28
+ "java-jwt-alg",
29
+ "csharp-jwt-alg",
30
+ "rust-jwt-algorithm",
31
+ ]);
23
32
  import { hashContent, loadCache, rulesetFingerprint, saveCache } from "./cache.js";
24
33
  import { builtinDetectors, defaultRegistry, detectorScope } from "./registry.js";
25
34
  import { isManifestFile, scanManifest } from "./dependencies.js";
@@ -139,22 +148,42 @@ export async function scan(options) {
139
148
  const absPath = singleFileName ? options.root : path.join(baseDir, ...rel.split("/"));
140
149
  const reportedPath = singleFileName ? toPosix(rel) : rel;
141
150
  options.onFile?.(reportedPath);
151
+ // Cryptographic keystores (.jks/.p12/…) are binary; read them byte-preserving
152
+ // (latin1) so the keystore detector can inspect magic bytes.
153
+ const keystore = isKeystorePath(reportedPath);
154
+ // Keystores are read whole into memory as latin1. The walker already caps file
155
+ // size, but the explicit-file-list (incremental) path does not — so stat and
156
+ // skip an oversized binary here to avoid reading a huge encrypted archive
157
+ // (`.gpg`) into a string.
158
+ if (keystore) {
159
+ try {
160
+ const { size } = await stat(absPath);
161
+ if (size > (options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE))
162
+ continue;
163
+ }
164
+ catch {
165
+ unreadable += 1;
166
+ continue;
167
+ }
168
+ }
142
169
  let content;
143
170
  try {
144
- content = await readFile(absPath, "utf8");
171
+ content = await readFile(absPath, keystore ? "latin1" : "utf8");
145
172
  }
146
173
  catch {
147
174
  unreadable += 1; // permissions / vanished / decode failure — tracked, not silent.
148
175
  continue;
149
176
  }
150
177
  // Skip machine-minified / generated content (unless explicitly enabled).
151
- // Manifests are always scanned (their findings are dependency findings).
152
- if (!scanMinified && !isManifestFile(reportedPath) && looksMinified(content)) {
178
+ // Manifests and keystores are always scanned.
179
+ if (!scanMinified && !isManifestFile(reportedPath) && !keystore && looksMinified(content)) {
153
180
  skippedMinified += 1;
154
181
  continue;
155
182
  }
156
183
  // Cumulative-byte budget: enforced once this file's bytes are accounted for.
157
- bytesScanned += Buffer.byteLength(content, "utf8");
184
+ // Use the read encoding so a latin1 keystore is counted at its on-disk size
185
+ // (a utf8 byteLength would double-count every byte ≥ 0x80).
186
+ bytesScanned += Buffer.byteLength(content, keystore ? "latin1" : "utf8");
158
187
  if (typeof maxBytes === "number" && bytesScanned > maxBytes) {
159
188
  throw new BudgetExceededError(`maxBytes budget exceeded (limit: ${maxBytes}).`);
160
189
  }
package/dist/scan.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scan orchestrator. Walks the target (or an explicit file list), runs the\n * applicable detectors over each file, parses dependency manifests, aggregates\n * everything into an inventory, and returns a {@link ScanResult} with timing and\n * the tool version.\n *\n * Detector scope (source vs config) is driven by each {@link Detector}'s\n * declared `scope` (defaulting to \"source\"), not by ruleId prefixes. The\n * detector set comes from {@link defaultRegistry} unless overridden via\n * `options.detectors`.\n */\nimport { readFile, stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport type { Detector, Finding, ScanOptions, ScanResult } from \"./types.js\";\nimport { walkFiles, toPosix, isBinaryPath, looksMinified, matchesAny } from \"./walk.js\";\nimport { isAnalyzableSource } from \"./detect-utils.js\";\nimport {\n stripCommentFindings,\n stripIgnoredFindings,\n stripStringLiteralFindings,\n} from \"./comments.js\";\n\n/**\n * Rules whose token is only meaningful as code, never inside a string literal.\n * A match inside a string (e.g. `t.Error(\"SigningMethodPS256 …\")`) is prose and\n * is dropped. Rules that legitimately match quoted tokens are NOT listed here.\n */\nconst CODE_ONLY_RULES: ReadonlySet<string> = new Set([\"go-jwt-signingmethod\"]);\nimport { hashContent, loadCache, rulesetFingerprint, saveCache } from \"./cache.js\";\nimport type { CacheEntry } from \"./cache.js\";\nimport { builtinDetectors, defaultRegistry, detectorScope } from \"./registry.js\";\nimport { isManifestFile, scanManifest } from \"./dependencies.js\";\nimport { buildInventory } from \"./inventory.js\";\nimport { AbortError, BudgetExceededError } from \"./errors.js\";\nimport { VERSION } from \"./version.js\";\n\n/**\n * The full set of built-in detectors exposed on the public API. Re-exported\n * from {@link builtinDetectors} in `registry.ts` — the single source of truth\n * the {@link defaultRegistry} is also built from — so the public array and the\n * registry can never drift apart. The manifest scanner is handled separately\n * (it parses JSON rather than running a Detector).\n */\nexport const detectors: Detector[] = builtinDetectors;\n\n/** Stable comparator: by file, then line, then ruleId. Exported for reuse. */\nexport function compareFindings(a: Finding, b: Finding): number {\n if (a.location.file !== b.location.file) return a.location.file < b.location.file ? -1 : 1;\n if (a.location.line !== b.location.line) return a.location.line - b.location.line;\n return a.ruleId < b.ruleId ? -1 : a.ruleId > b.ruleId ? 1 : 0;\n}\n\n/** Resolve the active detector list for a scan (override or the default registry). */\nfunction resolveDetectors(options: ScanOptions): Detector[] {\n return options.detectors ?? defaultRegistry.all();\n}\n\n/**\n * Run all applicable detectors + the manifest scanner over a single file's\n * contents, honouring the source / config / dependencies toggles. Pure: no I/O.\n * Exported so the parallel worker and unit tests can drive it directly.\n */\nexport function detectFile(\n file: string,\n content: string,\n dets: readonly Detector[],\n toggles: { source: boolean; config: boolean; deps: boolean },\n disabledRules?: readonly string[],\n): Finding[] {\n let out: Finding[] = [];\n\n for (const det of dets) {\n if (!det.appliesTo(file)) continue;\n const isConfig = detectorScope(det) === \"config\";\n if (isConfig ? !toggles.config : !toggles.source) continue;\n out.push(...det.detect({ file, content }));\n }\n\n // Drop lexical false positives that land inside comments (`// migrated off\n // createECDH()`), then honour inline `// qscan-ignore-line` /\n // `qscan-ignore-next-line` directives. Both run before the dependency scan\n // appends its findings (manifests carry neither).\n out = stripCommentFindings(out, content, file);\n out = stripStringLiteralFindings(out, content, file, CODE_ONLY_RULES);\n out = stripIgnoredFindings(out, content);\n\n if (toggles.deps && isManifestFile(file)) {\n out.push(...scanManifest(file, content));\n }\n\n // Per-rule suppression: drop findings whose ruleId was disabled. Applied here\n // so both the serial scan and the worker path honour it identically.\n if (disabledRules && disabledRules.length > 0) {\n const disabled = new Set(disabledRules);\n return out.filter((f) => !disabled.has(f.ruleId));\n }\n\n return out;\n}\n\n/**\n * Recursively scan a directory (or single file, or explicit file list) for\n * classical asymmetric crypto. Honours the source / dependencies / config\n * toggles (all default true) and reports progress through `options.onFile`.\n */\nexport async function scan(options: ScanOptions): Promise<ScanResult> {\n const startedAt = new Date();\n\n const doSource = options.source !== false;\n const doDeps = options.dependencies !== false;\n const doConfig = options.config !== false;\n const scanMinified = options.scanMinified === true;\n const dets = resolveDetectors(options);\n\n // Resolve whether the root is a file so we can rebuild absolute paths to read.\n const rootStat = await stat(options.root);\n const rootIsFile = rootStat.isFile();\n const baseDir = rootIsFile ? path.dirname(options.root) : options.root;\n const singleFileName = rootIsFile ? path.basename(options.root) : null;\n\n const findings: Finding[] = [];\n let filesScanned = 0;\n let analyzedFiles = 0;\n let bytesScanned = 0;\n let unreadable = 0;\n let skippedMinified = 0;\n\n // Optional content-hash scan cache: unchanged files reuse prior findings.\n const cacheFile = options.cacheFile;\n const ruleset = cacheFile ? rulesetFingerprint(dets, options.disabledRules) : \"\";\n const cache = cacheFile ? await loadCache(cacheFile, ruleset) : null;\n // A full scan starts with an empty next-cache so files that vanished are\n // evicted. An INCREMENTAL scan (explicit `files` list) only visits a subset,\n // so seed the next-cache with the existing entries — otherwise saving would\n // drop every file we didn't happen to scan this run (audit: arch #4).\n const incremental = Array.isArray(options.files);\n const nextEntries: Map<string, CacheEntry> | null = cacheFile\n ? new Map(incremental && cache ? cache : [])\n : null;\n\n // Work-budget / cancellation controls (all optional, unlimited when omitted).\n const signal = options.signal;\n const maxFiles = options.maxFiles;\n const maxBytes = options.maxBytes;\n\n // Source of relative paths: an explicit file list (incremental) or the walker.\n const relPaths: AsyncIterable<string> = options.files\n ? filterExplicitFiles(options.files, options)\n : walkFiles(options.root, {\n include: options.include,\n exclude: options.exclude,\n noDefaultIgnores: options.noDefaultIgnores,\n maxFileSize: options.maxFileSize,\n });\n\n for await (const rel of relPaths) {\n // Cooperative cancellation: check the signal at the top of every iteration.\n if (signal?.aborted) throw new AbortError();\n\n // File-count budget: enforced before reading the next file.\n if (typeof maxFiles === \"number\" && filesScanned >= maxFiles) {\n throw new BudgetExceededError(`maxFiles budget exceeded (limit: ${maxFiles}).`);\n }\n\n // In single-file mode, walkFiles yields the basename; map back to the file.\n const absPath = singleFileName ? options.root : path.join(baseDir, ...rel.split(\"/\"));\n const reportedPath = singleFileName ? toPosix(rel) : rel;\n\n options.onFile?.(reportedPath);\n\n let content: string;\n try {\n content = await readFile(absPath, \"utf8\");\n } catch {\n unreadable += 1; // permissions / vanished / decode failure — tracked, not silent.\n continue;\n }\n\n // Skip machine-minified / generated content (unless explicitly enabled).\n // Manifests are always scanned (their findings are dependency findings).\n if (!scanMinified && !isManifestFile(reportedPath) && looksMinified(content)) {\n skippedMinified += 1;\n continue;\n }\n\n // Cumulative-byte budget: enforced once this file's bytes are accounted for.\n bytesScanned += Buffer.byteLength(content, \"utf8\");\n if (typeof maxBytes === \"number\" && bytesScanned > maxBytes) {\n throw new BudgetExceededError(`maxBytes budget exceeded (limit: ${maxBytes}).`);\n }\n\n filesScanned += 1;\n if (isAnalyzableSource(reportedPath)) analyzedFiles += 1;\n\n // Cache: reuse prior findings when the content hash matches; otherwise scan\n // and record. `nextEntries` becomes the new cache (dropping vanished files).\n let fileFindings: Finding[];\n if (cache && nextEntries) {\n const hash = hashContent(content);\n const hit = cache.get(reportedPath);\n fileFindings =\n hit && hit.hash === hash\n ? hit.findings\n : detectFile(\n reportedPath,\n content,\n dets,\n { source: doSource, config: doConfig, deps: doDeps },\n options.disabledRules,\n );\n nextEntries.set(reportedPath, { hash, findings: fileFindings });\n } else {\n fileFindings = detectFile(\n reportedPath,\n content,\n dets,\n { source: doSource, config: doConfig, deps: doDeps },\n options.disabledRules,\n );\n }\n findings.push(...fileFindings);\n }\n\n // Persist the cache (best effort) before ordering/returning.\n if (cacheFile && nextEntries) await saveCache(cacheFile, ruleset, nextEntries);\n\n // Stable ordering: by file, then line, then ruleId.\n findings.sort(compareFindings);\n\n const inventory = buildInventory(findings);\n const finishedAt = new Date();\n\n return {\n root: options.root,\n findings,\n filesScanned,\n analyzedFiles,\n diagnostics: { unreadable, skippedMinified },\n inventory,\n startedAt: startedAt.toISOString(),\n finishedAt: finishedAt.toISOString(),\n toolVersion: VERSION,\n };\n}\n\n/**\n * Filter an explicit relative file list down to the paths that pass the binary\n * filter and the include/exclude patterns, returning them deduped and sorted for\n * deterministic output. Size limits are enforced later (we still read manifests\n * over the cap). Non-existent files are simply skipped at read time.\n *\n * Pure + synchronous, and exported so the parallel enumerator applies the EXACT\n * same filtering for explicit file lists as the serial path (byte-for-byte\n * identical results under `--parallel`).\n */\nexport function filterExplicitFileList(\n files: readonly string[],\n options: Pick<ScanOptions, \"include\" | \"exclude\">,\n): string[] {\n const include = options.include ?? [];\n const exclude = options.exclude ?? [];\n const seen = new Set<string>();\n const list = [...files]\n .map((f) => toPosix(f))\n .filter((f) => {\n if (seen.has(f)) return false;\n seen.add(f);\n return true;\n })\n .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n\n return list.filter((rel) => {\n if (isBinaryPath(rel)) return false;\n if (include.length > 0 && !matchesAny(rel, include)) return false;\n if (matchesAny(rel, exclude)) return false;\n return true;\n });\n}\n\n/** Async-generator wrapper around {@link filterExplicitFileList} for `scan()`. */\nasync function* filterExplicitFiles(files: string[], options: ScanOptions): AsyncGenerator<string> {\n for (const rel of filterExplicitFileList(files, options)) yield rel;\n}\n"]}
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scan orchestrator. Walks the target (or an explicit file list), runs the\n * applicable detectors over each file, parses dependency manifests, aggregates\n * everything into an inventory, and returns a {@link ScanResult} with timing and\n * the tool version.\n *\n * Detector scope (source vs config) is driven by each {@link Detector}'s\n * declared `scope` (defaulting to \"source\"), not by ruleId prefixes. The\n * detector set comes from {@link defaultRegistry} unless overridden via\n * `options.detectors`.\n */\nimport { readFile, stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport type { Detector, Finding, ScanOptions, ScanResult } from \"./types.js\";\nimport {\n walkFiles,\n toPosix,\n isBinaryPath,\n isKeystorePath,\n looksMinified,\n matchesAny,\n DEFAULT_MAX_FILE_SIZE,\n} from \"./walk.js\";\nimport { isAnalyzableSource } from \"./detect-utils.js\";\nimport {\n stripCommentFindings,\n stripIgnoredFindings,\n stripStringLiteralFindings,\n} from \"./comments.js\";\n\n/**\n * Rules whose token is only meaningful as code, never inside a string literal.\n * A match inside a string (e.g. `t.Error(\"SigningMethodPS256 …\")`) is prose and\n * is dropped. Rules that legitimately match quoted tokens are NOT listed here.\n */\nconst CODE_ONLY_RULES: ReadonlySet<string> = new Set([\n \"go-jwt-signingmethod\",\n // Identifier-form JWT alg constants (jjwt `SignatureAlgorithm.RS256`, auth0\n // `Algorithm.RSA256`, C# `SecurityAlgorithms.*`, Rust `Algorithm::RS256`). Like the\n // Go rule, these are only meaningful as code; the SAME token inside a string literal\n // (an error message that names/forbids the alg) is prose, not a usage.\n \"java-jwt-alg\",\n \"csharp-jwt-alg\",\n \"rust-jwt-algorithm\",\n]);\nimport { hashContent, loadCache, rulesetFingerprint, saveCache } from \"./cache.js\";\nimport type { CacheEntry } from \"./cache.js\";\nimport { builtinDetectors, defaultRegistry, detectorScope } from \"./registry.js\";\nimport { isManifestFile, scanManifest } from \"./dependencies.js\";\nimport { buildInventory } from \"./inventory.js\";\nimport { AbortError, BudgetExceededError } from \"./errors.js\";\nimport { VERSION } from \"./version.js\";\n\n/**\n * The full set of built-in detectors exposed on the public API. Re-exported\n * from {@link builtinDetectors} in `registry.ts` — the single source of truth\n * the {@link defaultRegistry} is also built from — so the public array and the\n * registry can never drift apart. The manifest scanner is handled separately\n * (it parses JSON rather than running a Detector).\n */\nexport const detectors: Detector[] = builtinDetectors;\n\n/** Stable comparator: by file, then line, then ruleId. Exported for reuse. */\nexport function compareFindings(a: Finding, b: Finding): number {\n if (a.location.file !== b.location.file) return a.location.file < b.location.file ? -1 : 1;\n if (a.location.line !== b.location.line) return a.location.line - b.location.line;\n return a.ruleId < b.ruleId ? -1 : a.ruleId > b.ruleId ? 1 : 0;\n}\n\n/** Resolve the active detector list for a scan (override or the default registry). */\nfunction resolveDetectors(options: ScanOptions): Detector[] {\n return options.detectors ?? defaultRegistry.all();\n}\n\n/**\n * Run all applicable detectors + the manifest scanner over a single file's\n * contents, honouring the source / config / dependencies toggles. Pure: no I/O.\n * Exported so the parallel worker and unit tests can drive it directly.\n */\nexport function detectFile(\n file: string,\n content: string,\n dets: readonly Detector[],\n toggles: { source: boolean; config: boolean; deps: boolean },\n disabledRules?: readonly string[],\n): Finding[] {\n let out: Finding[] = [];\n\n for (const det of dets) {\n if (!det.appliesTo(file)) continue;\n const isConfig = detectorScope(det) === \"config\";\n if (isConfig ? !toggles.config : !toggles.source) continue;\n out.push(...det.detect({ file, content }));\n }\n\n // Drop lexical false positives that land inside comments (`// migrated off\n // createECDH()`), then honour inline `// qscan-ignore-line` /\n // `qscan-ignore-next-line` directives. Both run before the dependency scan\n // appends its findings (manifests carry neither).\n out = stripCommentFindings(out, content, file);\n out = stripStringLiteralFindings(out, content, file, CODE_ONLY_RULES);\n out = stripIgnoredFindings(out, content);\n\n if (toggles.deps && isManifestFile(file)) {\n out.push(...scanManifest(file, content));\n }\n\n // Per-rule suppression: drop findings whose ruleId was disabled. Applied here\n // so both the serial scan and the worker path honour it identically.\n if (disabledRules && disabledRules.length > 0) {\n const disabled = new Set(disabledRules);\n return out.filter((f) => !disabled.has(f.ruleId));\n }\n\n return out;\n}\n\n/**\n * Recursively scan a directory (or single file, or explicit file list) for\n * classical asymmetric crypto. Honours the source / dependencies / config\n * toggles (all default true) and reports progress through `options.onFile`.\n */\nexport async function scan(options: ScanOptions): Promise<ScanResult> {\n const startedAt = new Date();\n\n const doSource = options.source !== false;\n const doDeps = options.dependencies !== false;\n const doConfig = options.config !== false;\n const scanMinified = options.scanMinified === true;\n const dets = resolveDetectors(options);\n\n // Resolve whether the root is a file so we can rebuild absolute paths to read.\n const rootStat = await stat(options.root);\n const rootIsFile = rootStat.isFile();\n const baseDir = rootIsFile ? path.dirname(options.root) : options.root;\n const singleFileName = rootIsFile ? path.basename(options.root) : null;\n\n const findings: Finding[] = [];\n let filesScanned = 0;\n let analyzedFiles = 0;\n let bytesScanned = 0;\n let unreadable = 0;\n let skippedMinified = 0;\n\n // Optional content-hash scan cache: unchanged files reuse prior findings.\n const cacheFile = options.cacheFile;\n const ruleset = cacheFile ? rulesetFingerprint(dets, options.disabledRules) : \"\";\n const cache = cacheFile ? await loadCache(cacheFile, ruleset) : null;\n // A full scan starts with an empty next-cache so files that vanished are\n // evicted. An INCREMENTAL scan (explicit `files` list) only visits a subset,\n // so seed the next-cache with the existing entries — otherwise saving would\n // drop every file we didn't happen to scan this run (audit: arch #4).\n const incremental = Array.isArray(options.files);\n const nextEntries: Map<string, CacheEntry> | null = cacheFile\n ? new Map(incremental && cache ? cache : [])\n : null;\n\n // Work-budget / cancellation controls (all optional, unlimited when omitted).\n const signal = options.signal;\n const maxFiles = options.maxFiles;\n const maxBytes = options.maxBytes;\n\n // Source of relative paths: an explicit file list (incremental) or the walker.\n const relPaths: AsyncIterable<string> = options.files\n ? filterExplicitFiles(options.files, options)\n : walkFiles(options.root, {\n include: options.include,\n exclude: options.exclude,\n noDefaultIgnores: options.noDefaultIgnores,\n maxFileSize: options.maxFileSize,\n });\n\n for await (const rel of relPaths) {\n // Cooperative cancellation: check the signal at the top of every iteration.\n if (signal?.aborted) throw new AbortError();\n\n // File-count budget: enforced before reading the next file.\n if (typeof maxFiles === \"number\" && filesScanned >= maxFiles) {\n throw new BudgetExceededError(`maxFiles budget exceeded (limit: ${maxFiles}).`);\n }\n\n // In single-file mode, walkFiles yields the basename; map back to the file.\n const absPath = singleFileName ? options.root : path.join(baseDir, ...rel.split(\"/\"));\n const reportedPath = singleFileName ? toPosix(rel) : rel;\n\n options.onFile?.(reportedPath);\n\n // Cryptographic keystores (.jks/.p12/…) are binary; read them byte-preserving\n // (latin1) so the keystore detector can inspect magic bytes.\n const keystore = isKeystorePath(reportedPath);\n // Keystores are read whole into memory as latin1. The walker already caps file\n // size, but the explicit-file-list (incremental) path does not — so stat and\n // skip an oversized binary here to avoid reading a huge encrypted archive\n // (`.gpg`) into a string.\n if (keystore) {\n try {\n const { size } = await stat(absPath);\n if (size > (options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE)) continue;\n } catch {\n unreadable += 1;\n continue;\n }\n }\n let content: string;\n try {\n content = await readFile(absPath, keystore ? \"latin1\" : \"utf8\");\n } catch {\n unreadable += 1; // permissions / vanished / decode failure — tracked, not silent.\n continue;\n }\n\n // Skip machine-minified / generated content (unless explicitly enabled).\n // Manifests and keystores are always scanned.\n if (!scanMinified && !isManifestFile(reportedPath) && !keystore && looksMinified(content)) {\n skippedMinified += 1;\n continue;\n }\n\n // Cumulative-byte budget: enforced once this file's bytes are accounted for.\n // Use the read encoding so a latin1 keystore is counted at its on-disk size\n // (a utf8 byteLength would double-count every byte ≥ 0x80).\n bytesScanned += Buffer.byteLength(content, keystore ? \"latin1\" : \"utf8\");\n if (typeof maxBytes === \"number\" && bytesScanned > maxBytes) {\n throw new BudgetExceededError(`maxBytes budget exceeded (limit: ${maxBytes}).`);\n }\n\n filesScanned += 1;\n if (isAnalyzableSource(reportedPath)) analyzedFiles += 1;\n\n // Cache: reuse prior findings when the content hash matches; otherwise scan\n // and record. `nextEntries` becomes the new cache (dropping vanished files).\n let fileFindings: Finding[];\n if (cache && nextEntries) {\n const hash = hashContent(content);\n const hit = cache.get(reportedPath);\n fileFindings =\n hit && hit.hash === hash\n ? hit.findings\n : detectFile(\n reportedPath,\n content,\n dets,\n { source: doSource, config: doConfig, deps: doDeps },\n options.disabledRules,\n );\n nextEntries.set(reportedPath, { hash, findings: fileFindings });\n } else {\n fileFindings = detectFile(\n reportedPath,\n content,\n dets,\n { source: doSource, config: doConfig, deps: doDeps },\n options.disabledRules,\n );\n }\n findings.push(...fileFindings);\n }\n\n // Persist the cache (best effort) before ordering/returning.\n if (cacheFile && nextEntries) await saveCache(cacheFile, ruleset, nextEntries);\n\n // Stable ordering: by file, then line, then ruleId.\n findings.sort(compareFindings);\n\n const inventory = buildInventory(findings);\n const finishedAt = new Date();\n\n return {\n root: options.root,\n findings,\n filesScanned,\n analyzedFiles,\n diagnostics: { unreadable, skippedMinified },\n inventory,\n startedAt: startedAt.toISOString(),\n finishedAt: finishedAt.toISOString(),\n toolVersion: VERSION,\n };\n}\n\n/**\n * Filter an explicit relative file list down to the paths that pass the binary\n * filter and the include/exclude patterns, returning them deduped and sorted for\n * deterministic output. Size limits are enforced later (we still read manifests\n * over the cap). Non-existent files are simply skipped at read time.\n *\n * Pure + synchronous, and exported so the parallel enumerator applies the EXACT\n * same filtering for explicit file lists as the serial path (byte-for-byte\n * identical results under `--parallel`).\n */\nexport function filterExplicitFileList(\n files: readonly string[],\n options: Pick<ScanOptions, \"include\" | \"exclude\">,\n): string[] {\n const include = options.include ?? [];\n const exclude = options.exclude ?? [];\n const seen = new Set<string>();\n const list = [...files]\n .map((f) => toPosix(f))\n .filter((f) => {\n if (seen.has(f)) return false;\n seen.add(f);\n return true;\n })\n .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n\n return list.filter((rel) => {\n if (isBinaryPath(rel)) return false;\n if (include.length > 0 && !matchesAny(rel, include)) return false;\n if (matchesAny(rel, exclude)) return false;\n return true;\n });\n}\n\n/** Async-generator wrapper around {@link filterExplicitFileList} for `scan()`. */\nasync function* filterExplicitFiles(files: string[], options: ScanOptions): AsyncGenerator<string> {\n for (const rel of filterExplicitFileList(files, options)) yield rel;\n}\n"]}