@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
@@ -28,6 +28,7 @@
28
28
  */
29
29
 
30
30
  var numericBounds = require("./numeric-bounds");
31
+ var pick = require("./pick");
31
32
 
32
33
  function _format(primitive, unknownKey, allowedKeys) {
33
34
  return primitive + ": unknown option '" + unknownKey + "'. " +
@@ -78,13 +79,18 @@ function auditShape(audit, callerLabel, errorClass, code) {
78
79
  // _throw — shared error-emission for the optional* validators. Routes
79
80
  // through the caller's framework-error class when supplied, falls back
80
81
  // to plain Error so this helper itself stays decoupled from any one
81
- // error hierarchy.
82
- function _throw(errorClass, code, msg, defaultCode) {
82
+ // error hierarchy. `permanent` (optional) is forwarded as the framework
83
+ // error's third constructor argument so callers whose config-time failure
84
+ // is non-retryable (a misconfigured dependency never becomes valid on
85
+ // retry) keep that flag when they route through these validators instead
86
+ // of hand-throwing; omitted → undefined → the class default (false), so
87
+ // existing callers are unaffected.
88
+ function _throw(errorClass, code, msg, defaultCode, permanent) {
83
89
  if (errorClass && errorClass.factory) {
84
- throw errorClass.factory(code || "BAD_OPT", msg);
90
+ throw errorClass.factory(code || "BAD_OPT", msg, permanent);
85
91
  }
86
92
  if (typeof errorClass === "function") {
87
- throw new errorClass(code || defaultCode, msg);
93
+ throw new errorClass(code || defaultCode, msg, permanent);
88
94
  }
89
95
  throw new Error(msg);
90
96
  }
@@ -132,6 +138,15 @@ function optionalFiniteNonNegative(value, label, errorClass, code) {
132
138
  return value;
133
139
  }
134
140
 
141
+ function optionalDate(value, label, errorClass, code) {
142
+ if (value === undefined || value === null) return value;
143
+ if (!(value instanceof Date) || !isFinite(value.getTime())) {
144
+ _throw(errorClass, code, (label || "opt") + " must be a valid Date",
145
+ "validate-opts/bad-date");
146
+ }
147
+ return value;
148
+ }
149
+
135
150
  function optionalPositiveFinite(value, label, errorClass, code) {
136
151
  if (value === undefined || value === null) return value;
137
152
  if (typeof value !== "number" || !isFinite(value) || value <= 0) {
@@ -218,18 +233,25 @@ function requireObject(opts, callerLabel, errorClass, code) {
218
233
  // (b.agent.*.reseal stores, b.dsr / b.outbox create() backends, etc.)
219
234
  // into one definition. Throws on null / non-object / any missing-or-
220
235
  // non-function method; returns obj on success.
221
- function requireMethods(obj, methods, callerLabel, errorClass, code) {
236
+ //
237
+ // `permanent` (optional) forwards to the framework error's permanent flag
238
+ // — pass true for a config-time dependency check whose failure is not
239
+ // retryable (a backend/store/vault missing its contract never becomes
240
+ // valid on retry). Omitted → the error class default (false), matching
241
+ // the historical bare `new Err(code, msg)` shape, so existing callers are
242
+ // unchanged.
243
+ function requireMethods(obj, methods, callerLabel, errorClass, code, permanent) {
222
244
  var label = callerLabel || "dependency";
223
245
  if (!obj || typeof obj !== "object") {
224
246
  _throw(errorClass, code, label + " must be an object exposing { " +
225
247
  methods.join(", ") + " }, got " + (obj === null ? "null" : typeof obj),
226
- "validate-opts/bad-methods-object");
248
+ "validate-opts/bad-methods-object", permanent);
227
249
  }
228
250
  for (var i = 0; i < methods.length; i += 1) {
229
251
  if (typeof obj[methods[i]] !== "function") {
230
252
  _throw(errorClass, code, label + " must expose a " + methods[i] +
231
253
  "() method (requires { " + methods.join(", ") + " })",
232
- "validate-opts/missing-method");
254
+ "validate-opts/missing-method", permanent);
233
255
  }
234
256
  }
235
257
  return obj;
@@ -331,6 +353,128 @@ function optionalPlainObject(value, label, errorClass, code, description) {
331
353
  return value;
332
354
  }
333
355
 
356
+ // _SHAPE_RULES — rule-token → validator dispatch table for `shape`. Each
357
+ // validator has the uniform (value, label, errorClass, code) signature, so the
358
+ // table drives them all from a declarative schema.
359
+ var _SHAPE_RULES = {
360
+ "required-string": requireNonEmptyString,
361
+ "optional-string": optionalNonEmptyString,
362
+ "optional-string-array": optionalNonEmptyStringArray,
363
+ "optional-boolean": optionalBoolean,
364
+ "optional-positive-int": optionalPositiveInt,
365
+ "optional-positive-finite": optionalPositiveFinite,
366
+ "optional-non-negative": optionalFiniteNonNegative,
367
+ "optional-date": optionalDate,
368
+ "optional-function": optionalFunction,
369
+ "optional-plain-object": optionalPlainObject,
370
+ "optional-port": optionalPort,
371
+ // numeric-bounds finite-int family (rejects Infinity/NaN, unlike the
372
+ // optional-positive-int sugar) — so byte/count caps declare in the shape
373
+ // rather than a separate numericBounds call outside it.
374
+ "optional-positive-finite-int": numericBounds.requirePositiveFiniteIntIfPresent,
375
+ "optional-non-negative-finite-int": numericBounds.requireNonNegativeFiniteIntIfPresent,
376
+ "required-positive-finite-int": numericBounds.requirePositiveFiniteInt,
377
+ };
378
+
379
+ // shape — declarative opts validator. Collapses the `requireObject(opts) +
380
+ // requireNonEmptyString(opts.a) + optionalPositiveFinite(opts.b) + ...` preamble
381
+ // that every create()/build() factory re-rolls into one schema-driven call.
382
+ // The schema is expressive enough that a factory never has to contort its
383
+ // validation to fit a fixed vocabulary — each opt's rule is ANY of:
384
+ //
385
+ // - a rule TOKEN (see _SHAPE_RULES) — sugar for the common per-field checks,
386
+ // using the call-wide `code`;
387
+ // - a per-field DESCRIPTOR `{ rule: "<token>", code?, label? }` — keeps a
388
+ // DISTINCT per-field code (BAD_THIRD_PARTY vs BAD_CONSUMER_REF) or a custom
389
+ // label, so behavior a per-field test asserts is preserved;
390
+ // - an injected-DEPENDENCY `{ methods: [...], optional?, code?, label? }` —
391
+ // validated via requireMethods;
392
+ // - a NESTED `{ shape: {...}, optional?, code?, label? }` — recurses into a
393
+ // sub-object field (e.g. opts.authServer.{issuer,jwksUri});
394
+ // - an arbitrary VALIDATOR FUNCTION `(value, label, errorClass, code, opts)
395
+ // => void` that throws on invalid — the universal hatch for a bespoke check.
396
+ // It receives the whole `opts` as the 5th arg, so CROSS-FIELD logic ("field
397
+ // B required when opts.a === X", a custom message, a numeric-bounds call)
398
+ // lives IN the shape rather than as a hand-rolled check outside it.
399
+ //
400
+ // Validates `opts` is an object, then dispatches each declared field; returns
401
+ // opts. An unknown rule token throws at call time — a schema typo is the
402
+ // author's bug, surfaced loudly rather than silently skipping a field.
403
+ //
404
+ // The schema is ALWAYS the authoritative, exhaustive opts contract — there is
405
+ // no opt-in: any key present on `opts` that the schema does not declare (nor
406
+ // list in `options.allow`) is rejected. This is mandatory by design, so the
407
+ // "future code adds an opt but forgets to validate it" gap cannot reopen — an
408
+ // undeclared opt can't be silently consumed, because it is refused here;
409
+ // adding it forces declaring its rule in the schema, where it is validated.
410
+ // (A field declared in the schema is always validated; exhaustiveness adds the
411
+ // converse — nothing reaches the body unvalidated.) `options.allow` is the only
412
+ // escape: an explicit list of keys a factory forwards to a sub-component rather
413
+ // than validating locally — there is no "skip the contract" mode.
414
+ function shape(opts, schema, callerLabel, errorClass, code, options) {
415
+ requireObject(opts, callerLabel, errorClass, code);
416
+ var fields = Object.keys(schema);
417
+ for (var i = 0; i < fields.length; i += 1) {
418
+ var field = fields[i];
419
+ var rule = schema[field];
420
+ var fieldCode = code;
421
+ var label = (callerLabel || "opts") + ": " + field;
422
+ var value = opts[field];
423
+ // Arbitrary validator function — the universal hatch. Receives the whole
424
+ // `opts` as a 5th arg so a rule can do CROSS-FIELD validation (e.g. "field B
425
+ // is required when opts.a === 'material'") without dropping back to inline
426
+ // checks outside the shape.
427
+ if (typeof rule === "function") { rule(value, label, errorClass, fieldCode, opts); continue; }
428
+ if (rule && typeof rule === "object") {
429
+ if (Array.isArray(rule.methods)) {
430
+ if (rule.optional && (value === undefined || value === null)) continue;
431
+ requireMethods(value, rule.methods, rule.label || label, errorClass, rule.code || code, rule.permanent);
432
+ continue;
433
+ }
434
+ // Nested sub-object: validate it is an object, then recurse.
435
+ if (rule.shape && typeof rule.shape === "object") {
436
+ if (rule.optional && (value === undefined || value === null)) continue;
437
+ requireObject(value, rule.label || label, errorClass, rule.code || code);
438
+ shape(value, rule.shape, rule.label || label, errorClass, rule.code || code);
439
+ continue;
440
+ }
441
+ // Per-field descriptor: { rule, code?, label? }.
442
+ if (typeof rule.rule === "string") {
443
+ if (typeof rule.code === "string") fieldCode = rule.code;
444
+ if (typeof rule.label === "string") label = rule.label;
445
+ rule = rule.rule;
446
+ } else {
447
+ _throw(errorClass, code, (callerLabel || "opts") +
448
+ ": unsupported shape rule object for field " + field,
449
+ "validate-opts/bad-shape-rule");
450
+ }
451
+ }
452
+ if (rule === "required-object") { requireObject(value, label, errorClass, fieldCode); continue; }
453
+ var fn = _SHAPE_RULES[rule];
454
+ if (typeof fn !== "function") {
455
+ _throw(errorClass, code, (callerLabel || "opts") +
456
+ ": unknown shape rule " + JSON.stringify(rule) + " for field " + field,
457
+ "validate-opts/bad-shape-rule");
458
+ }
459
+ fn(value, label, errorClass, fieldCode);
460
+ }
461
+ // Exhaustive contract enforcement — always on (the schema is complete).
462
+ var declared = Object.create(null);
463
+ for (var d = 0; d < fields.length; d += 1) declared[fields[d]] = true;
464
+ var allowList = (options && options.allow) || [];
465
+ for (var a = 0; a < allowList.length; a += 1) declared[allowList[a]] = true;
466
+ var present = Object.keys(opts);
467
+ for (var p = 0; p < present.length; p += 1) {
468
+ if (!declared[present[p]]) {
469
+ _throw(errorClass, code, (callerLabel || "opts") +
470
+ ": unknown opt " + JSON.stringify(present[p]) +
471
+ " (not in the validated shape; add it to the schema or pass options.allow)",
472
+ "validate-opts/unknown-opt");
473
+ }
474
+ }
475
+ return opts;
476
+ }
477
+
334
478
  // makeAuditEmitter — closure factory parallel to safeAsync.makeDropCallback.
335
479
  // Replaces the per-file `function _emit(action, info) { if (!audit) return;
336
480
  // try { audit.safeEmit(Object.assign({ action: action }, info || {})); }
@@ -412,7 +556,7 @@ function assignOwnEnumerable(target, source, reservedKeys) {
412
556
  var entries = [];
413
557
  for (var i = 0; i < keys.length; i += 1) {
414
558
  var k = keys[i];
415
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
559
+ if (pick.isPoisonedKey(k)) continue;
416
560
  if (reserved[k]) continue;
417
561
  entries.push([k, source[k]]);
418
562
  }
@@ -445,6 +589,7 @@ module.exports.auditShape = auditShape;
445
589
  module.exports.optionalBoolean = optionalBoolean;
446
590
  module.exports.optionalPositiveInt = optionalPositiveInt;
447
591
  module.exports.optionalFiniteNonNegative = optionalFiniteNonNegative;
592
+ module.exports.optionalDate = optionalDate;
448
593
  module.exports.optionalPositiveFinite = optionalPositiveFinite;
449
594
  module.exports.optionalPort = optionalPort;
450
595
  module.exports.optionalFunction = optionalFunction;
@@ -456,6 +601,7 @@ module.exports.requireNonEmptyString = requireNonEmptyString;
456
601
  module.exports.observabilityShape = observabilityShape;
457
602
  module.exports.requireObject = requireObject;
458
603
  module.exports.requireMethods = requireMethods;
604
+ module.exports.shape = shape;
459
605
  module.exports.applyDefaults = applyDefaults;
460
606
  module.exports.makeAuditEmitter = makeAuditEmitter;
461
607
  module.exports.makeNamespacedEmitters = makeNamespacedEmitters;
@@ -681,6 +681,11 @@ module.exports = {
681
681
  init: init,
682
682
  seal: seal,
683
683
  unseal: unseal,
684
+ // The default sealed-storage Store: a { seal, unseal } pair backed by the
685
+ // in-process vault key. b.cert.create (and other sealed-disk consumers)
686
+ // resolve `opts.vault || getDefaultStore()`; documented in their @opts.
687
+ getDefaultStore: function () { return { seal: seal, unseal: unseal }; },
688
+ Store: { seal: seal, unseal: unseal },
684
689
  getDerivedHashSalt: getDerivedHashSalt,
685
690
  getDerivedHashMacKey: getDerivedHashMacKey,
686
691
  _zeroizeAndReplace: _zeroizeAndReplace,
@@ -38,6 +38,7 @@
38
38
  var nodeFs = require("node:fs");
39
39
  var nodePath = require("node:path");
40
40
  var atomicFile = require("../atomic-file");
41
+ var C = require("../constants");
41
42
  var frameworkFiles = require("../framework-files");
42
43
  var vaultWrap = require("./wrap");
43
44
  var { defineClass } = require("../framework-error");
@@ -75,17 +76,6 @@ function _requirePassphrase(opts, fieldName) {
75
76
  }
76
77
  }
77
78
 
78
- // fsync-by-path semantic: open then sync then close. atomicFile.fsync
79
- // expects an already-open fd; this wrapper opens the file we just
80
- // wrote, flushes its contents, and closes — the right shape when we
81
- // don't have the original write fd around.
82
- function _fsyncPath(p) {
83
- try {
84
- var fd = nodeFs.openSync(p, "r+");
85
- try { atomicFile.fsync(fd); } finally { nodeFs.closeSync(fd); }
86
- } catch (_e) { /* best-effort across filesystems */ }
87
- }
88
-
89
79
  // ---- Pre-flight checks (no side effects) ----
90
80
 
91
81
  function preflightSealable(opts) {
@@ -142,16 +132,18 @@ async function seal(opts) {
142
132
  var p = _paths(opts.dataDir);
143
133
  var keepPlaintext = !!opts.keepPlaintext;
144
134
 
145
- var plainBytes = nodeFs.readFileSync(p.plaintext);
135
+ var plainBytes = atomicFile.fdSafeReadSync(p.plaintext, { maxBytes: C.BYTES.kib(64) });
146
136
  var sealedBytes = await vaultWrap.wrap(plainBytes, opts.passphrase);
147
137
 
148
- // Step 1: write sealed.tmp + fsync
149
- nodeFs.writeFileSync(p.sealedTmp, sealedBytes, { mode: 0o600 });
150
- _fsyncPath(p.sealedTmp);
138
+ // Step 1: write sealed.tmp (exclusive + no-follow create, fsynced) — a
139
+ // bare writeFileSync to this PREDICTABLE temp name would follow a symlink
140
+ // pre-planted at sealed.tmp (CWE-59); writeExclSync clears any stale entry
141
+ // then creates with O_EXCL | O_NOFOLLOW and fsyncs the bytes.
142
+ atomicFile.writeExclSync(p.sealedTmp, sealedBytes, { fileMode: 0o600 });
151
143
  atomicFile.fsyncDir(opts.dataDir);
152
144
 
153
145
  // Step 2: round-trip verify the .tmp before committing the rename
154
- var verifyBytes = nodeFs.readFileSync(p.sealedTmp);
146
+ var verifyBytes = atomicFile.fdSafeReadSync(p.sealedTmp, { maxBytes: C.BYTES.kib(64) });
155
147
  var unwrapped;
156
148
  try {
157
149
  unwrapped = await vaultWrap.unwrap(verifyBytes, opts.passphrase);
@@ -195,7 +187,7 @@ async function unseal(opts) {
195
187
  }
196
188
  var p = _paths(opts.dataDir);
197
189
 
198
- var sealedBytes = nodeFs.readFileSync(p.sealed);
190
+ var sealedBytes = atomicFile.fdSafeReadSync(p.sealed, { maxBytes: C.BYTES.kib(64) });
199
191
  var plainBytes;
200
192
  try {
201
193
  plainBytes = await vaultWrap.unwrap(sealedBytes, opts.passphrase);
@@ -205,13 +197,15 @@ async function unseal(opts) {
205
197
  " — " + SEALED_NAME + " is UNCHANGED");
206
198
  }
207
199
 
208
- // Step 1: write plaintext.tmp + fsync
209
- nodeFs.writeFileSync(p.plaintextTmp, plainBytes, { mode: 0o600 });
210
- _fsyncPath(p.plaintextTmp);
200
+ // Step 1: write plaintext.tmp (exclusive + no-follow create, fsynced) — a
201
+ // bare writeFileSync to this PREDICTABLE temp name would follow a symlink
202
+ // pre-planted at plaintext.tmp (CWE-59); writeExclSync clears any stale
203
+ // entry then creates with O_EXCL | O_NOFOLLOW and fsyncs the bytes.
204
+ atomicFile.writeExclSync(p.plaintextTmp, plainBytes, { fileMode: 0o600 });
211
205
  atomicFile.fsyncDir(opts.dataDir);
212
206
 
213
207
  // Step 2: round-trip sanity — re-read tmp and verify
214
- var verifyBytes = nodeFs.readFileSync(p.plaintextTmp);
208
+ var verifyBytes = atomicFile.fdSafeReadSync(p.plaintextTmp, { maxBytes: C.BYTES.kib(64) });
215
209
  if (Buffer.compare(verifyBytes, plainBytes) !== 0) {
216
210
  try { nodeFs.unlinkSync(p.plaintextTmp); } catch (_e) { /* cleanup */ }
217
211
  throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
@@ -246,7 +240,7 @@ async function rotate(opts) {
246
240
  }
247
241
  var p = _paths(opts.dataDir);
248
242
 
249
- var sealedBytes = nodeFs.readFileSync(p.sealed);
243
+ var sealedBytes = atomicFile.fdSafeReadSync(p.sealed, { maxBytes: C.BYTES.kib(64) });
250
244
  var plainBytes;
251
245
  try {
252
246
  plainBytes = await vaultWrap.unwrap(sealedBytes, opts.oldPassphrase);
@@ -257,14 +251,16 @@ async function rotate(opts) {
257
251
  }
258
252
  var newSealedBytes = await vaultWrap.wrap(plainBytes, opts.newPassphrase);
259
253
 
260
- // Step 1: write new sealed.tmp + fsync
261
- nodeFs.writeFileSync(p.sealedTmp, newSealedBytes, { mode: 0o600 });
262
- _fsyncPath(p.sealedTmp);
254
+ // Step 1: write new sealed.tmp (exclusive + no-follow create, fsynced) — a
255
+ // bare writeFileSync to this PREDICTABLE temp name would follow a symlink
256
+ // pre-planted at sealed.tmp (CWE-59); writeExclSync clears any stale entry
257
+ // then creates with O_EXCL | O_NOFOLLOW and fsyncs the bytes.
258
+ atomicFile.writeExclSync(p.sealedTmp, newSealedBytes, { fileMode: 0o600 });
263
259
  atomicFile.fsyncDir(opts.dataDir);
264
260
 
265
261
  // Step 2: round-trip verify with NEW passphrase, AND assert unwrap
266
262
  // with the OLD passphrase fails — otherwise the rotation didn't take.
267
- var verifyBytes = nodeFs.readFileSync(p.sealedTmp);
263
+ var verifyBytes = atomicFile.fdSafeReadSync(p.sealedTmp, { maxBytes: C.BYTES.kib(64) });
268
264
  var verifyPlain;
269
265
  try { verifyPlain = await vaultWrap.unwrap(verifyBytes, opts.newPassphrase); }
270
266
  catch (e) {
@@ -23,10 +23,10 @@
23
23
  * exposure to later env-dump surfaces. This doesn't zero the memory
24
24
  * (JavaScript can't) but does remove the env-object reference.
25
25
  */
26
- var nodeFs = require("node:fs");
27
26
  var readline = require("node:readline");
28
27
  var safeEnv = require("../parsers/safe-env");
29
28
  var safeBuffer = require("../safe-buffer");
29
+ var atomicFile = require("../atomic-file");
30
30
 
31
31
  var MAX_PASSPHRASE_BYTES = 4096;
32
32
 
@@ -83,9 +83,14 @@ async function fromFile(filePath, opts) {
83
83
  }
84
84
  var raw;
85
85
  try {
86
- raw = nodeFs.readFileSync(filePath);
86
+ // Cap the passphrase-file read at MAX_PASSPHRASE_BYTES at READ time (was only
87
+ // checked AFTER the whole file was in memory), so a swapped multi-GiB target
88
+ // can't OOM boot before validatePassphraseBuffer. NO refuseSymlink: the
89
+ // BLAMEJS_VAULT_PASSPHRASE_FILE is frequently a k8s projected-secret mount,
90
+ // which is a symlink chain — following it is correct.
91
+ raw = atomicFile.fdSafeReadSync(filePath, { maxBytes: MAX_PASSPHRASE_BYTES });
87
92
  } catch (e) {
88
- throw new Error("failed to read " + envVars.file + " (" + filePath + "): " + e.code);
93
+ throw new Error("failed to read " + envVars.file + " (" + filePath + "): " + (e.code || e.message));
89
94
  }
90
95
  var buf = trimTrailingNewlines(raw);
91
96
  validatePassphraseBuffer(buf, "file source (" + filePath + ")");
@@ -493,16 +493,11 @@ function _emit(cb, ev) {
493
493
  // staging dir is already 0o700 owner-only, so this is defense in depth
494
494
  // against a same-user pre-plant / symlink swap (CWE-377 / CWE-379 / CWE-59).
495
495
  function _writeStagedFileExclusive(p, data) {
496
- try { nodeFs.unlinkSync(p); } catch (_e) { /* no stale entry to clear */ }
497
- var fd = nodeFs.openSync(p,
498
- nodeFs.constants.O_WRONLY | nodeFs.constants.O_CREAT |
499
- nodeFs.constants.O_EXCL | (nodeFs.constants.O_NOFOLLOW || 0), 0o600);
500
- try {
501
- nodeFs.writeFileSync(fd, data);
502
- nodeFs.fsyncSync(fd);
503
- } finally {
504
- nodeFs.closeSync(fd);
505
- }
496
+ // The clear-stale + O_EXCL|O_NOFOLLOW create + write + fsync sequence now
497
+ // lives in the atomic-file primitive (also used by the vault passphrase
498
+ // seal/unseal staging writes) so every staged exclusive write shares one
499
+ // implementation.
500
+ atomicFile.writeExclSync(p, data, { fileMode: 0o600 });
506
501
  }
507
502
 
508
503
  function _reSealValue(sealedValue, oldKeys, newKeys) {
@@ -759,7 +754,7 @@ async function rotate(opts) {
759
754
  // Stage via the exclusive-create + fsync helper rather than a plain copy,
760
755
  // so the verbatim file is durable at write time (no later by-path fsync)
761
756
  // and a pre-planted file/symlink at the staging path hard-fails.
762
- _writeStagedFileExclusive(dest, nodeFs.readFileSync(src));
757
+ _writeStagedFileExclusive(dest, atomicFile.fdSafeReadSync(src, { maxBytes: C.BYTES.mib(64) }));
763
758
  }
764
759
  for (var vd = 0; vd < paths.verbatimDirs.length; vd++) {
765
760
  var dent = paths.verbatimDirs[vd];
@@ -797,7 +792,7 @@ async function rotate(opts) {
797
792
  var dbKeySealedPath = nodePath.join(dataDir, paths.dbKeySealed);
798
793
  var dbKey = null;
799
794
  if (nodeFs.existsSync(dbKeySealedPath)) {
800
- var sealedKey = nodeFs.readFileSync(dbKeySealedPath, "utf8").trim();
795
+ var sealedKey = atomicFile.fdSafeReadSync(dbKeySealedPath, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }).trim();
801
796
  if (vaultAad.isAadSealed(sealedKey)) {
802
797
  // AAD-bound db.key.enc (db.js since v0.14.7): unseal under the OLD
803
798
  // root with the deployment-context AAD, then re-emit under the NEW
@@ -832,7 +827,7 @@ async function rotate(opts) {
832
827
  }
833
828
  continue;
834
829
  }
835
- var current = nodeFs.readFileSync(asSrc, "utf8").trim();
830
+ var current = atomicFile.fdSafeReadSync(asSrc, { maxBytes: C.BYTES.mib(1), encoding: "utf8" }).trim();
836
831
  if (current.indexOf(C.VAULT_PREFIX) !== 0) {
837
832
  throw new VaultRotateError("vault-rotate/bad-sealed",
838
833
  "rotate: sealed file does not start with the vault prefix: " + ase.relativePath);
@@ -854,11 +849,11 @@ async function rotate(opts) {
854
849
  // Stage via the exclusive-create + fsync helper (not a plain copy) so the
855
850
  // salt is durable at write time and no later by-path fsync is needed.
856
851
  _writeStagedFileExclusive(nodePath.join(stagingDir, "vault.derived-hash-salt"),
857
- nodeFs.readFileSync(saltSrc));
852
+ atomicFile.fdSafeReadSync(saltSrc, { maxBytes: C.BYTES.kib(4) }));
858
853
  }
859
854
  var macSrc = nodePath.join(dataDir, "vault.derived-hash-mac.sealed");
860
855
  if (nodeFs.existsSync(macSrc)) {
861
- var macCurrent = nodeFs.readFileSync(macSrc, "utf8").trim();
856
+ var macCurrent = atomicFile.fdSafeReadSync(macSrc, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }).trim();
862
857
  if (macCurrent.indexOf(C.VAULT_PREFIX) === 0) {
863
858
  _writeStagedFileExclusive(nodePath.join(stagingDir, "vault.derived-hash-mac.sealed"),
864
859
  _reSealValue(macCurrent, oldKeys, newKeys));
@@ -873,7 +868,7 @@ async function rotate(opts) {
873
868
  var verifyResult = null;
874
869
 
875
870
  if (nodeFs.existsSync(encDbPath) && dbKey) {
876
- var packed = nodeFs.readFileSync(encDbPath);
871
+ var packed = atomicFile.fdSafeReadSync(encDbPath, { maxBytes: C.BYTES.gib(2) });
877
872
  // db.enc is XChaCha20-Poly1305-sealed AAD-bound to its dataDir
878
873
  // (db.js _dbEncAad). Read with the dataDir AAD; retry without AAD for
879
874
  // pre-AAD envelopes (mirrors db.js:765-768). The in-place swap keeps
@@ -945,7 +940,7 @@ async function rotate(opts) {
945
940
  // _writeStagedFileExclusive — exclusive + no-follow create, owner-only
946
941
  // 0o600 — so a same-user pre-plant or symlink swap is a hard failure
947
942
  // rather than a followed write, and the bytes never inherit a wider mode.
948
- var rotatedBytes = nodeFs.readFileSync(tmpDbPath);
943
+ var rotatedBytes = atomicFile.fdSafeReadSync(tmpDbPath, { maxBytes: C.BYTES.gib(2) });
949
944
  // Re-encrypt under the SAME dataDir AAD so db.init's AAD-first open
950
945
  // succeeds after the staged dir is swapped over dataDir in place.
951
946
  _writeStagedFileExclusive(nodePath.join(stagingDir, paths.encryptedDb),
@@ -956,7 +951,7 @@ async function rotate(opts) {
956
951
  _emit(progress, { phase: "verify" });
957
952
  var verifyTmp = nodePath.join(stagingDir, "_blamejs_verify.tmp.db");
958
953
  _writeStagedFileExclusive(verifyTmp,
959
- bCrypto.decryptPacked(nodeFs.readFileSync(nodePath.join(stagingDir, paths.encryptedDb)), dbKey, dbEncAad));
954
+ bCrypto.decryptPacked(atomicFile.fdSafeReadSync(nodePath.join(stagingDir, paths.encryptedDb), { maxBytes: C.BYTES.gib(2) }), dbKey, dbEncAad));
960
955
  var vdb = new DatabaseSync(verifyTmp);
961
956
  try {
962
957
  verifyResult = verify({ keys: newKeys, db: vdb, oldKeys: oldKeys });
@@ -185,7 +185,6 @@ function sealPemFile(opts) {
185
185
  // optionalPositiveFinite above already threw on a bad-shaped opts.pollInterval;
186
186
  // here only undefined / null / valid-positive-finite remain.
187
187
  var pollInterval = opts.pollInterval || DEFAULT_POLL_MS;
188
- var auditOn = opts.audit !== false;
189
188
  var onResealed = typeof opts.onResealed === "function" ? opts.onResealed : null;
190
189
  var onError = typeof opts.onError === "function" ? opts.onError : null;
191
190
  validateOpts.optionalPositiveFinite(opts.maxSourceBytes,
@@ -200,16 +199,7 @@ function sealPemFile(opts) {
200
199
  var resealing = false;
201
200
  var pendingMtime = null;
202
201
 
203
- function _emitAudit(action, outcome, metadata) {
204
- if (!auditOn) return;
205
- try {
206
- audit().safeEmit({
207
- action: "vault.seal_pem_file." + action,
208
- outcome: outcome,
209
- metadata: metadata || {},
210
- });
211
- } catch (_e) { /* drop-silent */ }
212
- }
202
+ var _emitAudit = audit().namespaced("vault.seal_pem_file", opts.audit);
213
203
 
214
204
  function _writeSealed(plaintextBytes) {
215
205
  // atomicFile.writeSync already does the .tmp + fsync + rename +
@@ -242,7 +232,10 @@ function sealPemFile(opts) {
242
232
  }
243
233
  }
244
234
  var sealed = vault().seal(plaintextBytes);
245
- nodeFs.writeFileSync(markerPath, String(Date.now()), { mode: 0o600 }); // POSIX file mode
235
+ // Atomic, symlink-refusing marker write (matches the destination write
236
+ // just below) — a bare writeFileSync would follow a symlink planted at
237
+ // markerPath (CWE-59).
238
+ atomicFile.writeSync(markerPath, String(Date.now()), { fileMode: 0o600 }); // POSIX file mode
246
239
  try {
247
240
  atomicFile.writeSync(destination, sealed, { fileMode: 0o600 }); // POSIX file mode
248
241
  } catch (e) {
@@ -274,43 +267,35 @@ function sealPemFile(opts) {
274
267
  // source isn't a symlink we don't expect, then read via fd so
275
268
  // a swap-after-stat doesn't change which bytes we read.
276
269
  try {
277
- var lstat = nodeFs.lstatSync(source);
278
- if (lstat.isSymbolicLink()) {
279
- throw new SealPemFileError("seal-pem-file/symlink-refused",
280
- "source is a symlink (refused; follow + re-stat opens TOCTOU)");
281
- }
282
- if (lstat.size > maxSourceBytes) {
283
- throw new SealPemFileError("seal-pem-file/source-too-large",
284
- "source size " + lstat.size + " exceeds maxSourceBytes " + maxSourceBytes);
285
- }
286
- // CodeQL js/file-system-race: the open-fd + fstat + inode-equality
287
- // check (this block and the next) IS the TOCTOU defense. lstat ran
288
- // above, then we open(O_NOFOLLOW-equivalent via the symlink refusal
289
- // above) and rebind every subsequent measurement to the fd's inode.
290
- // Any swap between lstat and open is detected by the fstat.ino !==
291
- // lstat.ino branch below and refused as toctou-detected.
292
- var fd = nodeFs.openSync(source, "r");
293
- try {
294
- var fstat = nodeFs.fstatSync(fd);
295
- // H6 #3 confirm the fd points at the same inode lstat saw.
296
- if (fstat.ino !== lstat.ino || fstat.size > maxSourceBytes) {
297
- throw new SealPemFileError("seal-pem-file/toctou-detected",
298
- "source mutated between lstat and open (TOCTOU defense)");
299
- }
300
- plaintext = Buffer.alloc(fstat.size);
301
- var read = 0;
302
- while (read < fstat.size) {
303
- var n = nodeFs.readSync(fd, plaintext, read, fstat.size - read, null);
304
- if (n === 0) break;
305
- read += n;
306
- }
307
- if (read !== fstat.size) {
308
- throw new SealPemFileError("seal-pem-file/short-read",
309
- "short read: " + read + " of " + fstat.size + " bytes");
310
- }
311
- } finally {
312
- try { nodeFs.closeSync(fd); } catch (_e) { /* close best-effort */ }
313
- }
270
+ // TOCTOU-safe read via atomic-file with the strongest guards:
271
+ // symlink refusal + inode-equality + a byte cap. The bespoke
272
+ // SealPemFileError codes/messages are preserved via errorFor; this
273
+ // call sits inside the outer try/catch that wraps any failure into
274
+ // seal-pem-file/source-read-failed + audit + the onError callback.
275
+ plaintext = atomicFile.fdSafeReadSync(source, {
276
+ refuseSymlink: true,
277
+ inodeCheck: true,
278
+ maxBytes: maxSourceBytes,
279
+ errorFor: function (kind, detail) {
280
+ if (kind === "symlink") {
281
+ return new SealPemFileError("seal-pem-file/symlink-refused",
282
+ "source is a symlink (refused; follow + re-stat opens TOCTOU)");
283
+ }
284
+ if (kind === "too-large") {
285
+ return new SealPemFileError("seal-pem-file/source-too-large",
286
+ "source size " + detail.size + " exceeds maxSourceBytes " + maxSourceBytes);
287
+ }
288
+ if (kind === "toctou") {
289
+ return new SealPemFileError("seal-pem-file/toctou-detected",
290
+ "source mutated between lstat and open (TOCTOU defense)");
291
+ }
292
+ if (kind === "short-read") {
293
+ return new SealPemFileError("seal-pem-file/short-read",
294
+ "short read: " + detail.read + " of " + detail.size + " bytes");
295
+ }
296
+ return undefined;
297
+ },
298
+ });
314
299
  }
315
300
  catch (e) {
316
301
  var err = new SealPemFileError("seal-pem-file/source-read-failed",
package/lib/vault-aad.js CHANGED
@@ -48,6 +48,7 @@
48
48
 
49
49
  var lazyRequire = require("./lazy-require");
50
50
  var validateOpts = require("./validate-opts");
51
+ var pick = require("./pick");
51
52
  var C = require("./constants");
52
53
  var { defineClass } = require("./framework-error");
53
54
  var VaultAadError = defineClass("VaultAadError", { alwaysPermanent: true });
@@ -80,7 +81,7 @@ function _canonicalize(parts) {
80
81
  chunks.push(Buffer.from([AAD_VERSION]));
81
82
  for (var i = 0; i < keys.length; i += 1) {
82
83
  var key = keys[i];
83
- if (key === "__proto__" || key === "constructor" || key === "prototype") {
84
+ if (pick.isPoisonedKey(key)) {
84
85
  throw new VaultAadError("vault-aad/bad-aad",
85
86
  "AAD field name " + JSON.stringify(key) + " is forbidden (poisoned key)");
86
87
  }
@@ -130,7 +131,7 @@ function buildContextAad(parts) {
130
131
  var out = {};
131
132
  for (var k in parts) {
132
133
  if (!Object.prototype.hasOwnProperty.call(parts, k)) continue;
133
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
134
+ if (pick.isPoisonedKey(k)) continue;
134
135
  if (parts[k] == null) continue;
135
136
  out[k] = parts[k];
136
137
  }
package/lib/vc.js CHANGED
@@ -226,7 +226,7 @@ function _verifyJose(token, opts, expectedTyp) {
226
226
  throw new VcError("vc/crit-unsupported",
227
227
  "vc.verify: JWS 'crit' header lists extensions this verifier does not support (RFC 7515 §4.1.11)");
228
228
  }
229
- if (header.alg === "none" || !JOSE_ALGS[header.alg]) {
229
+ if (header.alg === "none" || !Object.prototype.hasOwnProperty.call(JOSE_ALGS, header.alg)) {
230
230
  throw new VcError("vc/bad-alg", "vc.verify: unsupported or unsecured JWS alg '" + header.alg + "'");
231
231
  }
232
232
  if (opts.algorithms.indexOf(header.alg) === -1) {
@@ -316,13 +316,8 @@ async function verify(secured, opts) {
316
316
  if (!opts.publicKey && typeof opts.keyResolver !== "function") {
317
317
  throw new VcError("vc/no-key", "vc.verify: pass publicKey or keyResolver");
318
318
  }
319
- var at = new Date();
320
- if (opts.at !== undefined && opts.at !== null) {
321
- if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
322
- throw new VcError("vc/bad-at", "vc.verify: opts.at must be a valid Date");
323
- }
324
- at = opts.at;
325
- }
319
+ validateOpts.optionalDate(opts.at, "vc.verify: opts.at", VcError, "vc/bad-at");
320
+ var at = (opts.at !== undefined && opts.at !== null) ? opts.at : new Date();
326
321
 
327
322
  var result = await _verifySecured(secured, opts, JOSE_TYP, COSE_TYP);
328
323