@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/src/index.ts CHANGED
@@ -56,13 +56,14 @@ export type {
56
56
  // Scan cancellation / work-budget errors.
57
57
  export { AbortError, BudgetExceededError } from "./errors.js";
58
58
 
59
- // Parallel scanning (worker_threads pool) + pure merge/chunk helpers.
60
- export { scanParallel, mergeChunkResults, chunkByBytes } from "./parallel.js";
61
- export type { ScanChunk, ChunkResult, SizedFile } from "./parallel.js";
59
+ // Parallel scanning (worker_threads pool). The chunk/merge helpers and their
60
+ // SizedFile/ChunkResult types are internal plumbing (not part of the public API).
61
+ export { scanParallel } from "./parallel.js";
62
+ export type { ScanChunk } from "./parallel.js";
62
63
 
63
- // Detector registry (plugin point) + helpers + the rule catalog.
64
- export { DetectorRegistry, defaultRegistry, detectorScope } from "./registry.js";
65
- export type { RuleCatalogEntry } from "./registry.js";
64
+ // Detector registry (plugin point). `detectorScope` and the rule-catalog type are
65
+ // internal.
66
+ export { DetectorRegistry, defaultRegistry } from "./registry.js";
66
67
 
67
68
  // Canonical baseline (shared by qScan + the Action).
68
69
  export {
@@ -82,8 +83,44 @@ export { changedFiles } from "./changed.js";
82
83
  export { loadConfig, ConfigError, CONFIG_FILENAME } from "./config.js";
83
84
  export type { QuantakryptoFileConfig, LoadConfigResult } from "./config.js";
84
85
 
86
+ // HNDL (harvest-now-decrypt-later) data-risk quantifier (see docs/HNDL.md).
87
+ export {
88
+ HNDL_MODEL_VERSION,
89
+ HNDL_FILENAME,
90
+ SEVERITY_VULNERABILITY,
91
+ CONFIDENCE_WEIGHT,
92
+ CLASSIFICATION_SENSITIVITY,
93
+ NON_HNDL_DISCOUNT,
94
+ DEFAULT_QUANTUM_THREAT_YEARS,
95
+ DEFAULT_MIGRATION_HORIZON_YEARS,
96
+ DEFAULT_UNBOUND_CLASSIFICATION,
97
+ HndlError,
98
+ computeHndl,
99
+ vulnerabilityFactor,
100
+ moscaFactor,
101
+ globMatch,
102
+ findingFingerprint,
103
+ findingScope,
104
+ parseHndlMap,
105
+ loadHndlMap,
106
+ scaffoldHndlYaml,
107
+ } from "./hndl.js";
108
+ export type {
109
+ DataClassification,
110
+ HndlScope,
111
+ HndlDataAsset,
112
+ HndlHorizon,
113
+ HndlDefaults,
114
+ HndlMap,
115
+ ExposureRationale,
116
+ FindingExposure,
117
+ AssetExposure,
118
+ HndlSummary,
119
+ HndlReport,
120
+ } from "./hndl.js";
121
+
85
122
  // Filesystem walker (relative POSIX paths, default ignores, size/binary filters).
86
- export { walkFiles, isBinaryPath, isGeneratedPath, looksMinified } from "./walk.js";
123
+ export { walkFiles, isBinaryPath, looksMinified } from "./walk.js";
87
124
 
88
125
  // Analyzable-language coverage (which source languages the scanner inspects).
89
126
  export {
@@ -104,7 +141,13 @@ export { vulnerableDependencies, DEP_VULNERABLE_RULE, isManifestFile } from "./d
104
141
  export { SEVERITY_ORDER, severityRank, meetsThreshold, sarifLevel } from "./severity.js";
105
142
 
106
143
  // Reporters.
107
- export { toSarif, toJson, formatSummary, formatTierGuidance } from "./report.js";
144
+ export {
145
+ toSarif,
146
+ toJson,
147
+ formatSummary,
148
+ formatTierGuidance,
149
+ formatProfileGuidance,
150
+ } from "./report.js";
108
151
  export type { ReportOptions } from "./report.js";
109
152
 
110
153
  // CycloneDX 1.6 cryptographic bill of materials (CBOM) export.
@@ -112,18 +155,45 @@ export { toCbom } from "./cbom.js";
112
155
  export type { CycloneDxBom, CbomComponent } from "./cbom.js";
113
156
  // Merge multiple CBOMs (code + infra + live endpoints) into one combined BOM.
114
157
  export { mergeCboms } from "./cbom-merge.js";
158
+ // OpenVEX 0.2.0 export — quantum-readiness posture as VEX statements.
159
+ export { toOpenVex } from "./vex.js";
160
+ export type { OpenVexDocument, OpenVexStatement, OpenVexOptions } from "./vex.js";
161
+ // Crypto-agility manifest: agent-consumable crypto-posture document.
162
+ export {
163
+ buildCryptoAgilityManifest,
164
+ validateCryptoAgilityManifest,
165
+ CRYPTO_AGILITY_MANIFEST_VERSION,
166
+ CRYPTO_AGILITY_WELL_KNOWN_PATH,
167
+ } from "./crypto-agility.js";
168
+ export type {
169
+ CryptoAgilityManifest,
170
+ CryptoAgilityManifestOptions,
171
+ CryptoAgilityPosture,
172
+ CryptoAgilityCbomSummary,
173
+ CryptoAgilityFamily,
174
+ CryptoAgilityPolicy,
175
+ ManifestValidation,
176
+ } from "./crypto-agility.js";
115
177
  // ISO/IEC 27001 A.8.24 evidence-chain readiness report.
116
- export { buildReadinessReport } from "./evidence.js";
117
- export type { ReadinessReport, EvidenceFinding, ReadinessReportOptions } from "./evidence.js";
178
+ export { buildReadinessReport, signReadinessReport, verifyReadinessReport } from "./evidence.js";
179
+ export type {
180
+ ReadinessReport,
181
+ EvidenceFinding,
182
+ ReadinessReportOptions,
183
+ EvidenceSigner,
184
+ SignEvidenceOptions,
185
+ VerifyReadinessResult,
186
+ } from "./evidence.js";
118
187
 
119
188
  // Cryptography policy → per-finding verdicts (A.8.24 evidence §4).
120
- export { buildPolicyMapping, parseCryptoPolicy, verdictForAlgorithm } from "./policy.js";
189
+ export { buildPolicyMapping, parseCryptoPolicy } from "./policy.js";
121
190
  export type { CryptoPolicy, PolicyVerdict, PolicyMapping, PolicyFindingVerdict } from "./policy.js";
122
191
 
123
- // Remediation lookup (family + tier-aware) and stateful-HBS guidance.
192
+ // Remediation lookup (family + tier-aware + profile-aware) and stateful-HBS guidance.
124
193
  export {
125
194
  remediationFor,
126
195
  remediationForTier,
196
+ remediationForProfile,
127
197
  TIER_PARAMS,
128
198
  STATEFUL_HBS_NOTE,
129
199
  PQC_TRANSITION_NOTE,
@@ -132,9 +202,19 @@ export {
132
202
  export type { SecurityTier } from "./remediation.js";
133
203
 
134
204
  // Post-quantum standards source of truth + review cadence.
135
- export { PQC_STANDARDS, standardsReviewStatus } from "./standards.js";
205
+ export { PQC_STANDARDS } from "./standards.js";
136
206
  export type { PqcStandards, StandardsCitation, StandardsReviewStatus } from "./standards.js";
137
207
 
208
+ // Selectable standards regime profiles (NIST / CNSA / BSI / ANSSI / NCSC).
209
+ export {
210
+ STANDARDS_PROFILES,
211
+ DEFAULT_PROFILE_ID,
212
+ standardsProfileIds,
213
+ getStandardsProfile,
214
+ defaultStandardsProfile,
215
+ } from "./standards-profiles.js";
216
+ export type { StandardsProfile, HybridStance } from "./standards-profiles.js";
217
+
138
218
  // CWE identifier constants.
139
219
  export {
140
220
  CWE_BROKEN_CRYPTO,
package/src/inventory.ts CHANGED
@@ -48,7 +48,7 @@ const SCORE_SCALE = 100;
48
48
  const TEST_PATH_WEIGHT = 0.15;
49
49
 
50
50
  /** True when a finding's file path is test / fixture / example / documentation. */
51
- export function isTestOrFixturePath(file: string): boolean {
51
+ function isTestOrFixturePath(file: string): boolean {
52
52
  const f = file.toLowerCase().replace(/\\/g, "/");
53
53
  if (
54
54
  /(?:^|\/)(?:tests?|__tests__|testdata|test-data|fixtures?|examples?|demos?|samples?|specs?|mocks?|docs?|benchmarks?|e2e)\//.test(
@@ -75,20 +75,28 @@ export function isTestOrFixturePath(file: string): boolean {
75
75
  export function readinessScore(findings: Finding[]): number {
76
76
  if (findings.length === 0) return 100;
77
77
 
78
- const seen: Record<Severity, number> = {
78
+ // Diminishing-returns occurrence counters, kept SEPARATE for full-weight vs
79
+ // test-path findings. Sharing one counter per severity let whichever finding came
80
+ // first in the (path-sorted) list claim the `1/√1` slot, so a directory rename — or
81
+ // a test finding sorting ahead of a real one — changed the score. Bucketing makes
82
+ // the penalty independent of file order and of test/real interleaving.
83
+ const zero = (): Record<Severity, number> => ({
79
84
  critical: 0,
80
85
  high: 0,
81
86
  medium: 0,
82
87
  low: 0,
83
88
  info: 0,
84
- };
89
+ });
90
+ const seenReal = zero();
91
+ const seenTest = zero();
85
92
 
86
93
  let penalty = 0;
87
94
  for (const f of findings) {
88
- seen[f.severity] += 1;
89
- const weight =
90
- SEVERITY_WEIGHT[f.severity] * (isTestOrFixturePath(f.location.file) ? TEST_PATH_WEIGHT : 1);
91
- penalty += penaltyFor(weight, seen[f.severity]);
95
+ const isTest = isTestOrFixturePath(f.location.file);
96
+ const bucket = isTest ? seenTest : seenReal;
97
+ bucket[f.severity] += 1;
98
+ const weight = SEVERITY_WEIGHT[f.severity] * (isTest ? TEST_PATH_WEIGHT : 1);
99
+ penalty += penaltyFor(weight, bucket[f.severity]);
92
100
  }
93
101
 
94
102
  return Math.max(0, Math.min(100, Math.round(100 * Math.exp(-penalty / SCORE_SCALE))));
package/src/parallel.ts CHANGED
@@ -17,7 +17,7 @@ import { fileURLToPath } from "node:url";
17
17
  import type { Worker as NodeWorker } from "node:worker_threads";
18
18
 
19
19
  import type { Finding, ParallelScanOptions, ScanResult } from "./types.js";
20
- import { walkFiles } from "./walk.js";
20
+ import { walkFilesSized } from "./walk.js";
21
21
  import { isAnalyzableSource } from "./detect-utils.js";
22
22
  import { buildInventory } from "./inventory.js";
23
23
  import { compareFindings, filterExplicitFileList, scan } from "./scan.js";
@@ -115,30 +115,31 @@ function shouldParallelize(options: ParallelScanOptions, files: SizedFile[]): bo
115
115
  * explicit `files` list or the walker. Sizes power byte-balanced chunking.
116
116
  */
117
117
  async function enumerateFiles(options: ParallelScanOptions, baseDir: string): Promise<SizedFile[]> {
118
- const rels: string[] = [];
118
+ const sized: SizedFile[] = [];
119
119
  if (options.files) {
120
+ // Explicit file list: the walker isn't involved, so stat each once here.
120
121
  // Apply the SAME include/exclude/binary filtering the serial path uses via
121
- // `filterExplicitFiles`, so `--parallel` is byte-for-byte identical to serial.
122
- for (const rel of filterExplicitFileList(options.files, options)) rels.push(rel);
123
- } else {
124
- for await (const rel of walkFiles(options.root, {
125
- include: options.include,
126
- exclude: options.exclude,
127
- noDefaultIgnores: options.noDefaultIgnores,
128
- maxFileSize: options.maxFileSize,
129
- })) {
130
- rels.push(rel);
122
+ // `filterExplicitFileList`, so `--parallel` is byte-for-byte identical to serial.
123
+ for (const rel of filterExplicitFileList(options.files, options)) {
124
+ let size = 0;
125
+ try {
126
+ size = (await stat(path.join(baseDir, ...rel.split("/")))).size;
127
+ } catch {
128
+ // Unreadable now; keep with size 0 (the worker read will skip if it's gone).
129
+ }
130
+ sized.push({ rel, size });
131
131
  }
132
+ return sized;
132
133
  }
133
134
 
134
- const sized: SizedFile[] = [];
135
- for (const rel of rels) {
136
- let size = 0;
137
- try {
138
- size = (await stat(path.join(baseDir, ...rel.split("/")))).size;
139
- } catch {
140
- // Unreadable now; keep with size 0 — worker read will skip if it's gone.
141
- }
135
+ // Directory walk: reuse the size the walker already stat'd while enforcing the
136
+ // size limit, so files are not stat'd a second time here (double-stat fix).
137
+ for await (const { rel, size } of walkFilesSized(options.root, {
138
+ include: options.include,
139
+ exclude: options.exclude,
140
+ noDefaultIgnores: options.noDefaultIgnores,
141
+ maxFileSize: options.maxFileSize,
142
+ })) {
142
143
  sized.push({ rel, size });
143
144
  }
144
145
  return sized;
package/src/registry.ts CHANGED
@@ -21,6 +21,10 @@ import { rubyDetector } from "./detectors/ruby.js";
21
21
  import { phpDetector } from "./detectors/php.js";
22
22
  import { elixirDetector } from "./detectors/elixir.js";
23
23
  import { cDetector } from "./detectors/c.js";
24
+ import { swiftDetector } from "./detectors/swift.js";
25
+ import { objcDetector } from "./detectors/objc.js";
26
+ import { dartDetector } from "./detectors/dart.js";
27
+ import { solidityDetector } from "./detectors/solidity.js";
24
28
  import { pemDetector } from "./detectors/pem.js";
25
29
  import { jwkDetector } from "./detectors/jwk.js";
26
30
  import { terraformDetector } from "./detectors/terraform.js";
@@ -31,10 +35,27 @@ import { joseDetector } from "./detectors/jose.js";
31
35
  import { k8sDetector } from "./detectors/k8s.js";
32
36
  import { messagingDetector } from "./detectors/messaging.js";
33
37
  import { databaseDetector } from "./detectors/database.js";
38
+ import { xmldsigDetector } from "./detectors/xmldsig.js";
39
+ import { pkcs11Detector } from "./detectors/pkcs11.js";
40
+ import { dkimDetector } from "./detectors/dkim.js";
41
+ import { sshCaDetector } from "./detectors/ssh-ca.js";
42
+ import { spireDetector } from "./detectors/spire.js";
43
+ import { proxyDetector } from "./detectors/proxy.js";
44
+ import { webauthnDetector } from "./detectors/webauthn.js";
45
+ import { codesignDetector } from "./detectors/codesign.js";
46
+ import { weakHashDetector } from "./detectors/weak-hash.js";
34
47
  import { cloudformationDetector } from "./detectors/cloudformation.js";
48
+ import { bicepDetector } from "./detectors/bicep.js";
49
+ import { pulumiDetector } from "./detectors/pulumi.js";
35
50
  import { meshDetector } from "./detectors/mesh.js";
36
51
  import { dnssecDetector } from "./detectors/dnssec.js";
37
52
  import { vpnDetector } from "./detectors/vpn.js";
53
+ import { ansibleDetector } from "./detectors/ansible.js";
54
+ import { ageDetector } from "./detectors/age.js";
55
+ import { supplyChainDetector } from "./detectors/supply-chain.js";
56
+ import { vaultDetector } from "./detectors/vault.js";
57
+ import { keystoreDetector } from "./detectors/keystore.js";
58
+ import { openpgpDetector } from "./detectors/openpgp.js";
38
59
  import { statefulHbsDetector } from "./detectors/stateful-hbs.js";
39
60
 
40
61
  /** Normalised scope of a detector (defaults to "source" when undeclared). */
@@ -125,10 +146,15 @@ export class DetectorRegistry {
125
146
  }
126
147
 
127
148
  /**
128
- * The built-in detectors, in run order: the JS/TS source + config detectors,
129
- * then the per-language detectors (Python, Go, Java, C#, Rust, Ruby, C/C++),
130
- * then the language-agnostic PEM and stateful-HBS (SP 800-208) detectors. The
131
- * manifest (dependency) scanner is handled separately by `scan()`.
149
+ * The built-in detectors, in run order: the JS/TS source + language-agnostic config
150
+ * detectors, the per-language source packs (Python, Go, Java/Kotlin/Scala, C#, Rust,
151
+ * Ruby, PHP, Elixir, C/C++, Swift, Objective-C, Dart, Solidity/Move/Cairo), the
152
+ * infrastructure/config detectors (Terraform, Bicep, Pulumi, CloudFormation, cloud-KMS,
153
+ * k8s, mesh, DNSSEC, Vault, database/TDE, messaging, VPN, Ansible, supply-chain, CI/CD,
154
+ * secrets, age, keystore, OpenPGP, JWK, XML-DSig/SAML, PKCS#11/HSM, DKIM, SSH-CA,
155
+ * SPIFFE/SPIRE, reverse-proxy/gRPC TLS, WebAuthn/FIDO2, code-signing, weak-hash-in-
156
+ * signature), and the stateful-HBS (SP 800-208) detector. The manifest (dependency)
157
+ * scanner is handled separately by `scan()`.
132
158
  *
133
159
  * This is the single source of truth for the default detector set: both
134
160
  * {@link defaultRegistry} and the public `detectors` export (re-exported from
@@ -145,6 +171,10 @@ export const builtinDetectors: Detector[] = [
145
171
  phpDetector,
146
172
  elixirDetector,
147
173
  cDetector,
174
+ swiftDetector,
175
+ objcDetector,
176
+ dartDetector,
177
+ solidityDetector,
148
178
  pemDetector,
149
179
  jwkDetector,
150
180
  terraformDetector,
@@ -155,10 +185,27 @@ export const builtinDetectors: Detector[] = [
155
185
  k8sDetector,
156
186
  messagingDetector,
157
187
  databaseDetector,
188
+ xmldsigDetector,
189
+ pkcs11Detector,
190
+ dkimDetector,
191
+ sshCaDetector,
192
+ spireDetector,
193
+ proxyDetector,
194
+ webauthnDetector,
195
+ codesignDetector,
196
+ weakHashDetector,
158
197
  cloudformationDetector,
198
+ bicepDetector,
199
+ pulumiDetector,
159
200
  meshDetector,
160
201
  dnssecDetector,
161
202
  vpnDetector,
203
+ ansibleDetector,
204
+ ageDetector,
205
+ supplyChainDetector,
206
+ vaultDetector,
207
+ keystoreDetector,
208
+ openpgpDetector,
162
209
  statefulHbsDetector,
163
210
  ];
164
211
 
@@ -69,7 +69,7 @@ const NEW_SINK_LINE_RE = new RegExp(NEW_SINK_RE.source);
69
69
 
70
70
  /** Max changed lines allowed in an auto-verified LLM patch — a real crypto fix
71
71
  * is localized; a sprawling rewrite is not reviewable as "just the fix". */
72
- export const LLM_PATCH_MAX_CHANGED_LINES = 60;
72
+ const LLM_PATCH_MAX_CHANGED_LINES = 60;
73
73
 
74
74
  function countMatches(re: RegExp, s: string): number {
75
75
  return (s.match(re) ?? []).length;
@@ -6,6 +6,7 @@
6
6
  * standards to track are captured in {@link PQC_TRANSITION_NOTE}.
7
7
  */
8
8
  import type { AlgorithmFamily, Remediation } from "./types.js";
9
+ import type { StandardsProfile } from "./standards-profiles.js";
9
10
 
10
11
  /** Canonical remediation table, keyed by algorithm family. */
11
12
  const REMEDIATIONS: Record<AlgorithmFamily, Remediation> = {
@@ -188,6 +189,44 @@ export function remediationForTier(
188
189
  };
189
190
  }
190
191
 
192
+ /**
193
+ * Regime-aware remediation. Composes the base family guidance with a
194
+ * {@link StandardsProfile}'s parameter sets, HYBRID STANCE, and deprecation
195
+ * deadlines, so an ANSSI/BSI audience is told hybrid is *required* while a CNSA 2.0
196
+ * audience is told it is *optional* (and pointed at the 1024-level hybrid). This is
197
+ * the profile-driven successor to {@link remediationForTier}; `--tier category-5`
198
+ * resolves to the `cnsa-2.0` profile.
199
+ */
200
+ export function remediationForProfile(
201
+ algorithm: AlgorithmFamily,
202
+ profile: StandardsProfile,
203
+ ): Remediation {
204
+ const base = REMEDIATIONS[algorithm];
205
+ const isConf = isConfidentialityFamily(algorithm);
206
+ const primary = isConf ? profile.paramSets.kem : profile.paramSets.signature;
207
+ // The hybrid stance governs KEY ESTABLISHMENT, so it is surfaced for confidentiality
208
+ // families; signature families migrate to a stateless PQC signer directly.
209
+ const stanceWord =
210
+ profile.hybridStance === "required"
211
+ ? "hybrid required"
212
+ : profile.hybridStance === "recommended"
213
+ ? "hybrid recommended"
214
+ : "hybrids optional";
215
+ const recommendation = isConf
216
+ ? `${primary} — ${profile.name}: ${stanceWord}`
217
+ : `${primary} — ${profile.name}`;
218
+ const hybridClause = isConf ? ` ${profile.hybridGuidance}` : "";
219
+ return {
220
+ algorithm,
221
+ recommendation,
222
+ detail:
223
+ `${base.detail} Under ${profile.name} (${profile.citation}), use ${profile.paramSets.kem} for ` +
224
+ `key establishment and ${profile.paramSets.signature} for signatures; classical public-key ` +
225
+ `crypto is deprecated after ${profile.deprecateAfter} and disallowed after ${profile.disallowAfter}.` +
226
+ hybridClause,
227
+ };
228
+ }
229
+
191
230
  /**
192
231
  * Guidance for stateful hash-based signatures (SP 800-208: LMS / XMSS / HSS).
193
232
  * These are NIST-approved for firmware / boot signing but are STATEFUL — each
package/src/report.ts CHANGED
@@ -7,9 +7,12 @@ import type { AlgorithmFamily, Finding, RuleMeta, ScanResult, Severity } from ".
7
7
  import { VERSION } from "./version.js";
8
8
  import { SEVERITY_ORDER, sarifLevel } from "./severity.js";
9
9
  import { ANALYZABLE_LANGUAGES_LABEL } from "./detect-utils.js";
10
- import { remediationFor, remediationForTier } from "./remediation.js";
10
+ import { remediationFor, remediationForTier, remediationForProfile } from "./remediation.js";
11
11
  import type { SecurityTier } from "./remediation.js";
12
+ import type { StandardsProfile } from "./standards-profiles.js";
12
13
  import { fingerprintFinding } from "./baseline.js";
14
+ import { findingFingerprint } from "./hndl.js";
15
+ import type { FindingExposure, HndlReport } from "./hndl.js";
13
16
 
14
17
  /** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */
15
18
  export interface SarifLog {
@@ -35,6 +38,29 @@ export interface ReportOptions {
35
38
  * {@link toJson}.
36
39
  */
37
40
  catalog?: RuleMeta[];
41
+ /**
42
+ * Optional HNDL exposure analysis ({@link computeHndl}). When supplied, each
43
+ * finding gains its `exposure` fields (score, bound data asset, rationale)
44
+ * keyed by fingerprint, and the report carries the repo-level HNDL summary.
45
+ * Purely additive: it never changes finding identity, ordering, or exit codes.
46
+ */
47
+ hndl?: HndlReport;
48
+ }
49
+
50
+ /** The per-finding exposure block emitted in JSON / SARIF, or undefined. */
51
+ function exposureFor(f: Finding, hndl: HndlReport | undefined): FindingExposure | undefined {
52
+ if (!hndl) return undefined;
53
+ return hndl.byFingerprint.get(findingFingerprint(f));
54
+ }
55
+
56
+ /** The repo HNDL summary block shared by JSON output and the SARIF run. */
57
+ function hndlSummaryBlock(hndl: HndlReport): Record<string, unknown> {
58
+ return {
59
+ modelVersion: hndl.modelVersion,
60
+ horizon: hndl.horizon,
61
+ summary: hndl.summary,
62
+ assets: hndl.assets,
63
+ };
38
64
  }
39
65
 
40
66
  const SARIF_SCHEMA =
@@ -104,6 +130,16 @@ function sarifRule(spec: {
104
130
  };
105
131
  }
106
132
 
133
+ /** SARIF result.properties fragment for a finding's HNDL exposure, or empty. */
134
+ function exposureProperties(exposure: FindingExposure | undefined): Record<string, unknown> {
135
+ if (!exposure) return {};
136
+ return {
137
+ exposureScore: exposure.exposureScore,
138
+ dataAsset: exposure.dataAsset,
139
+ exposureRationale: exposure.rationale,
140
+ };
141
+ }
142
+
107
143
  /** Serialize a scan result as SARIF 2.1.0. */
108
144
  export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
109
145
  const redactSnippets = opts?.redactSnippets ?? false;
@@ -170,6 +206,10 @@ export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
170
206
  // edit above it. `quantakrypto/v1` names our scheme.
171
207
  partialFingerprints: { "quantakrypto/v1": fingerprintFinding(f) },
172
208
  properties: {
209
+ // Same stable identity mirrored into properties so non-GitHub SARIF
210
+ // consumers (our platform ingest) can read one uniform `fingerprint`
211
+ // field across JSON and SARIF without reaching into partialFingerprints.
212
+ fingerprint: fingerprintFinding(f),
173
213
  category: f.category,
174
214
  severity: f.severity,
175
215
  confidence: f.confidence,
@@ -177,6 +217,7 @@ export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
177
217
  ...(f.algorithm ? { algorithm: f.algorithm } : {}),
178
218
  ...(f.remediation ? { remediation: f.remediation } : {}),
179
219
  ...(f.cwe ? { cwe: f.cwe } : {}),
220
+ ...exposureProperties(exposureFor(f, opts?.hndl)),
180
221
  },
181
222
  ...(f.cwe
182
223
  ? {
@@ -232,6 +273,7 @@ export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
232
273
  },
233
274
  },
234
275
  ...(taxonomies.length > 0 ? { taxonomies } : {}),
276
+ ...(opts?.hndl ? { properties: { hndl: hndlSummaryBlock(opts.hndl) } } : {}),
235
277
  results,
236
278
  },
237
279
  ],
@@ -257,6 +299,7 @@ function securitySeverity(severity: Severity): string {
257
299
  /** Serialize a scan result as a plain JSON-friendly object. */
258
300
  export function toJson(result: ScanResult, opts?: ReportOptions): Record<string, unknown> {
259
301
  const redactSnippets = opts?.redactSnippets ?? false;
302
+ const hndl = opts?.hndl;
260
303
  return {
261
304
  toolVersion: result.toolVersion,
262
305
  root: result.root,
@@ -272,25 +315,48 @@ export function toJson(result: ScanResult, opts?: ReportOptions): Record<string,
272
315
  byCategory: result.inventory.byCategory,
273
316
  byAlgorithm: result.inventory.byAlgorithm,
274
317
  },
275
- findings: result.findings.map((f) => ({
276
- ruleId: f.ruleId,
277
- title: f.title,
278
- category: f.category,
279
- severity: f.severity,
280
- confidence: f.confidence,
281
- algorithm: f.algorithm,
282
- hndl: f.hndl,
283
- message: f.message,
284
- remediation: f.remediation,
285
- cwe: f.cwe,
286
- location: {
287
- file: f.location.file,
288
- line: f.location.line,
289
- column: f.location.column,
290
- endLine: f.location.endLine,
291
- snippet: emittedSnippet(f, redactSnippets),
292
- },
293
- })),
318
+ ...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),
319
+ findings: result.findings.map((f) => {
320
+ const exposure = exposureFor(f, hndl);
321
+ return {
322
+ // Stable, line-INSENSITIVE identity of the finding: sha256 of
323
+ // ruleId | normalized-POSIX-repo-relative-path | normalized-snippet
324
+ // (the SARIF partialFingerprints trick, line number deliberately
325
+ // excluded). Reused verbatim from the baseline module so JSON identity,
326
+ // SARIF partialFingerprints, and the baseline suppression set are one and
327
+ // the same value. A line move does NOT change it; when no snippet context
328
+ // exists it falls back to ruleId|path. This is the cross-run identity the
329
+ // platform keys posture drift on.
330
+ fingerprint: fingerprintFinding(f),
331
+ ruleId: f.ruleId,
332
+ title: f.title,
333
+ category: f.category,
334
+ severity: f.severity,
335
+ confidence: f.confidence,
336
+ algorithm: f.algorithm,
337
+ hndl: f.hndl,
338
+ message: f.message,
339
+ remediation: f.remediation,
340
+ cwe: f.cwe,
341
+ location: {
342
+ file: f.location.file,
343
+ line: f.location.line,
344
+ column: f.location.column,
345
+ endLine: f.location.endLine,
346
+ snippet: emittedSnippet(f, redactSnippets),
347
+ },
348
+ ...(exposure
349
+ ? {
350
+ exposure: {
351
+ fingerprint: exposure.fingerprint,
352
+ exposureScore: exposure.exposureScore,
353
+ dataAsset: exposure.dataAsset,
354
+ rationale: exposure.rationale,
355
+ },
356
+ }
357
+ : {}),
358
+ };
359
+ }),
294
360
  };
295
361
  }
296
362
 
@@ -475,3 +541,37 @@ export function formatTierGuidance(
475
541
  }
476
542
  return out;
477
543
  }
544
+
545
+ /**
546
+ * Per-family migration targets tailored to a selected {@link StandardsProfile}
547
+ * (`--profile`). Unlike {@link formatTierGuidance} (CNSA-tier only), this surfaces the
548
+ * regime's parameter sets AND its hybrid stance — required (ANSSI/BSI) vs recommended
549
+ * (NIST/NCSC) vs optional (CNSA 2.0) — so guidance isn't regime-wrong. Returns plain
550
+ * (un-coloured) lines; the caller styles them.
551
+ */
552
+ export function formatProfileGuidance(
553
+ byAlgorithm: Record<string, number>,
554
+ profile: StandardsProfile,
555
+ ): string[] {
556
+ const out: string[] = [`${profile.name} migration targets:`];
557
+ const seen = new Set<string>();
558
+ for (const [k, n] of Object.entries(byAlgorithm)) {
559
+ if (n <= 0) continue;
560
+ const fam = k as AlgorithmFamily;
561
+ if (fam === "unknown" || !remediationFor(fam)) continue; // skip unmapped families
562
+ const rem = remediationForProfile(fam, profile);
563
+ if (seen.has(rem.recommendation)) continue;
564
+ seen.add(rem.recommendation);
565
+ out.push(` ${fam} → ${rem.recommendation}`);
566
+ }
567
+ const stance =
568
+ profile.hybridStance === "required"
569
+ ? "requires classical+PQC hybridization"
570
+ : profile.hybridStance === "recommended"
571
+ ? "recommends hybridization"
572
+ : "does not require hybridization";
573
+ out.push(
574
+ ` ${profile.authority} ${stance}; classical public-key crypto disallowed after ${profile.disallowAfter} (${profile.citation}).`,
575
+ );
576
+ return out;
577
+ }
@@ -15,7 +15,7 @@ import { parentPort, workerData } from "node:worker_threads";
15
15
  import type { Finding } from "./types.js";
16
16
  import { defaultRegistry } from "./registry.js";
17
17
  import { detectFile } from "./scan.js";
18
- import { looksMinified } from "./walk.js";
18
+ import { isKeystorePath, looksMinified } from "./walk.js";
19
19
  import { isManifestFile } from "./dependencies.js";
20
20
 
21
21
  interface WorkerToggles {
@@ -48,14 +48,16 @@ if (parentPort) {
48
48
 
49
49
  for (const rel of req.files) {
50
50
  const abs = path.join(baseDir, ...rel.split("/"));
51
+ // Keystores (.jks/.p12/…) are read byte-preserving (latin1); see scan.ts.
52
+ const keystore = isKeystorePath(rel);
51
53
  let content: string;
52
54
  try {
53
- content = readFileSync(abs, "utf8");
55
+ content = readFileSync(abs, keystore ? "latin1" : "utf8");
54
56
  } catch {
55
57
  unreadable += 1;
56
58
  continue;
57
59
  }
58
- if (!toggles.scanMinified && !isManifestFile(rel) && looksMinified(content)) {
60
+ if (!toggles.scanMinified && !isManifestFile(rel) && !keystore && looksMinified(content)) {
59
61
  skippedMinified += 1;
60
62
  continue;
61
63
  }