@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/mcp.js CHANGED
@@ -152,7 +152,7 @@ function refuse(res, code, message, id) {
152
152
  function _readBearer(req) {
153
153
  var h = req.headers && req.headers.authorization;
154
154
  if (typeof h !== "string") return null;
155
- if (h.length > C.BYTES.kib(8)) return null;
155
+ if (safeBuffer.byteLengthOf(h) > C.BYTES.kib(8)) return null;
156
156
  var m = /^Bearer\s+([A-Za-z0-9._~+/=-]+)$/.exec(h.trim());
157
157
  return m ? m[1] : null;
158
158
  }
@@ -636,10 +636,12 @@ function _validateValueAgainstSchema(value, schema, path) {
636
636
  }
637
637
  if (typeof schema.pattern === "string") {
638
638
  // Schema-supplied pattern — operator-controlled at registration
639
- // time, not request-controlled. Cap value length first per the
640
- // codebase-patterns regex-bound rule so a 10MB string doesn't
641
- // ReDoS the validator.
642
- if (value.length > 4096) return path + ": value exceeds 4 KiB cap before regex test"; // 4 KiB regex-input cap
639
+ // time, not request-controlled. Cap the input LENGTH first per the
640
+ // codebase-patterns regex-bound rule so a huge string can't ReDoS the
641
+ // validator. This is a CHARACTER cap, not a byte cap: regex matching
642
+ // cost scales with the number of code units the engine scans, so 4096
643
+ // chars is the correct ReDoS bound regardless of UTF-8 byte size.
644
+ if (value.length > 4096) return path + ": value exceeds 4096-char cap before regex test"; // ReDoS char cap (not bytes)
643
645
  try {
644
646
  var pat = new RegExp(schema.pattern); // allow:dynamic-regex — schema.pattern from registered tool author, not request input; bounded above
645
647
  if (!pat.test(value)) return path + ": does not match pattern";
package/lib/mdoc.js CHANGED
@@ -53,6 +53,8 @@ var C = require("./constants");
53
53
  var cbor = require("./cbor");
54
54
  var cose = require("./cose");
55
55
  var bCrypto = require("./crypto");
56
+ var x509Chain = require("./x509-chain");
57
+ var safeBuffer = require("./safe-buffer");
56
58
  var validateOpts = require("./validate-opts");
57
59
  var { defineClass } = require("./framework-error");
58
60
 
@@ -65,11 +67,14 @@ var TAG_ENCODED_CBOR = 24; // RFC 8949 §3.4.5.1 emb
65
67
  var ALLOWED_TAGS = [0, 1, TAG_ENCODED_CBOR, 1004];
66
68
  var DIGEST_ALGS = { "SHA-256": "sha256", "SHA-384": "sha384", "SHA-512": "sha512" };
67
69
 
68
- function _bytes(x, what) {
69
- if (Buffer.isBuffer(x)) return x;
70
- if (x instanceof Uint8Array) return Buffer.from(x);
71
- throw new MdocError("mdoc/bad-input", "mdoc: " + what + " must be a Buffer / Uint8Array of CBOR");
72
- }
70
+ // mdoc inputs are CBOR byte strings, never text (allowString:false).
71
+ var _bytes = safeBuffer.makeByteCoercer({
72
+ errorClass: MdocError,
73
+ typeCode: "mdoc/bad-input",
74
+ messagePrefix: "mdoc: ",
75
+ messageSuffix: " must be a Buffer / Uint8Array of CBOR",
76
+ allowString: false,
77
+ });
73
78
 
74
79
  // validityInfo dates are tdate (Tag 0, an RFC 3339 string) or epoch
75
80
  // (Tag 1). Returns epoch-ms; fails closed on a malformed value.
@@ -124,17 +129,28 @@ function _mapGet(m, k) { return m instanceof Map ? m.get(k) : (m ? m[k] : undefi
124
129
  */
125
130
  async function verifyIssuerSigned(issuerSigned, opts) {
126
131
  validateOpts.requireObject(opts, "mdoc.verifyIssuerSigned", MdocError);
127
- validateOpts(opts, ["algorithms", "trustAnchorsPem", "expectedDocType", "at", "maxBytes", "maxDepth"], "mdoc.verifyIssuerSigned");
132
+ validateOpts(opts, ["algorithms", "trustAnchorsPem", "allowUntrustedIssuer", "expectedDocType", "at", "maxBytes", "maxDepth"], "mdoc.verifyIssuerSigned");
128
133
  if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
129
134
  throw new MdocError("mdoc/algorithms-required", "mdoc.verifyIssuerSigned: opts.algorithms is required");
130
135
  }
131
- var at = new Date();
132
- if (opts.at !== undefined && opts.at !== null) {
133
- if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
134
- throw new MdocError("mdoc/bad-at", "mdoc.verifyIssuerSigned: opts.at must be a valid Date");
135
- }
136
- at = opts.at;
136
+ // Issuer authentication is the WHOLE point of verifyIssuerSigned. The signer
137
+ // cert rides in the attacker-supplied x5chain, so verifying the COSE_Sign1
138
+ // against that embedded key only proves the MSO is self-consistent — a forged
139
+ // mDL (attacker keypair, self-signed leaf, self-signed MSO) passes unless the
140
+ // chain is anchored to a configured trust root. Require trustAnchorsPem by
141
+ // default; an operator who genuinely wants trust-anchor-free verification must
142
+ // opt in EXPLICITLY (and gets issuerTrusted:false on the result so the
143
+ // unauthenticated posture is visible). Fail closed — never silently accept an
144
+ // unanchored issuer.
145
+ var hasAnchors = opts.trustAnchorsPem !== undefined && opts.trustAnchorsPem !== null;
146
+ if (!hasAnchors && opts.allowUntrustedIssuer !== true) {
147
+ throw new MdocError("mdoc/trust-anchors-required",
148
+ "mdoc.verifyIssuerSigned: opts.trustAnchorsPem is required to authenticate the issuer " +
149
+ "(the x5chain signer cert is attacker-controlled); pass trustAnchorsPem, or set " +
150
+ "allowUntrustedIssuer:true to explicitly accept an unauthenticated issuer");
137
151
  }
152
+ validateOpts.optionalDate(opts.at, "mdoc.verifyIssuerSigned: opts.at", MdocError, "mdoc/bad-at");
153
+ var at = (opts.at !== undefined && opts.at !== null) ? opts.at : new Date();
138
154
  var decodeOpts = { allowedTags: ALLOWED_TAGS, maxBytes: opts.maxBytes, maxDepth: opts.maxDepth };
139
155
 
140
156
  var top = cbor.decode(_bytes(issuerSigned, "issuerSigned"), decodeOpts);
@@ -265,6 +281,10 @@ async function verifyIssuerSigned(issuerSigned, opts) {
265
281
  deviceKey: deviceKey,
266
282
  signerCert: signerCert.toString(),
267
283
  alg: verified.alg,
284
+ // true only when the signer chain was validated against a configured trust
285
+ // anchor; false on the explicit allowUntrustedIssuer opt-out so a caller
286
+ // can tell an authenticated issuer from a merely self-consistent MSO.
287
+ issuerTrusted: hasAnchors,
268
288
  };
269
289
  }
270
290
 
@@ -410,8 +430,10 @@ function _verifyChain(chainDer, anchorsPem, at) {
410
430
  throw new MdocError("mdoc/chain-loop", "mdoc.verifyIssuerSigned: certificate chain did not terminate");
411
431
  }
412
432
  function _issued(issuer, subject) {
413
- try { return subject.checkIssued(issuer) && subject.verify(issuer.publicKey); }
414
- catch (_e) { return false; }
433
+ // Enforces basicConstraints cA:TRUE on the issuer alongside the
434
+ // checkIssued + signature linkage — an IACA/DS chain cannot accept a
435
+ // non-CA cert as an issuer (basicConstraints bypass, CVE-2002-0862 class).
436
+ return x509Chain.issuerValidlyIssued(issuer, subject);
415
437
  }
416
438
  function _assertValidAt(cert, atMs) {
417
439
  if (atMs < cert.validFromDate.getTime() || atMs > cert.validToDate.getTime()) {
package/lib/metrics.js CHANGED
@@ -39,7 +39,6 @@
39
39
 
40
40
  var C = require("./constants");
41
41
  var canonicalJson = require("./canonical-json");
42
- var nodeFs = require("node:fs");
43
42
  var atomicFile = require("./atomic-file");
44
43
  var safeJson = require("./safe-json");
45
44
  var { defineClass } = require("./framework-error");
@@ -48,6 +47,7 @@ var numericBounds = require("./numeric-bounds");
48
47
  var requestHelpers = require("./request-helpers");
49
48
  var { resolveRoute, captureResponseStatus, HTTP_STATUS } = requestHelpers;
50
49
  var validateOpts = require("./validate-opts");
50
+ var boundedMap = require("./bounded-map");
51
51
 
52
52
  var MetricsError = defineClass("MetricsError", { alwaysPermanent: true });
53
53
  var log = boot("metrics");
@@ -353,10 +353,10 @@ function create(opts) {
353
353
  }
354
354
 
355
355
  function _registerMetric(metric) {
356
- if (metrics.has(metric.name)) {
356
+ boundedMap.requireAbsent(metrics, metric.name, function () {
357
357
  throw new MetricsError("metrics/duplicate",
358
358
  "metric '" + metric.name + "' already registered");
359
- }
359
+ });
360
360
  metrics.set(metric.name, metric);
361
361
  return metric;
362
362
  }
@@ -388,19 +388,20 @@ function create(opts) {
388
388
  }
389
389
  var resolved = _resolveLabels(defaultLabels, labelNames, arg.labels);
390
390
  var key = _labelsKey(resolved);
391
- var entry = values.get(key);
392
- if (!entry) {
393
- if (values.size >= cardinalityCap) {
391
+ var entry = boundedMap.getOrInsert(values, key, function () {
392
+ return { labels: resolved, value: 0 };
393
+ }, {
394
+ maxSize: cardinalityCap,
395
+ onFull: function () {
394
396
  if (!capWarned) {
395
397
  log("metric '" + fullName + "' hit labelCardinalityCap (" + cardinalityCap +
396
398
  ") — dropping new label combinations. Reduce label cardinality or raise the cap.");
397
399
  capWarned = true;
398
400
  }
399
- return;
400
- }
401
- entry = { labels: resolved, value: 0 };
402
- values.set(key, entry);
403
- }
401
+ return null;
402
+ },
403
+ });
404
+ if (!entry) return;
404
405
  entry.value += arg.value;
405
406
  },
406
407
  reset: function () { values.clear(); capWarned = false; },
@@ -428,19 +429,18 @@ function create(opts) {
428
429
  function _ensure(callLabels) {
429
430
  var resolved = _resolveLabels(defaultLabels, labelNames, callLabels);
430
431
  var key = _labelsKey(resolved);
431
- var entry = values.get(key);
432
- if (!entry) {
433
- if (values.size >= cardinalityCap) {
432
+ return boundedMap.getOrInsert(values, key, function () {
433
+ return { labels: resolved, value: 0 };
434
+ }, {
435
+ maxSize: cardinalityCap,
436
+ onFull: function () {
434
437
  if (!capWarned) {
435
438
  log("metric '" + fullName + "' hit labelCardinalityCap (" + cardinalityCap + ")");
436
439
  capWarned = true;
437
440
  }
438
441
  return null;
439
- }
440
- entry = { labels: resolved, value: 0 };
441
- values.set(key, entry);
442
- }
443
- return entry;
442
+ },
443
+ });
444
444
  }
445
445
 
446
446
  var instance = {
@@ -524,25 +524,26 @@ function create(opts) {
524
524
  }
525
525
  var resolved = _resolveLabels(defaultLabels, labelNames, arg.labels);
526
526
  var key = _labelsKey(resolved);
527
- var entry = values.get(key);
528
- if (!entry) {
529
- if (values.size >= cardinalityCap) {
530
- if (!capWarned) {
531
- log("metric '" + fullName + "' hit labelCardinalityCap (" + cardinalityCap + ")");
532
- capWarned = true;
533
- }
534
- return;
535
- }
527
+ var entry = boundedMap.getOrInsert(values, key, function () {
536
528
  // counts[i] is the count for the [<=buckets[i]] bucket; counts[buckets.length] is +Inf.
537
- entry = {
529
+ return {
538
530
  labels: resolved,
539
531
  counts: new Array(buckets.length + 1).fill(0),
540
532
  sum: 0,
541
533
  count: 0,
542
534
  exemplars: new Array(buckets.length + 1).fill(null),
543
535
  };
544
- values.set(key, entry);
545
- }
536
+ }, {
537
+ maxSize: cardinalityCap,
538
+ onFull: function () {
539
+ if (!capWarned) {
540
+ log("metric '" + fullName + "' hit labelCardinalityCap (" + cardinalityCap + ")");
541
+ capWarned = true;
542
+ }
543
+ return null;
544
+ },
545
+ });
546
+ if (!entry) return;
546
547
  for (var i = 0; i < buckets.length; i++) {
547
548
  if (arg.value <= buckets[i]) {
548
549
  entry.counts[i]++;
@@ -1065,8 +1066,20 @@ function snapshotRead(p) {
1065
1066
  MetricsError, "metrics-snapshot/bad-path");
1066
1067
  var raw;
1067
1068
  try {
1068
- raw = nodeFs.readFileSync(p, "utf8");
1069
+ // Capped fd-bound read: the snapshot is parsed AFTER read, so the cap must
1070
+ // precede the alloc — a hostile multi-GB file at the snapshot path would
1071
+ // otherwise OOM the reader before safeJson's 4 MiB parse cap is consulted.
1072
+ raw = atomicFile.fdSafeReadSync(p, {
1073
+ maxBytes: C.BYTES.mib(4), encoding: "utf8", refuseSymlink: true,
1074
+ errorFor: function (kind, detail) {
1075
+ if (kind === "enoent") return new MetricsError("metrics-snapshot/not-found", "metrics.snapshot.read: " + p + " — not found");
1076
+ if (kind === "too-large") return new MetricsError("metrics-snapshot/too-large", "metrics.snapshot.read: " + p + " exceeds " + detail.max + " bytes");
1077
+ if (kind === "symlink") return new MetricsError("metrics-snapshot/symlink-refused", "metrics.snapshot.read: " + p + " is a symlink (refused)");
1078
+ return undefined;
1079
+ },
1080
+ });
1069
1081
  } catch (e) {
1082
+ if (e instanceof MetricsError) throw e;
1070
1083
  throw new MetricsError("metrics-snapshot/not-found",
1071
1084
  "metrics.snapshot.read: " + p + " — " + (e && e.message ? e.message : String(e)));
1072
1085
  }
@@ -1327,7 +1340,7 @@ function shadowRegistry(opts) {
1327
1340
  var gaugeSet = _shadowSetOf(opts.gauges, "gauges");
1328
1341
  var infoSet = _shadowSetOf(opts.info, "info");
1329
1342
  var cap = opts.cardinalityCap === undefined ? SHADOW_DEFAULT_CARDINALITY : opts.cardinalityCap;
1330
- if (typeof cap !== "number" || !isFinite(cap) || cap < 1 || Math.floor(cap) !== cap) {
1343
+ if (!numericBounds.isPositiveFiniteInt(cap)) {
1331
1344
  throw new MetricsError("metrics-shadow/bad-cap",
1332
1345
  "shadowRegistry: cardinalityCap must be a positive integer");
1333
1346
  }
@@ -38,6 +38,7 @@
38
38
  var defineClass = require("../framework-error").defineClass;
39
39
  var lazyRequire = require("../lazy-require");
40
40
  var validateOpts = require("../validate-opts");
41
+ var requestHelpers = require("../request-helpers");
41
42
  var denyResponse = require("./deny-response").denyResponse;
42
43
 
43
44
  var audit = lazyRequire(function () { return require("../audit"); });
@@ -106,7 +107,6 @@ function create(opts) {
106
107
  ? opts.consentRequired : null;
107
108
  var hasParentalConsent = typeof opts.hasParentalConsent === "function" ? opts.hasParentalConsent : null;
108
109
  var skipPaths = Array.isArray(opts.skipPaths) ? opts.skipPaths.slice() : [];
109
- var auditOn = opts.audit !== false;
110
110
  var errorMessage = typeof opts.errorMessage === "string" && opts.errorMessage.length > 0
111
111
  ? opts.errorMessage : "service unavailable without parental consent";
112
112
  var onDeny = typeof opts.onDeny === "function" ? opts.onDeny : null;
@@ -123,29 +123,9 @@ function create(opts) {
123
123
  privacyPostureHeader = "X-Privacy-Posture";
124
124
  }
125
125
 
126
- function _shouldSkip(req) {
127
- if (skipPaths.length === 0) return false;
128
- var p = req.url || "";
129
- var qpos = p.indexOf("?");
130
- if (qpos !== -1) p = p.slice(0, qpos);
131
- for (var i = 0; i < skipPaths.length; i++) {
132
- var s = skipPaths[i];
133
- if (typeof s === "string" && (p === s || p.indexOf(s + "/") === 0)) return true;
134
- if (s instanceof RegExp && s.test(p)) return true;
135
- }
136
- return false;
137
- }
126
+ var _shouldSkip = requestHelpers.makeSkipMatcher({ skipPaths: skipPaths }, "middleware.ageGate");
138
127
 
139
- function _emitAudit(action, outcome, metadata) {
140
- if (!auditOn) return;
141
- try {
142
- audit().safeEmit({
143
- action: "middleware.age_gate." + action,
144
- outcome: outcome,
145
- metadata: metadata || {},
146
- });
147
- } catch (_e) { /* drop-silent */ }
148
- }
128
+ var _emitAudit = audit().namespaced("middleware.age_gate", opts.audit);
149
129
 
150
130
  return function ageGateMiddleware(req, res, next) {
151
131
  if (_shouldSkip(req)) return next();
@@ -98,8 +98,9 @@
98
98
 
99
99
  var bCrypto = require("../crypto");
100
100
  var C = require("../constants");
101
+ var numericBounds = require("../numeric-bounds");
101
102
  var lazyRequire = require("../lazy-require");
102
- var nonceStoreLib = require("../nonce-store");
103
+ var nonceStore = require("../nonce-store");
103
104
  var requestHelpers = require("../request-helpers");
104
105
  var safeJson = require("../safe-json");
105
106
  var validateOpts = require("../validate-opts");
@@ -313,7 +314,7 @@ function create(opts) {
313
314
  "apiEncrypt: pruneIntervalMs", ApiEncryptError, "BAD_OPT");
314
315
  var pruneIntervalMs = opts.pruneIntervalMs != null
315
316
  ? opts.pruneIntervalMs : Math.max(C.TIME.seconds(30), Math.floor(replayWindowMs / 2));
316
- var nonceStore = opts.nonceStore || nonceStoreLib.create({ backend: "memory" });
317
+ var store = opts.nonceStore || nonceStore.create({ backend: "memory" });
317
318
  var exemptPaths = Array.isArray(opts.exemptPaths) ? opts.exemptPaths.slice() : [];
318
319
  // contentTypes scoping — middleware only operates on requests whose
319
320
  // Content-Type is in this list. Default JSON; operators with more
@@ -344,12 +345,9 @@ function create(opts) {
344
345
  "apiEncrypt: sessionTtlMs must be a positive finite number (ms), got " +
345
346
  JSON.stringify(opts.sessionTtlMs), 500);
346
347
  }
347
- if (typeof sessionMaxResponses !== "number" || !isFinite(sessionMaxResponses) ||
348
- sessionMaxResponses <= 0 || Math.floor(sessionMaxResponses) !== sessionMaxResponses) {
349
- throw _err("BAD_OPT",
350
- "apiEncrypt: sessionMaxResponses must be a positive finite integer, got " +
351
- JSON.stringify(opts.sessionMaxResponses), 500);
352
- }
348
+ numericBounds.requirePositiveFiniteInt(sessionMaxResponses,
349
+ "apiEncrypt: sessionMaxResponses", ApiEncryptError, "BAD_OPT", null,
350
+ { permanent: true, statusCode: 500 });
353
351
  // sessionStore — duck-typed handle exposing { get, set, delete }. The
354
352
  // helper optionalObjectWithMethod only checks one method; here we need
355
353
  // three. Inline shape kept; not a generic enough pattern to warrant a
@@ -393,16 +391,7 @@ function create(opts) {
393
391
  } catch (_e) { /* audit best-effort */ }
394
392
  }
395
393
 
396
- function _isExempt(req) {
397
- var p = req.pathname || (req.url || "/").split("?")[0];
398
- for (var i = 0; i < exemptPaths.length; i++) {
399
- var rule = exemptPaths[i];
400
- if (typeof rule === "string" ? p === rule || p.indexOf(rule + "/") === 0 : rule.test(p)) {
401
- return true;
402
- }
403
- }
404
- return false;
405
- }
394
+ var _isExempt = requestHelpers.makeSkipMatcher({ skipPaths: exemptPaths }, "middleware.apiEncrypt");
406
395
 
407
396
  function _matchesContentType(req) {
408
397
  if (!contentTypes) return true; // filtering disabled
@@ -443,7 +432,7 @@ function create(opts) {
443
432
  var now = Date.now();
444
433
  if (now - lastPruneAt < pruneIntervalMs) return;
445
434
  lastPruneAt = now;
446
- nonceStore.purgeExpired().catch(function (e) {
435
+ store.purgeExpired().catch(function (e) {
447
436
  try {
448
437
  logger().warn("nonce-store prune failed: " + ((e && e.message) || String(e)));
449
438
  } catch (_e) { /* logger best-effort */ }
@@ -548,7 +537,7 @@ function create(opts) {
548
537
  var nonceHash = bCrypto.sha3Hash(nonce, "hex");
549
538
  var expireAt = now + replayWindowMs;
550
539
  var freshNonce;
551
- try { freshNonce = await nonceStore.checkAndInsert(nonceHash, expireAt); }
540
+ try { freshNonce = await store.checkAndInsert(nonceHash, expireAt); }
552
541
  catch (_e) {
553
542
  _emitFailure(req, "nonce-store-error");
554
543
  return _writeRejection(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, { error: "nonce-store-unavailable" });
@@ -567,7 +556,7 @@ function create(opts) {
567
556
  _emitFailure(req, "shape");
568
557
  return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
569
558
  }
570
- if (typeof ctr !== "number" || !isFinite(ctr) || ctr < 0 || Math.floor(ctr) !== ctr) {
559
+ if (!numericBounds.isNonNegativeFiniteInt(ctr)) {
571
560
  _emitFailure(req, "shape");
572
561
  return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
573
562
  }
@@ -607,7 +596,7 @@ function create(opts) {
607
596
  _emitFailure(req, "shape");
608
597
  return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
609
598
  }
610
- if (!isFinite(ctr) || ctr < 0 || Math.floor(ctr) !== ctr) {
599
+ if (!numericBounds.isNonNegativeFiniteInt(ctr)) {
611
600
  _emitFailure(req, "shape");
612
601
  return _writeRejection(res, HTTP_STATUS.BAD_REQUEST, { error: "encrypted-payload-required" });
613
602
  }
@@ -682,7 +671,7 @@ function create(opts) {
682
671
  // capacity.
683
672
  var ctrKey = "ctr:" + sid + ":" + ctr;
684
673
  var ctrFresh;
685
- try { ctrFresh = await nonceStore.checkAndInsert(ctrKey, session.expiresAt); }
674
+ try { ctrFresh = await store.checkAndInsert(ctrKey, session.expiresAt); }
686
675
  catch (_e) {
687
676
  _emitFailure(req, "nonce-store-error");
688
677
  return _writeRejection(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, { error: "nonce-store-unavailable" });
@@ -779,7 +768,7 @@ function create(opts) {
779
768
 
780
769
  middleware.publishPublicKey = publishPublicKey;
781
770
  middleware.close = function () {
782
- if (typeof nonceStore.close === "function") nonceStore.close();
771
+ if (typeof store.close === "function") store.close();
783
772
  if (sessionStore && typeof sessionStore.close === "function") sessionStore.close();
784
773
  };
785
774
  // Expose for tests / operator dashboards. Counts are 0 in per-request mode.
@@ -28,6 +28,7 @@
28
28
  var lazyRequire = require("../lazy-require");
29
29
  var safeJson = require("../safe-json");
30
30
  var validateOpts = require("../validate-opts");
31
+ var denyResponse = require("./deny-response");
31
32
  var { defineClass } = require("../framework-error");
32
33
 
33
34
  var AssetlinksError = defineClass("AssetlinksError", { alwaysPermanent: true });
@@ -108,13 +109,7 @@ function create(opts) {
108
109
  var path = qIdx === -1 ? url : url.slice(0, qIdx);
109
110
  if (path !== "/.well-known/assetlinks.json") return next();
110
111
  if (req.method !== "GET" && req.method !== "HEAD") {
111
- var bodyMsg = "Method Not Allowed";
112
- res.writeHead(405, { // HTTP 405 status
113
- "Allow": "GET, HEAD",
114
- "Content-Type": "text/plain; charset=utf-8",
115
- "Content-Length": Buffer.byteLength(bodyMsg),
116
- });
117
- res.end(bodyMsg);
112
+ denyResponse.methodNotAllowed(res, "GET, HEAD");
118
113
  return;
119
114
  }
120
115
  res.writeHead(200, { // HTTP 200 status
@@ -38,6 +38,7 @@ var lazyRequire = require("../lazy-require");
38
38
  var requestHelpers = require("../request-helpers");
39
39
  var validateOpts = require("../validate-opts");
40
40
  var denyResponse = require("./deny-response").denyResponse;
41
+ var codepointClass = require("../codepoint-class");
41
42
  var { AuthError } = require("../framework-error");
42
43
 
43
44
  var audit = lazyRequire(function () { return require("../audit"); });
@@ -164,7 +165,7 @@ function create(opts) {
164
165
  }
165
166
  for (var ri = 0; ri < realm.length; ri += 1) {
166
167
  var rcode = realm.charCodeAt(ri);
167
- if (rcode < 32 || rcode === 127) { // ASCII control codepoints
168
+ if (codepointClass.isForbiddenControlChar(rcode, { forbidTab: true })) { // ASCII control codepoints
168
169
  throw new AuthError("auth-bearer/bad-realm",
169
170
  "realm contains control character at index " + ri);
170
171
  }