@blamejs/core 0.15.12 → 0.15.14

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 (365) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +45 -29
  4. package/lib/acme.js +6 -5
  5. package/lib/agent-event-bus.js +18 -4
  6. package/lib/agent-idempotency.js +7 -6
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-snapshot.js +32 -2
  10. package/lib/agent-tenant.js +2 -5
  11. package/lib/ai-adverse-decision.js +2 -15
  12. package/lib/ai-aedt-bias-audit.js +2 -1
  13. package/lib/ai-capability.js +1 -6
  14. package/lib/ai-content-detect.js +1 -3
  15. package/lib/ai-dp.js +1 -5
  16. package/lib/ai-frontier-protocol.js +1 -1
  17. package/lib/ai-input.js +2 -2
  18. package/lib/ai-model-manifest.js +1 -1
  19. package/lib/ai-output.js +16 -7
  20. package/lib/ai-pref.js +3 -8
  21. package/lib/ai-quota.js +3 -14
  22. package/lib/api-key.js +37 -28
  23. package/lib/api-snapshot.js +4 -1
  24. package/lib/app-shutdown.js +7 -1
  25. package/lib/archive-adapters.js +2 -4
  26. package/lib/archive-entry-policy.js +32 -0
  27. package/lib/archive-gz.js +9 -0
  28. package/lib/archive-read.js +14 -24
  29. package/lib/archive-tar-read.js +56 -24
  30. package/lib/archive.js +6 -12
  31. package/lib/arg-parser.js +7 -6
  32. package/lib/asn1-der.js +70 -22
  33. package/lib/asyncapi-traits.js +2 -6
  34. package/lib/atomic-file.js +312 -33
  35. package/lib/audit-chain.js +183 -54
  36. package/lib/audit-daily-review.js +36 -16
  37. package/lib/audit-emit.js +82 -0
  38. package/lib/audit-sign.js +258 -0
  39. package/lib/audit-tools.js +108 -23
  40. package/lib/audit.js +91 -2
  41. package/lib/auth/access-lock.js +7 -29
  42. package/lib/auth/bot-challenge.js +1 -1
  43. package/lib/auth/ciba.js +43 -4
  44. package/lib/auth/dpop.js +29 -36
  45. package/lib/auth/fido-mds3.js +14 -16
  46. package/lib/auth/jwt-external.js +26 -8
  47. package/lib/auth/jwt.js +15 -17
  48. package/lib/auth/lockout.js +24 -27
  49. package/lib/auth/oauth.js +67 -45
  50. package/lib/auth/oid4vci.js +55 -32
  51. package/lib/auth/oid4vp.js +3 -2
  52. package/lib/auth/openid-federation.js +6 -6
  53. package/lib/auth/passkey.js +4 -4
  54. package/lib/auth/password.js +8 -2
  55. package/lib/auth/saml.js +70 -40
  56. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  57. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  58. package/lib/auth/sd-jwt-vc.js +25 -5
  59. package/lib/auth/status-list.js +21 -9
  60. package/lib/auth/step-up.js +15 -13
  61. package/lib/auth-bot-challenge.js +27 -39
  62. package/lib/backup/bundle.js +18 -20
  63. package/lib/backup/crypto.js +23 -8
  64. package/lib/backup/index.js +55 -67
  65. package/lib/backup/manifest.js +7 -1
  66. package/lib/bounded-map.js +112 -1
  67. package/lib/breach-deadline.js +1 -11
  68. package/lib/break-glass.js +41 -22
  69. package/lib/cache.js +7 -18
  70. package/lib/cbor.js +36 -12
  71. package/lib/cdn-cache-control.js +15 -12
  72. package/lib/cert.js +8 -13
  73. package/lib/chain-writer.js +162 -47
  74. package/lib/cli.js +10 -2
  75. package/lib/client-hints.js +7 -9
  76. package/lib/cloud-events.js +43 -33
  77. package/lib/cluster-storage.js +9 -41
  78. package/lib/cms-codec.js +2 -1
  79. package/lib/codepoint-class.js +84 -1
  80. package/lib/compliance-ai-act-logging.js +1 -6
  81. package/lib/compliance-ai-act-transparency.js +2 -4
  82. package/lib/compliance-eaa.js +2 -12
  83. package/lib/compliance-sanctions-fetcher.js +21 -28
  84. package/lib/compliance-sanctions.js +11 -21
  85. package/lib/compliance.js +3 -10
  86. package/lib/config-drift.js +24 -20
  87. package/lib/content-credentials.js +11 -8
  88. package/lib/content-digest.js +21 -12
  89. package/lib/cookies.js +15 -13
  90. package/lib/cose.js +39 -11
  91. package/lib/cra-report.js +1 -11
  92. package/lib/crdt.js +2 -1
  93. package/lib/crypto-field.js +34 -33
  94. package/lib/crypto.js +138 -7
  95. package/lib/csp.js +239 -3
  96. package/lib/daemon.js +46 -42
  97. package/lib/data-act.js +46 -13
  98. package/lib/db-file-lifecycle.js +14 -6
  99. package/lib/db-query.js +75 -49
  100. package/lib/db.js +66 -27
  101. package/lib/dbsc.js +5 -6
  102. package/lib/ddl-change-control.js +18 -14
  103. package/lib/deprecate.js +4 -5
  104. package/lib/did.js +6 -2
  105. package/lib/dora.js +43 -13
  106. package/lib/dr-runbook.js +1 -1
  107. package/lib/dsa.js +2 -1
  108. package/lib/dsr.js +30 -20
  109. package/lib/early-hints.js +19 -0
  110. package/lib/eat.js +5 -1
  111. package/lib/external-db-migrate.js +21 -22
  112. package/lib/external-db.js +74 -30
  113. package/lib/fda-21cfr11.js +42 -13
  114. package/lib/fdx.js +22 -18
  115. package/lib/file-upload.js +80 -66
  116. package/lib/flag-evaluation-context.js +5 -8
  117. package/lib/flag-providers.js +6 -2
  118. package/lib/forms.js +1 -1
  119. package/lib/framework-error.js +12 -0
  120. package/lib/framework-schema.js +19 -0
  121. package/lib/fsm.js +7 -12
  122. package/lib/gate-contract.js +911 -39
  123. package/lib/gdpr-ropa.js +22 -22
  124. package/lib/graphql-federation.js +18 -5
  125. package/lib/guard-agent-registry.js +2 -1
  126. package/lib/guard-all.js +3 -2
  127. package/lib/guard-archive.js +9 -30
  128. package/lib/guard-auth.js +23 -80
  129. package/lib/guard-cidr.js +20 -96
  130. package/lib/guard-csv.js +135 -196
  131. package/lib/guard-domain.js +23 -106
  132. package/lib/guard-dsn.js +17 -14
  133. package/lib/guard-email.js +46 -53
  134. package/lib/guard-envelope.js +2 -2
  135. package/lib/guard-event-bus-topic.js +2 -1
  136. package/lib/guard-filename.js +12 -60
  137. package/lib/guard-graphql.js +28 -75
  138. package/lib/guard-html-wcag.js +15 -2
  139. package/lib/guard-html.js +74 -128
  140. package/lib/guard-idempotency-key.js +2 -1
  141. package/lib/guard-image.js +280 -77
  142. package/lib/guard-imap-command.js +9 -10
  143. package/lib/guard-jmap.js +1 -1
  144. package/lib/guard-json.js +101 -109
  145. package/lib/guard-jsonpath.js +20 -88
  146. package/lib/guard-jwt.js +32 -114
  147. package/lib/guard-list-id.js +2 -7
  148. package/lib/guard-list-unsubscribe.js +2 -7
  149. package/lib/guard-mail-compose.js +5 -6
  150. package/lib/guard-mail-move.js +3 -2
  151. package/lib/guard-mail-query.js +5 -3
  152. package/lib/guard-mail-sieve.js +6 -7
  153. package/lib/guard-managesieve-command.js +6 -5
  154. package/lib/guard-markdown.js +76 -110
  155. package/lib/guard-message-id.js +5 -6
  156. package/lib/guard-mime.js +20 -99
  157. package/lib/guard-oauth.js +19 -73
  158. package/lib/guard-pdf.js +65 -72
  159. package/lib/guard-pop3-command.js +13 -14
  160. package/lib/guard-posture-chain.js +2 -1
  161. package/lib/guard-regex.js +24 -99
  162. package/lib/guard-saga-config.js +2 -1
  163. package/lib/guard-shell.js +22 -87
  164. package/lib/guard-smtp-command.js +9 -12
  165. package/lib/guard-sql.js +15 -13
  166. package/lib/guard-stream-args.js +2 -1
  167. package/lib/guard-svg.js +103 -149
  168. package/lib/guard-template.js +23 -80
  169. package/lib/guard-tenant-id.js +2 -1
  170. package/lib/guard-text.js +592 -0
  171. package/lib/guard-time.js +27 -95
  172. package/lib/guard-uuid.js +21 -93
  173. package/lib/guard-xml.js +76 -106
  174. package/lib/guard-yaml.js +24 -60
  175. package/lib/html-balance.js +7 -3
  176. package/lib/http-client-cache.js +5 -12
  177. package/lib/http-client-cookie-jar.js +35 -16
  178. package/lib/http-client.js +233 -74
  179. package/lib/http-message-signature.js +3 -2
  180. package/lib/i18n-messageformat.js +5 -7
  181. package/lib/i18n.js +83 -26
  182. package/lib/iab-tcf.js +3 -2
  183. package/lib/importmap-integrity.js +41 -1
  184. package/lib/inbox.js +8 -8
  185. package/lib/incident-report.js +12 -27
  186. package/lib/ip-utils.js +49 -6
  187. package/lib/jobs.js +3 -2
  188. package/lib/json-patch.js +1 -1
  189. package/lib/json-path.js +24 -3
  190. package/lib/jtd.js +2 -2
  191. package/lib/keychain.js +6 -18
  192. package/lib/legal-hold.js +30 -23
  193. package/lib/log-stream-cloudwatch.js +44 -112
  194. package/lib/log-stream-otlp-grpc.js +17 -14
  195. package/lib/log-stream-otlp.js +16 -80
  196. package/lib/log-stream-webhook.js +20 -92
  197. package/lib/log.js +2 -2
  198. package/lib/mail-agent.js +2 -2
  199. package/lib/mail-arc-sign.js +8 -3
  200. package/lib/mail-arf.js +4 -3
  201. package/lib/mail-auth.js +43 -69
  202. package/lib/mail-bimi.js +35 -55
  203. package/lib/mail-bounce.js +3 -3
  204. package/lib/mail-crypto-pgp.js +3 -2
  205. package/lib/mail-crypto-smime.js +71 -6
  206. package/lib/mail-dav.js +8 -35
  207. package/lib/mail-deploy.js +15 -14
  208. package/lib/mail-dkim.js +19 -38
  209. package/lib/mail-greylist.js +17 -30
  210. package/lib/mail-helo.js +4 -7
  211. package/lib/mail-journal.js +13 -9
  212. package/lib/mail-mdn.js +12 -7
  213. package/lib/mail-rbl.js +7 -12
  214. package/lib/mail-scan.js +5 -7
  215. package/lib/mail-send-deliver.js +33 -20
  216. package/lib/mail-server-imap.js +32 -87
  217. package/lib/mail-server-jmap.js +39 -52
  218. package/lib/mail-server-managesieve.js +29 -83
  219. package/lib/mail-server-mx.js +33 -74
  220. package/lib/mail-server-net.js +177 -0
  221. package/lib/mail-server-pop3.js +30 -83
  222. package/lib/mail-server-rate-limit.js +30 -6
  223. package/lib/mail-server-registry.js +1 -1
  224. package/lib/mail-server-submission.js +34 -73
  225. package/lib/mail-server-tls.js +98 -2
  226. package/lib/mail-spam-score.js +9 -12
  227. package/lib/mail-store-fts.js +1 -1
  228. package/lib/mail-store.js +3 -2
  229. package/lib/mail.js +28 -13
  230. package/lib/markup-escape.js +31 -0
  231. package/lib/markup-tokenizer.js +78 -0
  232. package/lib/mcp-tool-registry.js +26 -23
  233. package/lib/mcp.js +7 -5
  234. package/lib/mdoc.js +36 -14
  235. package/lib/metrics.js +46 -33
  236. package/lib/middleware/age-gate.js +3 -23
  237. package/lib/middleware/api-encrypt.js +13 -24
  238. package/lib/middleware/assetlinks.js +2 -7
  239. package/lib/middleware/bearer-auth.js +2 -1
  240. package/lib/middleware/body-parser.js +41 -52
  241. package/lib/middleware/bot-disclose.js +7 -10
  242. package/lib/middleware/bot-guard.js +28 -28
  243. package/lib/middleware/clear-site-data.js +5 -1
  244. package/lib/middleware/compression.js +9 -0
  245. package/lib/middleware/cors.js +32 -23
  246. package/lib/middleware/csrf-protect.js +73 -23
  247. package/lib/middleware/daily-byte-quota.js +12 -30
  248. package/lib/middleware/deny-response.js +19 -1
  249. package/lib/middleware/fetch-metadata.js +35 -4
  250. package/lib/middleware/idempotency-key.js +4 -20
  251. package/lib/middleware/network-allowlist.js +61 -30
  252. package/lib/middleware/rate-limit.js +45 -44
  253. package/lib/middleware/scim-server.js +5 -3
  254. package/lib/middleware/security-headers.js +33 -7
  255. package/lib/middleware/security-txt.js +2 -6
  256. package/lib/middleware/speculation-rules.js +6 -3
  257. package/lib/middleware/tus-upload.js +14 -29
  258. package/lib/middleware/web-app-manifest.js +2 -7
  259. package/lib/migrations.js +4 -13
  260. package/lib/mime-parse.js +34 -17
  261. package/lib/module-loader.js +44 -0
  262. package/lib/money.js +105 -0
  263. package/lib/mtls-ca.js +125 -41
  264. package/lib/network-byte-quota.js +4 -18
  265. package/lib/network-dane.js +8 -6
  266. package/lib/network-dns-resolver.js +98 -7
  267. package/lib/network-dns.js +9 -2
  268. package/lib/network-dnssec.js +18 -17
  269. package/lib/network-heartbeat.js +3 -2
  270. package/lib/network-smtp-policy.js +52 -46
  271. package/lib/network-tls.js +67 -47
  272. package/lib/network-tsig.js +2 -2
  273. package/lib/nis2-report.js +2 -12
  274. package/lib/nist-crosswalk.js +1 -1
  275. package/lib/nonce-store.js +81 -3
  276. package/lib/ntp-check.js +28 -0
  277. package/lib/numeric-bounds.js +31 -8
  278. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  279. package/lib/object-store/azure-blob.js +6 -47
  280. package/lib/object-store/gcs-bucket-ops.js +4 -2
  281. package/lib/object-store/gcs.js +14 -75
  282. package/lib/object-store/http-put.js +2 -7
  283. package/lib/object-store/http-request.js +157 -0
  284. package/lib/object-store/local.js +37 -17
  285. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  286. package/lib/object-store/sigv4.js +10 -79
  287. package/lib/observability-otlp-exporter.js +29 -29
  288. package/lib/observability.js +95 -0
  289. package/lib/openapi-paths-builder.js +7 -3
  290. package/lib/otel-export.js +7 -10
  291. package/lib/outbox.js +54 -41
  292. package/lib/pagination.js +11 -15
  293. package/lib/parsers/safe-env.js +5 -4
  294. package/lib/parsers/safe-ini.js +10 -4
  295. package/lib/parsers/safe-toml.js +11 -9
  296. package/lib/parsers/safe-xml.js +3 -3
  297. package/lib/parsers/safe-yaml.js +28 -9
  298. package/lib/pick.js +63 -5
  299. package/lib/pipl-cn.js +69 -59
  300. package/lib/privacy-pass.js +8 -6
  301. package/lib/problem-details.js +2 -5
  302. package/lib/pubsub.js +4 -8
  303. package/lib/queue-local.js +10 -3
  304. package/lib/redact.js +9 -4
  305. package/lib/render.js +4 -2
  306. package/lib/request-helpers.js +334 -29
  307. package/lib/resource-access-lock.js +3 -3
  308. package/lib/restore-bundle.js +46 -18
  309. package/lib/restore-rollback.js +10 -4
  310. package/lib/restore.js +24 -22
  311. package/lib/retention.js +23 -9
  312. package/lib/router.js +17 -4
  313. package/lib/safe-async.js +457 -0
  314. package/lib/safe-buffer.js +137 -6
  315. package/lib/safe-decompress.js +2 -1
  316. package/lib/safe-dns.js +2 -1
  317. package/lib/safe-ical.js +14 -28
  318. package/lib/safe-icap.js +1 -1
  319. package/lib/safe-json.js +51 -8
  320. package/lib/safe-jsonpath.js +6 -5
  321. package/lib/safe-mime.js +25 -29
  322. package/lib/safe-redirect.js +2 -5
  323. package/lib/safe-schema.js +7 -6
  324. package/lib/safe-sieve.js +1 -1
  325. package/lib/safe-sql.js +126 -0
  326. package/lib/safe-vcard.js +13 -27
  327. package/lib/sandbox-worker.js +15 -2
  328. package/lib/sandbox.js +4 -3
  329. package/lib/scheduler.js +22 -13
  330. package/lib/sec-cyber.js +82 -37
  331. package/lib/seeders.js +4 -11
  332. package/lib/self-update-standalone-verifier.js +16 -0
  333. package/lib/self-update.js +92 -69
  334. package/lib/session-device-binding.js +112 -66
  335. package/lib/session.js +219 -7
  336. package/lib/sql.js +228 -81
  337. package/lib/sse.js +6 -10
  338. package/lib/static.js +201 -111
  339. package/lib/storage.js +4 -2
  340. package/lib/structured-fields.js +275 -16
  341. package/lib/template.js +7 -5
  342. package/lib/tenant-quota.js +53 -38
  343. package/lib/tsa.js +16 -17
  344. package/lib/validate-opts.js +154 -8
  345. package/lib/vault/index.js +5 -0
  346. package/lib/vault/passphrase-ops.js +22 -26
  347. package/lib/vault/passphrase-source.js +8 -3
  348. package/lib/vault/rotate.js +13 -18
  349. package/lib/vault/seal-pem-file.js +34 -49
  350. package/lib/vault-aad.js +3 -2
  351. package/lib/vc.js +3 -8
  352. package/lib/vendor/MANIFEST.json +10 -10
  353. package/lib/vendor/public-suffix-list.dat +23 -10
  354. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  355. package/lib/vex.js +35 -13
  356. package/lib/web-push-vapid.js +23 -20
  357. package/lib/webhook-dispatcher.js +706 -0
  358. package/lib/webhook.js +59 -19
  359. package/lib/websocket-channels.js +9 -7
  360. package/lib/websocket.js +8 -4
  361. package/lib/worm.js +3 -4
  362. package/lib/ws-client.js +65 -56
  363. package/lib/x509-chain.js +44 -0
  364. package/package.json +1 -1
  365. package/sbom.cdx.json +6 -6
package/lib/cose.js CHANGED
@@ -55,6 +55,7 @@
55
55
  var nodeCrypto = require("node:crypto");
56
56
  var cbor = require("./cbor");
57
57
  var bCrypto = require("./crypto");
58
+ var safeBuffer = require("./safe-buffer");
58
59
  var validateOpts = require("./validate-opts");
59
60
  var { defineClass } = require("./framework-error");
60
61
 
@@ -109,11 +110,30 @@ function _algParamsFor(algId) {
109
110
  }
110
111
  }
111
112
 
113
+ // RFC 9053 §2 binds each ECDSA alg to ONE curve. node:crypto will happily
114
+ // verify a sha512 (ES512-shape) signature against a P-256 key as
115
+ // self-consistent, so without this check a COSE_Sign1 declaring ES512 but
116
+ // carrying a P-256 key passes when ES512 is allowlisted — an alg/curve
117
+ // confusion (the COSE sibling of jwt-external._assertAlgKtyMatch). EdDSA /
118
+ // ML-DSA carry their parameters in the KeyObject and need no binding.
119
+ var _ES_ALG_CURVE = { ES256: "prime256v1", ES384: "secp384r1", ES512: "secp521r1" };
120
+ function _assertEcAlgCurve(algName, key) {
121
+ var want = _ES_ALG_CURVE[algName];
122
+ if (want === undefined) return; // not an ECDSA alg — nothing to bind
123
+ var got = (key && key.asymmetricKeyDetails && key.asymmetricKeyDetails.namedCurve) || null;
124
+ if (key.asymmetricKeyType !== "ec" || got !== want) {
125
+ throw new CoseError("cose/alg-curve-mismatch",
126
+ "cose: alg '" + algName + "' requires an EC key on " + want +
127
+ ", got " + (key.asymmetricKeyType === "ec" ? got : key.asymmetricKeyType));
128
+ }
129
+ }
130
+
112
131
  function _bstr(x) {
113
- if (Buffer.isBuffer(x)) return x;
114
- if (x instanceof Uint8Array) return Buffer.from(x);
115
- if (typeof x === "string") return Buffer.from(x, "utf8");
116
- throw new CoseError("cose/bad-bytes", "cose: expected bytes (Buffer / Uint8Array / string)");
132
+ return safeBuffer.toBuffer(x, {
133
+ errorFactory: function (code, msg) { return new CoseError(code, msg); },
134
+ typeCode: "cose/bad-bytes",
135
+ typeMessage: "cose: expected bytes (Buffer / Uint8Array / string)",
136
+ });
117
137
  }
118
138
 
119
139
  // Sig_structure (RFC 9052 §4.4) for COSE_Sign1:
@@ -167,6 +187,7 @@ async function sign(payload, opts) {
167
187
  var algId = ALG_NAME_TO_ID[opts.alg];
168
188
  var params = _algParamsFor(algId);
169
189
  var key = _toKeyObject(opts.privateKey, "private");
190
+ _assertEcAlgCurve(opts.alg, key); // RFC 9053 alg↔curve binding — refuse e.g. ES512 over a P-256 key
170
191
 
171
192
  var protMap = new Map();
172
193
  protMap.set(HDR_ALG, algId);
@@ -350,6 +371,7 @@ async function verify(coseSign1, opts) {
350
371
  var key = opts.publicKey
351
372
  ? _toKeyObject(opts.publicKey, "public")
352
373
  : _toKeyObject(opts.keyResolver(protMap, unprotected), "public");
374
+ _assertEcAlgCurve(algName, key); // RFC 9053 alg↔curve binding
353
375
 
354
376
  var externalAad = opts.externalAad == null ? Buffer.alloc(0) : _bstr(opts.externalAad);
355
377
  var toBeSigned = _toBeSigned(protectedBstr, externalAad, payload);
@@ -753,11 +775,14 @@ var COSE_KEY_LABEL_KTY = 1;
753
775
  var COSE_KEY_LABEL_KID = 2;
754
776
  var COSE_KEY_LABEL_ALG = 3;
755
777
 
756
- function _coseKeyBytes(v, what) {
757
- if (Buffer.isBuffer(v)) return v;
758
- if (v instanceof Uint8Array) return Buffer.from(v);
759
- throw new CoseError("cose/bad-cose-key", "cose.importKey: COSE_Key " + what + " must be a byte string");
760
- }
778
+ // COSE_Key components are byte strings, never text (allowString:false).
779
+ var _coseKeyBytes = safeBuffer.makeByteCoercer({
780
+ errorClass: CoseError,
781
+ typeCode: "cose/bad-cose-key",
782
+ messagePrefix: "cose.importKey: COSE_Key ",
783
+ messageSuffix: " must be a byte string",
784
+ allowString: false,
785
+ });
761
786
 
762
787
  /**
763
788
  * @primitive b.cose.importKey
@@ -806,8 +831,11 @@ function importKey(coseKey) {
806
831
  } else {
807
832
  throw new CoseError("cose/unsupported-key", "cose.importKey: kty must be OKP (1) or EC2 (2), got " + kty);
808
833
  }
809
- try { return nodeCrypto.createPublicKey({ key: jwk, format: "jwk" }); }
810
- catch (e) { throw new CoseError("cose/bad-cose-key", "cose.importKey: could not import COSE_Key: " + ((e && e.message) || e)); }
834
+ return bCrypto.importPublicJwk(jwk, {
835
+ errorClass: CoseError,
836
+ code: "cose/bad-cose-key",
837
+ messagePrefix: "cose.importKey: could not import COSE_Key: ",
838
+ });
811
839
  }
812
840
 
813
841
  /**
package/lib/cra-report.js CHANGED
@@ -70,7 +70,6 @@ function create(opts) {
70
70
  var enisaEndpoint = typeof opts.enisaEndpoint === "string" && opts.enisaEndpoint.length > 0
71
71
  ? opts.enisaEndpoint : null;
72
72
  var httpClient = opts.httpClient || null;
73
- var auditOn = opts.audit !== false;
74
73
 
75
74
  // CRA Article 14 deadlines — operators don't override these without
76
75
  // documented regulatory justification (the deadlines are statutory,
@@ -89,16 +88,7 @@ function create(opts) {
89
88
  },
90
89
  });
91
90
 
92
- function _emitAudit(action, outcome, metadata) {
93
- if (!auditOn) return;
94
- try {
95
- audit().safeEmit({
96
- action: "cra.report." + action,
97
- outcome: outcome,
98
- metadata: metadata || {},
99
- });
100
- } catch (_e) { /* drop-silent */ }
101
- }
91
+ var _emitAudit = audit().namespaced("cra.report", opts.audit);
102
92
 
103
93
  async function _submitToEnisa(payload) {
104
94
  if (!enisaEndpoint || !httpClient) {
package/lib/crdt.js CHANGED
@@ -41,6 +41,7 @@
41
41
 
42
42
  var bCrypto = require("./crypto");
43
43
  var safeJson = require("./safe-json");
44
+ var numericBounds = require("./numeric-bounds");
44
45
  var { defineClass } = require("./framework-error");
45
46
 
46
47
  var CrdtError = defineClass("CrdtError", { alwaysPermanent: true });
@@ -52,7 +53,7 @@ function _replicaId(opts) {
52
53
  return id;
53
54
  }
54
55
  function _posInt(n, label) {
55
- if (typeof n !== "number" || !isFinite(n) || n < 0 || Math.floor(n) !== n) throw new CrdtError("crdt/bad-value", "crdt: " + label + " must be a non-negative integer");
56
+ if (!numericBounds.isNonNegativeFiniteInt(n)) throw new CrdtError("crdt/bad-value", "crdt: " + label + " must be a non-negative integer");
56
57
  return n;
57
58
  }
58
59
  function _maxMerge(a, b) {
@@ -56,12 +56,12 @@
56
56
  * Per-column field-level encryption with AAD-bound envelopes.
57
57
  */
58
58
  var lazyRequire = require("./lazy-require");
59
+ var boundedMap = require("./bounded-map");
59
60
  var vault = require("./vault");
60
61
  var vaultAad = require("./vault-aad");
61
62
  var validateOpts = require("./validate-opts");
62
63
  var numericBounds = require("./numeric-bounds");
63
64
  var safeJson = require("./safe-json");
64
- var frameworkSchema = require("./framework-schema");
65
65
  var sql = require("./sql");
66
66
  var { defineClass } = require("./framework-error");
67
67
  var { sha3Hash, kdf, generateBytes, encryptPacked, decryptPacked, generateToken } = require("./crypto");
@@ -225,14 +225,19 @@ var PER_ROW_KEYS_TABLE = "_blamejs_per_row_keys"; // allow:hand-rolled-sql
225
225
  var PER_ROW_KEYS_COLUMN = "wrappedKey";
226
226
  var PER_ROW_KEYS_SCHEMA_VERSION = "1";
227
227
 
228
- // The per-row-key registry is read/written against the LOCAL db() / dbHandle
229
- // handle directly (not clusterStorage), so SQL composed for it uses the
230
- // RESOLVED name (prefix-aware via frameworkSchema.tableName) and quoteName so
231
- // b.sql emits the quoted identifier the single-node path expects the same
232
- // shape db-query.js's _sqlOpts and db.js's own local-handle b.sql calls use.
228
+ // The per-row-key registry ALWAYS lives in the LOCAL sqlite reconcile creates
229
+ // it under its RAW schema name, and per-row keys are never dispatched to an
230
+ // external backend (see _kRowOnce's db() fallback). It is read/written against
231
+ // the local db() / dbHandle directly, NOT through clusterStorage, so it must use
232
+ // the RAW table name. frameworkSchema.tableName() resolves to the cluster-mode
233
+ // EXTERNAL prefixed name (the prefix applies only to the external backend, via
234
+ // resolveTables — local DDL stays raw); under a custom tablePrefix that names a
235
+ // table that does not exist locally, which SILENTLY breaks crypto-shred:
236
+ // destroyPerRowKey deletes 0 rows and sealed cells stay decryptable after
237
+ // eraseHard. quoteName so b.sql emits the quoted identifier the local path expects.
233
238
  var _PER_ROW_SQL_OPTS = { dialect: "sqlite", quoteName: true };
234
239
  function _perRowKeysTableName() {
235
- return frameworkSchema.tableName(PER_ROW_KEYS_TABLE);
240
+ return PER_ROW_KEYS_TABLE;
236
241
  }
237
242
 
238
243
  // Build the canonical AAD parts for a row-secret wrap in
@@ -568,14 +573,9 @@ function computeNamespacedHash(ns, value, opts) {
568
573
  "(default) or 'hmac-shake256', got " + JSON.stringify(mode));
569
574
  }
570
575
  var truncateBytes = opts.truncateBytes;
571
- if (truncateBytes !== undefined) {
572
- if (typeof truncateBytes !== "number" || !isFinite(truncateBytes) ||
573
- truncateBytes <= 0 || Math.floor(truncateBytes) !== truncateBytes) {
574
- throw new CryptoFieldError("crypto-field/bad-truncate-bytes",
575
- "computeNamespacedHash: opts.truncateBytes must be a " +
576
- "positive integer (bytes), got " + JSON.stringify(truncateBytes));
577
- }
578
- }
576
+ numericBounds.requirePositiveFiniteIntIfPresent(truncateBytes,
577
+ "computeNamespacedHash: opts.truncateBytes", CryptoFieldError,
578
+ "crypto-field/bad-truncate-bytes");
579
579
  var hex = _computeDerivedHash({ mode: mode }, mode, ns, String(value));
580
580
  if (truncateBytes !== undefined) {
581
581
  return hex.slice(0, truncateBytes * 2);
@@ -918,8 +918,7 @@ function _rateNoteFailure(actor, table, column) {
918
918
  if (!_rateCap) return false;
919
919
  var nowMs = _rateCap.now();
920
920
  var key = _rateKey(actor, table, column);
921
- var arr = _rateFailWindows.get(key);
922
- if (!arr) { arr = []; _rateFailWindows.set(key, arr); }
921
+ var arr = boundedMap.getOrInsert(_rateFailWindows, key, function () { return []; });
923
922
  // Prune entries older than the window (sliding-window via timestamp
924
923
  // array — same shape as b.mail.server.rateLimit._pruneWindow).
925
924
  var cutoff = nowMs - _rateCap.windowMs;
@@ -1276,19 +1275,19 @@ function unsealRow(table, row, actor, dbHandle) {
1276
1275
  unsealed = out[field];
1277
1276
  }
1278
1277
  } catch (e) {
1279
- // A DB-write attacker who can write `vault:<crafted>` /
1280
- // `vault.aad:<crafted>` payloads to sealed columns can force
1281
- // KEM decapsulation / AEAD verify on attacker-controlled
1282
- // bytes via this read path. Surface the failure as a chain
1283
- // row so operators alert on burst patterns; null the field
1284
- // so downstream code sees "no value" instead of crashing the
1285
- // request. AAD-shape failures additionally indicate cross-
1286
- // row copy attempts the audit metadata flags the shape so
1287
- // operators can write alert rules.
1278
+ // A crypto-shredded (or never-materialized) per-row key is an EXPECTED
1279
+ // absence, not a decryption-oracle attack: the wrapped secret is gone,
1280
+ // so there is no oracle to brute-force. Reading such a row must read as
1281
+ // "no value" WITHOUT counting against the rate cap — otherwise a bulk
1282
+ // read over a table with many erased rows (GDPR eraseHard) trips the
1283
+ // cap and DoS's the live rows (self-DoS, CWE-307 mis-applied). It is
1284
+ // audited under a distinct, non-failure action so operators don't alert
1285
+ // on routine post-erasure reads as forged-ciphertext bursts.
1286
+ var _shredded = e && e.code === "crypto-field/row-key-unavailable";
1288
1287
  try {
1289
1288
  audit().safeEmit({
1290
- action: "system.crypto.unseal_failed",
1291
- outcome: "failure",
1289
+ action: _shredded ? "system.crypto.shredded_read" : "system.crypto.unseal_failed",
1290
+ outcome: _shredded ? "success" : "failure",
1292
1291
  metadata: {
1293
1292
  table: table,
1294
1293
  field: field,
@@ -1298,11 +1297,13 @@ function unsealRow(table, row, actor, dbHandle) {
1298
1297
  },
1299
1298
  });
1300
1299
  } catch (_e) { /* drop-silent */ }
1301
- // Default-on rate cap: account this failure against the (actor,
1302
- // table, column) tuple. When it trips the threshold, arm the
1303
- // cooldown + emit the distinct rate-exceeded audit once on the
1304
- // transition. No-op when the cap is disabled.
1305
- if (_rateNoteFailure(capActor, table, field)) {
1300
+ // Default-on rate cap: account a genuine decryption / AEAD-verify /
1301
+ // AAD-downgrade failure (a possible forged-ciphertext attack) against
1302
+ // the (actor, table, column) tuple. A shredded-key read is exempt (see
1303
+ // above). When the cap trips the threshold, arm the cooldown + emit the
1304
+ // distinct rate-exceeded audit once on the transition. No-op when the
1305
+ // cap is disabled.
1306
+ if (!_shredded && _rateNoteFailure(capActor, table, field)) {
1306
1307
  _emitRateAudit({
1307
1308
  table: table, field: field, actor: capActor, shape: shape,
1308
1309
  threshold: _rateCap.threshold, windowMs: _rateCap.windowMs, cooldownMs: _rateCap.cooldownMs,
package/lib/crypto.js CHANGED
@@ -49,6 +49,7 @@ var nodeFs = require("node:fs");
49
49
  var { pipeline } = require("node:stream/promises");
50
50
  var { xchacha20poly1305 } = require("./vendor/noble-ciphers.cjs");
51
51
  var C = require("./constants");
52
+ var numericBounds = require("./numeric-bounds");
52
53
  // Circular: audit imports b.crypto for sha3Hash + envelope sign. Lazy-
53
54
  // load the audit module so the legacy-envelope decrypt path can emit
54
55
  // `system.crypto.decrypt.allow_legacy` events without an inline
@@ -337,8 +338,7 @@ function hashFilesParallel(filePaths, opts) {
337
338
  // override per-call.
338
339
  var maxBytesPerFile = opts.maxBytesPerFile !== undefined
339
340
  ? opts.maxBytesPerFile : C.BYTES.gib(1);
340
- if (typeof maxBytesPerFile !== "number" || !isFinite(maxBytesPerFile) ||
341
- maxBytesPerFile <= 0 || Math.floor(maxBytesPerFile) !== maxBytesPerFile) {
341
+ if (!numericBounds.isPositiveFiniteInt(maxBytesPerFile)) {
342
342
  return Promise.reject(new TypeError(
343
343
  "crypto.hashFilesParallel: opts.maxBytesPerFile must be a positive integer, got " + maxBytesPerFile
344
344
  ));
@@ -847,6 +847,74 @@ function fromBase64Url(s, opts) {
847
847
  return Buffer.from(s, "base64url");
848
848
  }
849
849
 
850
+ /**
851
+ * @primitive b.crypto.makeBase64UrlDecoder
852
+ * @signature b.crypto.makeBase64UrlDecoder(opts)
853
+ * @since 0.15.13
854
+ * @status stable
855
+ * @related b.crypto.fromBase64Url, b.crypto.toBase64Url
856
+ *
857
+ * Bind the strict `fromBase64Url` decoder to one module's error contract,
858
+ * returning a `decode(s)` that translates the decoder's `TypeError` (bad
859
+ * type or non-canonical input) into the caller's typed error. Every
860
+ * base64url-carrying surface — JWT segments, DPoP / OAuth tokens, status
861
+ * lists, pagination cursors — wrapped `fromBase64Url` in the same
862
+ * `if (typeof s !== "string") throw new XError(...); try { return
863
+ * fromBase64Url(s); } catch { throw new XError(...); }` shape, differing
864
+ * only in error class, code, and the two messages; this owns that binding.
865
+ *
866
+ * `opts.badMessage` is thrown on any decode failure (non-canonical /
867
+ * malformed input — the strict decoder rejects the CWE-347 signature-
868
+ * canonicalization footgun). `opts.typeMessage` is optional: when set, a
869
+ * non-string input throws it directly (the common case names the field —
870
+ * "cursor must be a string"); when omitted, a non-string falls through to
871
+ * `badMessage` like any other decode failure.
872
+ *
873
+ * @opts
874
+ * errorClass: Function, // required — (code, message) error constructor
875
+ * code: string, // required — error code for both throws
876
+ * badMessage: string, // required — message on a decode failure
877
+ * typeMessage: string, // optional — message on a non-string input
878
+ *
879
+ * @example
880
+ * var b = require("blamejs");
881
+ *
882
+ * function JwtError(code, message) { this.code = code; this.message = message; }
883
+ * var decodeSeg = b.crypto.makeBase64UrlDecoder({
884
+ * errorClass: JwtError,
885
+ * code: "jwt/malformed",
886
+ * typeMessage: "expected base64url string",
887
+ * badMessage: "JWT segment is not valid base64url",
888
+ * });
889
+ * decodeSeg("aGVsbG8"); // → <Buffer 68 65 6c 6c 6f>
890
+ * // decodeSeg("!!!") throws JwtError("jwt/malformed", "JWT segment is not valid base64url")
891
+ */
892
+ function makeBase64UrlDecoder(opts) {
893
+ if (!opts || typeof opts !== "object") {
894
+ throw new TypeError("crypto.makeBase64UrlDecoder: opts is required");
895
+ }
896
+ if (typeof opts.errorClass !== "function") {
897
+ throw new TypeError("crypto.makeBase64UrlDecoder: opts.errorClass must be a constructor");
898
+ }
899
+ if (typeof opts.badMessage !== "string") {
900
+ throw new TypeError("crypto.makeBase64UrlDecoder: opts.badMessage must be a string");
901
+ }
902
+ var ErrorClass = opts.errorClass;
903
+ var code = opts.code;
904
+ var typeMessage = opts.typeMessage;
905
+ var badMessage = opts.badMessage;
906
+ return function decode(s) {
907
+ if (typeMessage !== undefined && typeof s !== "string") {
908
+ throw new ErrorClass(code, typeMessage);
909
+ }
910
+ try {
911
+ return fromBase64Url(s);
912
+ } catch (_e) {
913
+ throw new ErrorClass(code, badMessage);
914
+ }
915
+ };
916
+ }
917
+
850
918
  // ---- Subresource Integrity (W3C SRI 1.0) ----
851
919
  //
852
920
  // b.crypto.sri(content, { algorithm? }) — returns a `sha###-base64`
@@ -895,7 +963,7 @@ var SRI_ALGORITHMS = { "sha256": "sha256", "sha384": "sha384", "sha512": "sha512
895
963
  function sri(content, opts) {
896
964
  opts = opts || {};
897
965
  var algorithm = (opts.algorithm || "sha384").toLowerCase();
898
- if (!SRI_ALGORITHMS[algorithm]) {
966
+ if (!Object.prototype.hasOwnProperty.call(SRI_ALGORITHMS, algorithm)) {
899
967
  throw new Error("crypto.sri: unsupported algorithm '" + algorithm +
900
968
  "' (W3C SRI 1.0 §3.2 supports sha256/sha384/sha512)");
901
969
  }
@@ -1024,6 +1092,53 @@ function verify(data, signature, publicKeyPem) {
1024
1092
  return nodeCrypto.verify(null, Buffer.from(data), publicKeyPem, signature);
1025
1093
  }
1026
1094
 
1095
+ /**
1096
+ * @primitive b.crypto.importPublicJwk
1097
+ * @signature b.crypto.importPublicJwk(jwk, opts?)
1098
+ * @since 0.15.13
1099
+ * @status stable
1100
+ * @related b.crypto.verify
1101
+ *
1102
+ * Import a JWK as a public `KeyObject` via
1103
+ * `crypto.createPublicKey({ key, format: "jwk" })`, translating the Node
1104
+ * import failure into a caller-supplied typed error. Untrusted JWKs reach
1105
+ * this from DID documents (`publicKeyJwk`), DNSKEY records, and COSE_Key
1106
+ * structures — each module validates the `kty` / `crv` it accepts BEFORE
1107
+ * calling, then hands the assembled JWK here for the final import + error
1108
+ * translation that was otherwise hand-rolled identically in every one.
1109
+ * Centralising it gives one place to harden untrusted-JWK import.
1110
+ *
1111
+ * On failure, when `opts.errorClass` is supplied the thrown error is
1112
+ * `new opts.errorClass(opts.code, opts.messagePrefix + nodeFailureDetail)`
1113
+ * so the operator sees the module's own code + a message naming the
1114
+ * source; without it the original Node error propagates unchanged.
1115
+ *
1116
+ * @opts
1117
+ * errorClass: Function, // (code, message) error constructor
1118
+ * code: string, // error code passed to errorClass
1119
+ * messagePrefix: string, // text before the Node failure detail. default: ""
1120
+ *
1121
+ * @example
1122
+ * var b = require("blamejs");
1123
+ *
1124
+ * function KeyError(code, message) { this.code = code; this.message = message; }
1125
+ * var key = b.crypto.importPublicJwk(
1126
+ * { kty: "OKP", crv: "Ed25519", x: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" },
1127
+ * { errorClass: KeyError, code: "bad-key", messagePrefix: "bad key: " });
1128
+ * // → <KeyObject> (or throws KeyError("bad-key", "bad key: <detail>"))
1129
+ */
1130
+ function importPublicJwk(jwk, opts) {
1131
+ opts = opts || {};
1132
+ try {
1133
+ return nodeCrypto.createPublicKey({ key: jwk, format: "jwk" });
1134
+ } catch (e) {
1135
+ if (typeof opts.errorClass === "function") {
1136
+ throw new opts.errorClass(opts.code, (opts.messagePrefix || "") + ((e && e.message) || e));
1137
+ }
1138
+ throw e;
1139
+ }
1140
+ }
1141
+
1027
1142
  // Track whether the hybrid-disabled audit has been emitted at least
1028
1143
  // once per process, so a high-volume KEM-only deployment doesn't peg
1029
1144
  // the audit bus with one event per encrypt() call. Operators who want
@@ -1241,7 +1356,21 @@ function decrypt(ciphertext, privateKeys, opts) {
1241
1356
  opts && opts.raw === true ? { raw: true } : undefined);
1242
1357
  }
1243
1358
 
1359
+ // Bounds-checked 2-byte length read — a bare _envU16(packed, pos) on a
1360
+ // truncated envelope throws a raw Node RangeError that escapes the documented
1361
+ // "Invalid envelope: ..." error contract (untrusted ciphertext must fail with
1362
+ // the typed envelope error, not a stack-trace-leaking RangeError).
1363
+ function _envU16(buf, at) {
1364
+ if (at + 2 > buf.length) {
1365
+ throw new Error("Invalid envelope: truncated (expected a 2-byte length at offset " + at + ")");
1366
+ }
1367
+ return buf.readUInt16BE(at);
1368
+ }
1369
+
1244
1370
  function decryptEnvelope(packed, privateKeys, internalOpts) {
1371
+ if (!Buffer.isBuffer(packed) || packed.length < 4) {
1372
+ throw new Error("Invalid envelope: too short (need at least the 4-byte suite header)");
1373
+ }
1245
1374
  var kemId = packed[1], cipherId = packed[2], kdfId = packed[3], pos = 4;
1246
1375
 
1247
1376
  // The legacy 0xE1 envelope predates the FixedInfo / suite-binding
@@ -1257,7 +1386,7 @@ function decryptEnvelope(packed, privateKeys, internalOpts) {
1257
1386
  throw new Error("Invalid envelope: unsupported KDF (only SHAKE256 supported)");
1258
1387
  }
1259
1388
 
1260
- var kemCtLen = packed.readUInt16BE(pos); pos += 2;
1389
+ var kemCtLen = _envU16(packed, pos); pos += 2;
1261
1390
  var kemCt = packed.subarray(pos, pos + kemCtLen); pos += kemCtLen;
1262
1391
 
1263
1392
  var mlkemPriv = nodeCrypto.createPrivateKey(
@@ -1268,7 +1397,7 @@ function decryptEnvelope(packed, privateKeys, internalOpts) {
1268
1397
  var fixedInfo = omitFixedInfo ? Buffer.alloc(0) : _suiteFixedInfo(kemId, cipherId, kdfId);
1269
1398
 
1270
1399
  if (kemId === C.KEM_IDS.ML_KEM_1024_P384) {
1271
- var ecEphLen = packed.readUInt16BE(pos); pos += 2;
1400
+ var ecEphLen = _envU16(packed, pos); pos += 2;
1272
1401
  var ecEphDer = packed.subarray(pos, pos + ecEphLen); pos += ecEphLen;
1273
1402
  var ecPrivPem = typeof privateKeys === "string" ? null : privateKeys.ecPrivateKey;
1274
1403
  if (!ecPrivPem) throw new Error("Hybrid KEM requires EC private key");
@@ -1285,7 +1414,7 @@ function decryptEnvelope(packed, privateKeys, internalOpts) {
1285
1414
  // the correct keypair via privateKeys when the envelope was sealed
1286
1415
  // with this algorithm. Same length-prefixed shape as the P-384
1287
1416
  // hybrid: 2-byte ec-eph-len + DER X25519 pubkey + nonce + ct.
1288
- var x25519EphLen = packed.readUInt16BE(pos); pos += 2;
1417
+ var x25519EphLen = _envU16(packed, pos); pos += 2;
1289
1418
  var x25519EphDer = packed.subarray(pos, pos + x25519EphLen); pos += x25519EphLen;
1290
1419
  var x25519PrivPem = typeof privateKeys === "string" ? null : privateKeys.x25519PrivateKey;
1291
1420
  if (!x25519PrivPem) throw new Error("ML-KEM-768 + X25519 hybrid envelope requires x25519PrivateKey");
@@ -1768,7 +1897,7 @@ function _pemToDer(pemOrDer) {
1768
1897
  // for mTLS bootstrap / webhook verification / peer-cert pinning.
1769
1898
  // 64 KiB caps the largest plausible PEM (a P-384 cert + chain) at
1770
1899
  // ~3× margin while refusing pathological inputs outright.
1771
- if (pemOrDer.length > C.BYTES.kib(64)) {
1900
+ if (safeBuffer.byteLengthOf(pemOrDer) > C.BYTES.kib(64)) {
1772
1901
  throw new TypeError(
1773
1902
  "crypto.hashCertFingerprint: PEM input exceeds 64 KiB (" +
1774
1903
  pemOrDer.length + " bytes); refuse oversized input to avoid " +
@@ -2022,6 +2151,8 @@ module.exports = {
2022
2151
  // Signatures
2023
2152
  sign: sign,
2024
2153
  verify: verify,
2154
+ importPublicJwk: importPublicJwk,
2155
+ makeBase64UrlDecoder: makeBase64UrlDecoder,
2025
2156
  // Envelope encrypt/decrypt
2026
2157
  encrypt: encrypt,
2027
2158
  decrypt: decrypt,