@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
@@ -70,6 +70,7 @@
70
70
  // node:util is a builtin (no lib require cycle) — used for strict UTF-8
71
71
  // validation of RFC 9651 Display Strings.
72
72
  var TextDecoder = require("node:util").TextDecoder;
73
+ var codepointClass = require("./codepoint-class");
73
74
 
74
75
  function splitTopLevel(s, sep) {
75
76
  if (typeof s !== "string") return [];
@@ -103,6 +104,261 @@ function splitTopLevel(s, sep) {
103
104
  return out;
104
105
  }
105
106
 
107
+ /**
108
+ * @primitive b.structuredFields.splitUnquoted
109
+ * @signature b.structuredFields.splitUnquoted(s, sep)
110
+ * @since 0.15.13
111
+ * @status stable
112
+ * @related b.structuredFields.splitTopLevel
113
+ *
114
+ * Split `s` on every `sep` that falls OUTSIDE a `"..."` quoted run — the
115
+ * iCalendar / vCard variant of the quote-aware splitter. A `"` toggles the
116
+ * quoted state and there is NO backslash escaping of the quote (RFC 5545
117
+ * 3.1.1 / RFC 6350 3.3 QSAFE-CHAR excludes DQUOTE, so a `"` always opens or
118
+ * closes a run). This is deliberately simpler than `splitTopLevel`, which
119
+ * honours HTTP structured-field backslash-quote escapes; the two are NOT
120
+ * interchangeable. Accepts any single-character separator.
121
+ *
122
+ * @example
123
+ * b.structuredFields.splitUnquoted('a;b="x;y";c', ";");
124
+ * // returns ['a', 'b="x;y"', 'c']
125
+ */
126
+ function splitUnquoted(s, sep) {
127
+ var out = [];
128
+ var inQ = false;
129
+ var start = 0;
130
+ for (var i = 0; i < s.length; i++) {
131
+ var c = s.charAt(i);
132
+ if (c === '"') { inQ = !inQ; continue; }
133
+ if (c === sep && !inQ) {
134
+ out.push(s.slice(start, i));
135
+ start = i + 1;
136
+ }
137
+ }
138
+ out.push(s.slice(start));
139
+ return out;
140
+ }
141
+
142
+ /**
143
+ * @primitive b.structuredFields.stripDoubleQuotes
144
+ * @signature b.structuredFields.stripDoubleQuotes(s)
145
+ * @since 0.15.13
146
+ * @status stable
147
+ * @related b.structuredFields.splitUnquoted, b.structuredFields.unquoteSfString
148
+ *
149
+ * Strip ONE layer of surrounding `"` from `s` when both ends are a double
150
+ * quote (length ≥ 2), otherwise return `s` unchanged. The plain DQUOTE
151
+ * unwrap used by the iCal / vCard parsers for a quoted parameter value —
152
+ * no backslash-escape processing (RFC 5545 3.1 / RFC 6350 5: a quoted
153
+ * param value is QSAFE-CHAR, which excludes DQUOTE, so there is nothing to
154
+ * unescape). Distinct from `unquoteSfString`, which decodes HTTP
155
+ * structured-field `\"` / `\\` escapes.
156
+ *
157
+ * @example
158
+ * b.structuredFields.stripDoubleQuotes('"a;b"');
159
+ * // returns 'a;b'
160
+ *
161
+ * b.structuredFields.stripDoubleQuotes('plain');
162
+ * // returns 'plain'
163
+ */
164
+ function stripDoubleQuotes(s) {
165
+ if (s.length >= 2 && s.charAt(0) === '"' && s.charAt(s.length - 1) === '"') {
166
+ return s.slice(1, -1);
167
+ }
168
+ return s;
169
+ }
170
+
171
+ /**
172
+ * @primitive b.structuredFields.unfoldHeaderContinuations
173
+ * @signature b.structuredFields.unfoldHeaderContinuations(value)
174
+ * @since 0.15.13
175
+ * @status stable
176
+ * @related b.structuredFields.parseTagList
177
+ *
178
+ * Collapse RFC 5322 folding whitespace — a CRLF (or bare LF) followed by one
179
+ * or more spaces/tabs — back to a single space, reversing the line folding a
180
+ * header value may carry in transit. Used before parsing DKIM / ARC /
181
+ * Authentication-Results tag lists, where a folded `b=` / `bh=` value must be
182
+ * rejoined before its base64 is read.
183
+ *
184
+ * @example
185
+ * b.structuredFields.unfoldHeaderContinuations("v=DKIM1;\r\n k=rsa");
186
+ * // returns "v=DKIM1; k=rsa"
187
+ */
188
+ function unfoldHeaderContinuations(value) {
189
+ return String(value).replace(/\r?\n[ \t]+/g, " ");
190
+ }
191
+
192
+ /**
193
+ * @primitive b.structuredFields.parseKeyValuePiece
194
+ * @signature b.structuredFields.parseKeyValuePiece(piece, kvSep?, lowerKey?)
195
+ * @since 0.15.13
196
+ * @status stable
197
+ * @related b.structuredFields.parseTagList, b.structuredFields.splitTopLevel
198
+ *
199
+ * Parse ONE already-split list piece into a `{ key, value }` pair: `key` is the
200
+ * text before the first `kvSep` (default "="), trimmed and — unless `lowerKey`
201
+ * is `false` — lower-cased; `value` is the raw remainder (the caller trims /
202
+ * unquotes / sf-string-parses it per its own grammar). A piece with NO `kvSep`
203
+ * is a "bare" item — `value` is null — which the caller handles per its grammar
204
+ * (skip it, or treat as a flag-style directive).
205
+ *
206
+ * The shared per-pair step behind every `key=value` list parser: the naive
207
+ * `parseTagList` loop AND the quote-aware parsers that first `splitTopLevel`
208
+ * then parse each piece (Cache-Control directives, Client-Hints brand params,
209
+ * Content-Type parameters). `lowerKey:false` serves the rare grammar whose key
210
+ * is case-sensitive (a verbatim tag-list passthrough).
211
+ *
212
+ * @example
213
+ * b.structuredFields.parseKeyValuePiece("Max-Age=60"); // → { key: "max-age", value: "60" }
214
+ * b.structuredFields.parseKeyValuePiece("no-store"); // → { key: "no-store", value: null }
215
+ * b.structuredFields.parseKeyValuePiece("Key=a=b", "=", false); // → { key: "Key", value: "a=b" }
216
+ */
217
+ function parseKeyValuePiece(piece, kvSep, lowerKey) {
218
+ var sep = kvSep || "=";
219
+ var at = piece.indexOf(sep);
220
+ var rawKey = (at === -1 ? piece : piece.slice(0, at)).trim();
221
+ var key = lowerKey === false ? rawKey : rawKey.toLowerCase();
222
+ return { key: key, value: at === -1 ? null : piece.slice(at + sep.length) };
223
+ }
224
+
225
+ /**
226
+ * @primitive b.structuredFields.parseTagList
227
+ * @signature b.structuredFields.parseTagList(input, opts?)
228
+ * @since 0.15.13
229
+ * @status stable
230
+ * @related b.structuredFields.splitTopLevel
231
+ *
232
+ * Parse a NAIVE delimited `key<kvSep>value` tag list into an ordered
233
+ * array of `[key, value]` pairs. This is the non-quote-aware sibling
234
+ * of `splitTopLevel`: it is for grammars whose RFC forbids the DQUOTE
235
+ * structured-string form, so a bare `split` is correct and a
236
+ * quote-aware walk would be wrong — DKIM (RFC 6376 §3.2), DMARC
237
+ * (RFC 7489 §6.4), ARC (RFC 8617 §4), BIMI (RFC 9091 §4), and the
238
+ * MTA-STS policy grammar (RFC 8461, line/colon delimited).
239
+ *
240
+ * Pairs are returned (not a map) so a caller whose grammar permits a
241
+ * repeated key — MTA-STS `mx:` lines list one host each — keeps every
242
+ * occurrence; a caller wanting last-wins map semantics folds the
243
+ * pairs into a plain object itself. Order is preserved, so a caller
244
+ * that throws on a malformed value throws at the same point it would
245
+ * have mid-loop.
246
+ *
247
+ * Entries that are empty after trimming, or carry no `kvSep`, are
248
+ * skipped (matching every shipped parser's prior behavior).
249
+ *
250
+ * @opts
251
+ * sep: string|RegExp, // entry separator. default: ";" (MTA-STS uses /\r?\n/)
252
+ * kvSep: string, // key/value separator. default: "=" (MTA-STS uses ":")
253
+ * unfold: boolean, // collapse CRLF+WSP folds to a space first (DKIM FWS). default: false
254
+ * stripValueWs: boolean, // strip all whitespace inside each value (DKIM/ARC FWS). default: false
255
+ * lowerKey: boolean, // lower-case each key. default: true (set false to preserve case)
256
+ *
257
+ * @example
258
+ * b.structuredFields.parseTagList("v=DKIM1; k=rsa; p=MIGf");
259
+ * // → [ ["v", "DKIM1"], ["k", "rsa"], ["p", "MIGf"] ]
260
+ *
261
+ * b.structuredFields.parseTagList("version:STSv1\nmx:a.example\nmx:b.example",
262
+ * { sep: /\r?\n/, kvSep: ":" });
263
+ * // → [ ["version","STSv1"], ["mx","a.example"], ["mx","b.example"] ]
264
+ */
265
+ function parseTagList(input, opts) {
266
+ opts = opts || {};
267
+ var sep = opts.sep === undefined ? ";" : opts.sep;
268
+ var kvSep = opts.kvSep === undefined ? "=" : opts.kvSep;
269
+ var s = String(input);
270
+ if (opts.unfold) s = unfoldHeaderContinuations(s);
271
+ var kvps = parseKeyValuePieces(s.split(sep), 0, kvSep, opts.lowerKey);
272
+ var out = [];
273
+ forEachKeyValue(kvps, function (key, val) {
274
+ if (opts.stripValueWs) val = val.replace(/\s+/g, "");
275
+ out.push([key, val]);
276
+ });
277
+ return out;
278
+ }
279
+
280
+ /**
281
+ * @primitive b.structuredFields.parseKeyValuePieces
282
+ * @signature b.structuredFields.parseKeyValuePieces(pieces, startIndex?, kvSep?, lowerKey?)
283
+ * @since 0.15.13
284
+ * @status stable
285
+ * @related b.structuredFields.parseKeyValuePiece, b.structuredFields.parseTagList
286
+ *
287
+ * Iterate an already-split list of structured-field pieces, trimming each,
288
+ * dropping empties, and parsing the survivors into `{ key, value }` records
289
+ * via `parseKeyValuePiece`. `startIndex` skips a leading non-pair token (the
290
+ * media type in a `Content-Type`, the brand in a `Sec-CH-UA` member); `kvSep`
291
+ * overrides the default `"="`; `lowerKey:false` preserves key case for a
292
+ * case-sensitive grammar.
293
+ *
294
+ * The split is left to the caller because the boundary discipline differs by
295
+ * grammar — `splitTopLevel` honours quotes/parens for RFC 8941 lists, while a
296
+ * plain `String(value).split(";")` is right where inner separators cannot be
297
+ * quoted. Per-piece dispatch (unquote, numeric coercion, poisoned-key drops)
298
+ * stays with the caller; this owns only the uniform iterate-trim-skip-parse
299
+ * spine the parsers shared verbatim.
300
+ *
301
+ * @opts
302
+ * startIndex: number, // default: 0 — first piece treated as a key/value pair
303
+ * kvSep: string, // default: "=" — key/value separator within a piece
304
+ * lowerKey: boolean, // default: true — lower-case each parsed key
305
+ *
306
+ * @example
307
+ * var kvps = b.structuredFields.parseKeyValuePieces(
308
+ * b.structuredFields.splitTopLevel("max-age=600, immutable", ","));
309
+ * // → [ { key: "max-age", value: "600" }, { key: "immutable", value: null } ]
310
+ */
311
+ function parseKeyValuePieces(pieces, startIndex, kvSep, lowerKey) {
312
+ var out = [];
313
+ for (var i = startIndex || 0; i < pieces.length; i += 1) {
314
+ var p = pieces[i].trim();
315
+ if (p.length === 0) continue;
316
+ out.push(parseKeyValuePiece(p, kvSep, lowerKey));
317
+ }
318
+ return out;
319
+ }
320
+
321
+ /**
322
+ * @primitive b.structuredFields.forEachKeyValue
323
+ * @signature b.structuredFields.forEachKeyValue(kvps, handler)
324
+ * @since 0.15.13
325
+ * @status stable
326
+ * @related b.structuredFields.parseKeyValuePieces, b.structuredFields.parseTagList
327
+ *
328
+ * Consume the `{ key, value }` records from `parseKeyValuePieces`: skip
329
+ * the bare entries (those with a `null` value — a key that carried no
330
+ * separator), trim each surviving value, and invoke
331
+ * `handler(key, trimmedValue, index)`. The mirror of
332
+ * `parseKeyValuePieces` on the consuming side — that primitive owns the
333
+ * parse spine, this owns the iterate-skip-bare-trim spine every header
334
+ * parser repeated verbatim before dispatching.
335
+ *
336
+ * Per-key dispatch (sf-string unquoting, numeric coercion, poisoned-key
337
+ * drops, building a typed result) stays in the handler — a handler that
338
+ * `return`s skips the current entry, exactly like a `continue`. Parsers
339
+ * that instead treat a bare key as meaningful (a value-less directive)
340
+ * iterate the records directly rather than calling this.
341
+ *
342
+ * @example
343
+ * var b = require("blamejs");
344
+ *
345
+ * var out = {};
346
+ * var kvps = b.structuredFields.parseKeyValuePieces("a=1; b=2".split(";"));
347
+ * b.structuredFields.forEachKeyValue(kvps, function (key, value) {
348
+ * out[key] = value;
349
+ * });
350
+ * // out → { a: "1", b: "2" }
351
+ */
352
+ function forEachKeyValue(kvps, handler) {
353
+ if (typeof handler !== "function") {
354
+ throw new TypeError("structuredFields.forEachKeyValue: handler must be a function");
355
+ }
356
+ for (var i = 0; i < kvps.length; i += 1) {
357
+ if (kvps[i].value === null) continue;
358
+ handler(kvps[i].key, kvps[i].value.trim(), i);
359
+ }
360
+ }
361
+
106
362
  /**
107
363
  * @primitive b.structuredFields.refuseControlBytes
108
364
  * @signature b.structuredFields.refuseControlBytes(value, opts)
@@ -155,22 +411,18 @@ function refuseControlBytes(value, opts) {
155
411
  if (!opts.label || typeof opts.label !== "string") {
156
412
  throw new TypeError("refuseControlBytes: opts.label (non-empty string) is required");
157
413
  }
158
- var allowHt = opts.allowHt !== false;
159
- for (var i = 0; i < value.length; i += 1) {
160
- var cc = value.charCodeAt(i);
161
- if (allowHt && cc === 9) continue; // ASCII HT (folding whitespace)
162
- if (cc < 32 || cc === 127) { // C0 + DEL codepoint range
163
- var msg = opts.label + ": value contains control characters (C0 / DEL)";
164
- // opts.useNativeError === true → call the ErrorClass with a
165
- // single-arg `message` (matches native Error / TypeError /
166
- // RangeError signatures used by defensive request-shape
167
- // readers). Default false → call with (code, message) which
168
- // matches every framework-error class generated by `defineClass`.
169
- if (opts.useNativeError === true) {
170
- throw new opts.ErrorClass(msg);
171
- }
172
- throw new opts.ErrorClass(opts.code, msg);
414
+ var allowHt = opts.allowHt !== false; // ASCII HT (folding whitespace) permitted unless allowHt === false
415
+ if (codepointClass.firstControlCharOffset(value, { forbidTab: !allowHt }) !== -1) { // C0 + DEL codepoint range
416
+ var msg = opts.label + ": value contains control characters (C0 / DEL)";
417
+ // opts.useNativeError === true call the ErrorClass with a
418
+ // single-arg `message` (matches native Error / TypeError /
419
+ // RangeError signatures used by defensive request-shape
420
+ // readers). Default false → call with (code, message) which
421
+ // matches every framework-error class generated by `defineClass`.
422
+ if (opts.useNativeError === true) {
423
+ throw new opts.ErrorClass(msg);
173
424
  }
425
+ throw new opts.ErrorClass(opts.code, msg);
174
426
  }
175
427
  }
176
428
 
@@ -271,7 +523,7 @@ function containsControlBytes(value, opts) {
271
523
  for (var i = 0; i < value.length; i += 1) {
272
524
  var cc = value.charCodeAt(i);
273
525
  if (allowHt && cc === 9) continue; // ASCII HT (folding whitespace)
274
- if (cc < 32 || cc === 127) return true; // C0 + DEL codepoint range
526
+ if (codepointClass.isForbiddenControlChar(cc, { forbidTab: true })) return true; // C0 + DEL codepoint range
275
527
  }
276
528
  return false;
277
529
  }
@@ -707,6 +959,13 @@ function serialize(value, type, opts) {
707
959
 
708
960
  module.exports = {
709
961
  splitTopLevel: splitTopLevel,
962
+ splitUnquoted: splitUnquoted,
963
+ stripDoubleQuotes: stripDoubleQuotes,
964
+ parseTagList: parseTagList,
965
+ unfoldHeaderContinuations: unfoldHeaderContinuations,
966
+ parseKeyValuePiece: parseKeyValuePiece,
967
+ parseKeyValuePieces: parseKeyValuePieces,
968
+ forEachKeyValue: forEachKeyValue,
710
969
  refuseControlBytes: refuseControlBytes,
711
970
  containsControlBytes: containsControlBytes,
712
971
  unquoteSfString: unquoteSfString,
package/lib/template.js CHANGED
@@ -93,6 +93,7 @@ var nodeFs = require("node:fs");
93
93
  var nodePath = require("node:path");
94
94
  var lazyRequire = require("./lazy-require");
95
95
  var validateOpts = require("./validate-opts");
96
+ var markupEscape = require("./markup-escape").markupEscape;
96
97
 
97
98
  // Lazy because b.template can be loaded before b.sandbox (which pulls
98
99
  // in node:worker_threads). Operators not opting into sandboxed helpers
@@ -116,9 +117,6 @@ var DEFAULT_STRING_TEMPLATE_BYTES = require("./constants").BYTES.kib(256);
116
117
  // HTML escape (exported)
117
118
  // ============================================================
118
119
 
119
- var ESCAPE_MAP = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#x27;" };
120
- var ESCAPE_RE = /[&<>"']/g;
121
-
122
120
  /**
123
121
  * @primitive b.template.escapeHtml
124
122
  * @signature b.template.escapeHtml(value)
@@ -141,8 +139,12 @@ var ESCAPE_RE = /[&<>"']/g;
141
139
  */
142
140
  function escapeHtml(value) {
143
141
  if (value === null || value === undefined) return "";
144
- var s = typeof value === "string" ? value : String(value);
145
- return s.replace(ESCAPE_RE, function (c) { return ESCAPE_MAP[c]; });
142
+ // Delegate the actual escaping to the centralized markup escaper so the
143
+ // five-character HTML set (& < > " ') is defined in one place — a divergence
144
+ // between this and the shared escaper is an XSS / XML-injection surface. The
145
+ // apostrophe is escaped to its numeric form for safety in single-quoted
146
+ // attribute contexts (the historical behavior of this primitive).
147
+ return markupEscape(value, { apos: "&#x27;" });
146
148
  }
147
149
 
148
150
  // ============================================================
@@ -52,6 +52,7 @@
52
52
 
53
53
  var C = require("./constants");
54
54
  var lazyRequire = require("./lazy-require");
55
+ var boundedMap = require("./bounded-map");
55
56
  var validateOpts = require("./validate-opts");
56
57
  var { defineClass } = require("./framework-error");
57
58
 
@@ -183,16 +184,7 @@ function create(opts) {
183
184
  return defaultBytesCap;
184
185
  }
185
186
 
186
- function _emitAudit(action, outcome, metadata) {
187
- if (!auditOn) return;
188
- try {
189
- audit().safeEmit({
190
- action: action,
191
- outcome: outcome,
192
- metadata: metadata || {},
193
- });
194
- } catch (_e) { /* audit best-effort */ }
195
- }
187
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
196
188
 
197
189
  function _emitMetric(name, n) {
198
190
  try { observability().safeEvent(name, n || 1, {}); }
@@ -363,29 +355,49 @@ function budget(opts) {
363
355
  }
364
356
  var auditOn = opts.audit !== false;
365
357
 
366
- // tenantId { windowStart, calls, rowsRead }
358
+ // TRUE sliding window (RFC-ish rolling counter, as advertised) — the prior
359
+ // first-call-pinned fixed window admitted ~2x the cap in a boundary burst.
360
+ // The window is split into BINS sub-bins; each observe lands in the current
361
+ // bin and the cap is enforced against the trailing-window SUM, so a burst
362
+ // straddling the reset can't double the rate. Mirrors network-byte-quota's
363
+ // _slideAndSum ring (here with two counters + a configurable bin width).
364
+ var BINS = 12;
365
+ var binMs = Math.max(1, Math.floor(windowMs / BINS));
366
+
367
+ // tenantId → { calls: number[BINS], rows: number[BINS], startBin }
367
368
  var counters = new Map();
368
369
 
369
- function _slot(tenantId, now) {
370
- var c = counters.get(tenantId);
371
- if (!c || (now - c.windowStart) >= windowMs) {
372
- c = { windowStart: now, calls: 0, rowsRead: 0 };
373
- counters.set(tenantId, c);
370
+ // Advance the ring so its BINS bins cover the trailing [nowBin-BINS+1 .. nowBin],
371
+ // zeroing bins that scrolled out of the window. Returns the tenant's ring.
372
+ function _slide(tenantId, now) {
373
+ var nowBin = Math.floor(now / binMs);
374
+ // A freshly-created ring is anchored at the current bin, so the advance
375
+ // below evaluates to 0 (no-op) on the create path — get-or-insert and the
376
+ // slide stay one code path.
377
+ var c = boundedMap.getOrInsert(counters, tenantId, function () {
378
+ return { calls: new Array(BINS).fill(0), rows: new Array(BINS).fill(0), startBin: nowBin - (BINS - 1) };
379
+ });
380
+ var advance = nowBin - (c.startBin + (BINS - 1));
381
+ if (advance > 0) {
382
+ if (advance >= BINS) {
383
+ c.calls.fill(0); c.rows.fill(0);
384
+ } else {
385
+ for (var i = 0; i < BINS - advance; i++) { c.calls[i] = c.calls[i + advance]; c.rows[i] = c.rows[i + advance]; }
386
+ for (var k = BINS - advance; k < BINS; k++) { c.calls[k] = 0; c.rows[k] = 0; }
387
+ }
388
+ c.startBin = nowBin - (BINS - 1);
374
389
  }
375
390
  return c;
376
391
  }
377
392
 
378
- function _emitAudit(action, outcome, metadata) {
379
- if (!auditOn) return;
380
- try {
381
- audit().safeEmit({
382
- action: action,
383
- outcome: outcome,
384
- metadata: metadata || {},
385
- });
386
- } catch (_e) { /* audit best-effort */ }
393
+ function _sum(c) {
394
+ var calls = 0, rows = 0;
395
+ for (var i = 0; i < BINS; i++) { calls += c.calls[i]; rows += c.rows[i]; }
396
+ return { calls: calls, rowsRead: rows };
387
397
  }
388
398
 
399
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
400
+
389
401
  function _emitMetric(name, n) {
390
402
  try { observability().safeEvent(name, n || 1, {}); }
391
403
  catch (_e) { /* drop-silent */ }
@@ -396,16 +408,19 @@ function budget(opts) {
396
408
  "tenantQuota.budget.observe: tenantId", TenantQuotaError, "tenant-quota/bad-tenant");
397
409
  info = info || {};
398
410
  var rowsRead = (typeof info.rowsRead === "number" && info.rowsRead >= 0) ? info.rowsRead : 0;
399
- var now = Date.now();
400
- var c = _slot(tenantId, now);
401
- c.calls += 1;
402
- c.rowsRead += rowsRead;
411
+ // info.now lets a caller / test supply a deterministic clock (idiomatic —
412
+ // mirrors auth verifiers' opts.now); defaults to wall-clock.
413
+ var now = (typeof info.now === "number") ? info.now : Date.now();
414
+ var c = _slide(tenantId, now);
415
+ c.calls[BINS - 1] += 1;
416
+ c.rows[BINS - 1] += rowsRead;
417
+ var tot = _sum(c);
403
418
  var maxCalls = Math.max(1, Math.floor(qpsCap * (windowMs / C.TIME.seconds(1))));
404
- if (c.calls > maxCalls || c.rowsRead > rowsCap) {
419
+ if (tot.calls > maxCalls || tot.rowsRead > rowsCap) {
405
420
  _emitAudit("tenant.budget.exceeded", "denied", {
406
421
  tenantId: tenantId,
407
- calls: c.calls,
408
- rowsRead: c.rowsRead,
422
+ calls: tot.calls,
423
+ rowsRead: tot.rowsRead,
409
424
  qpsCap: qpsCap,
410
425
  rowsCap: rowsCap,
411
426
  windowMs: windowMs,
@@ -413,19 +428,19 @@ function budget(opts) {
413
428
  _emitMetric("tenant.budget.exceeded", 1);
414
429
  throw new TenantQuotaError("tenant-quota/budget-exceeded",
415
430
  "tenantQuota.budget: tenant '" + tenantId + "' exceeded budget " +
416
- "(calls=" + c.calls + "/" + maxCalls + ", rowsRead=" + c.rowsRead +
431
+ "(calls=" + tot.calls + "/" + maxCalls + ", rowsRead=" + tot.rowsRead +
417
432
  "/" + rowsCap + ", windowMs=" + windowMs + ")");
418
433
  }
419
- return { calls: c.calls, rowsRead: c.rowsRead, windowMs: windowMs };
434
+ return { calls: tot.calls, rowsRead: tot.rowsRead, windowMs: windowMs };
420
435
  }
421
436
 
422
437
  function snapshot(tenantId) {
423
- var now = Date.now();
424
- var c = counters.get(tenantId);
425
- if (!c || (now - c.windowStart) >= windowMs) {
438
+ if (!counters.has(tenantId)) {
426
439
  return { tenantId: tenantId, calls: 0, rowsRead: 0, windowMs: windowMs };
427
440
  }
428
- return { tenantId: tenantId, calls: c.calls, rowsRead: c.rowsRead, windowMs: windowMs };
441
+ var c = _slide(tenantId, Date.now());
442
+ var tot = _sum(c);
443
+ return { tenantId: tenantId, calls: tot.calls, rowsRead: tot.rowsRead, windowMs: windowMs };
429
444
  }
430
445
 
431
446
  function reset(tenantId) {
package/lib/tsa.js CHANGED
@@ -53,8 +53,10 @@
53
53
 
54
54
  var nodeCrypto = require("node:crypto");
55
55
  var bCrypto = require("./crypto");
56
+ var safeBuffer = require("./safe-buffer");
56
57
  var asn1 = require("./asn1-der");
57
58
  var cms = require("./cms-codec");
59
+ var x509Chain = require("./x509-chain");
58
60
  var validateOpts = require("./validate-opts");
59
61
  var { defineClass } = require("./framework-error");
60
62
 
@@ -104,12 +106,12 @@ var DIGEST_OID_TO_NODE = {
104
106
  "2.16.840.1.101.3.4.2.10": "sha3-512",
105
107
  };
106
108
 
107
- function _bytes(x, what) {
108
- if (Buffer.isBuffer(x)) return x;
109
- if (x instanceof Uint8Array) return Buffer.from(x);
110
- if (typeof x === "string") return Buffer.from(x, "utf8");
111
- throw new TsaError("tsa/bad-bytes", "tsa: " + what + " must be Buffer / Uint8Array / string");
112
- }
109
+ var _bytes = safeBuffer.makeByteCoercer({
110
+ errorClass: TsaError,
111
+ typeCode: "tsa/bad-bytes",
112
+ messagePrefix: "tsa: ",
113
+ messageSuffix: " must be Buffer / Uint8Array / string",
114
+ });
113
115
 
114
116
  function _normHex(h) {
115
117
  return String(h).replace(/[^0-9a-fA-F]/g, "").replace(/^0+(?=.)/, "").toUpperCase();
@@ -538,8 +540,10 @@ function _verifyChain(signerCertDer, tokenCerts, trustAnchorsPem, at) {
538
540
  throw new TsaError("tsa/chain-loop", "tsa.verifyToken: certificate chain did not terminate");
539
541
  }
540
542
  function _issued(issuer, subject) {
541
- try { return subject.checkIssued(issuer) && subject.verify(issuer.publicKey); }
542
- catch (_e) { return false; }
543
+ // Enforces basicConstraints cA:TRUE on the issuer in addition to the
544
+ // checkIssued + signature linkage — a non-CA cert can never be a chain
545
+ // issuer (basicConstraints bypass, CVE-2002-0862 class).
546
+ return x509Chain.issuerValidlyIssued(issuer, subject);
543
547
  }
544
548
  function _assertValidAt(cert, atMs) {
545
549
  if (atMs < cert.validFromDate.getTime() || atMs > cert.validToDate.getTime()) {
@@ -658,15 +662,10 @@ function verifyToken(token, opts) {
658
662
  throw new TsaError("tsa/bad-trust-anchors",
659
663
  "tsa.verifyToken: trustAnchorsPem must be a non-empty PEM string or array of PEM strings");
660
664
  }
661
- // A supplied opts.at must be a valid Date — an Invalid Date would
662
- // make every validity-window comparison NaN (silently disabling it).
663
- var at = tst.genTime;
664
- if (opts.at !== undefined && opts.at !== null) {
665
- if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
666
- throw new TsaError("tsa/bad-at", "tsa.verifyToken: opts.at must be a valid Date");
667
- }
668
- at = opts.at;
669
- }
665
+ // A supplied opts.at must be a valid Date — an Invalid Date would make
666
+ // every validity-window comparison NaN (silently disabling it).
667
+ validateOpts.optionalDate(opts.at, "tsa.verifyToken: opts.at", TsaError, "tsa/bad-at");
668
+ var at = (opts.at !== undefined && opts.at !== null) ? opts.at : tst.genTime;
670
669
  _verifyChain(signerCertDer, sd.certificates, anchors, at);
671
670
  }
672
671