@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
@@ -283,10 +283,16 @@ function list(opts) {
283
283
  var p = nodePath.join(rollbackRoot, name);
284
284
  var markerPath = p + ".marker.json";
285
285
  var marker = null;
286
- if (nodeFs.existsSync(markerPath)) {
287
- try { marker = safeJson.parse(nodeFs.readFileSync(markerPath, "utf8"), { maxBytes: C.BYTES.kib(64) }); }
288
- catch (_e) { marker = null; }
289
- }
286
+ // Capped fd-bound read (no existsSync check-then-read window): the fs read is
287
+ // now bounded to 64 KiB too, so a tampered multi-GB marker.json is refused
288
+ // BEFORE allocation (the parse-only cap let readFileSync slurp the whole file
289
+ // first). refuseSymlink: the marker lives under the operator's rollbackRoot,
290
+ // never a secret-mount. Any failure → marker:null, the best-effort behavior.
291
+ try {
292
+ marker = safeJson.parse(
293
+ atomicFile.fdSafeReadSync(markerPath, { maxBytes: C.BYTES.kib(64), encoding: "utf8", refuseSymlink: true }),
294
+ { maxBytes: C.BYTES.kib(64) });
295
+ } catch (_e) { marker = null; }
290
296
  var stat;
291
297
  try { stat = nodeFs.statSync(p); } catch (_e) { continue; }
292
298
  out.push({
package/lib/restore.js CHANGED
@@ -59,9 +59,8 @@ var bCrypto = require("./crypto");
59
59
  var numericChecks = require("./numeric-checks");
60
60
  var restoreBundle = require("./restore-bundle");
61
61
  var restoreRollback = require("./restore-rollback");
62
- var lazyRequire = require("./lazy-require");
63
62
  var validateOpts = require("./validate-opts");
64
- var audit = lazyRequire(function () { return require("./audit"); });
63
+ var auditEmit = require("./audit-emit");
65
64
  var { FrameworkError } = require("./framework-error");
66
65
 
67
66
  class RestoreError extends FrameworkError {
@@ -74,17 +73,9 @@ class RestoreError extends FrameworkError {
74
73
  }
75
74
 
76
75
  function _validateStorage(storage) {
77
- if (!storage || typeof storage !== "object") {
78
- throw new RestoreError("restore/bad-storage",
79
- "storage backend is required (use b.backup.diskStorage or pass a custom one)");
80
- }
81
- var required = ["readBundle", "listBundles", "hasBundle"];
82
- for (var i = 0; i < required.length; i++) {
83
- if (typeof storage[required[i]] !== "function") {
84
- throw new RestoreError("restore/bad-storage",
85
- "storage backend missing method '" + required[i] + "'");
86
- }
87
- }
76
+ validateOpts.requireMethods(storage,
77
+ ["readBundle", "listBundles", "hasBundle"],
78
+ "storage backend", RestoreError, "restore/bad-storage");
88
79
  }
89
80
 
90
81
  function create(opts) {
@@ -92,6 +83,7 @@ function create(opts) {
92
83
  validateOpts(opts, [
93
84
  "dataDir", "storage", "passphrase", "rollbackRoot", "audit",
94
85
  "maxPulledBytes", "maxPulledFiles",
86
+ "requireSignature", "expectedFingerprint", "verifySignature",
95
87
  ], "restore");
96
88
  validateOpts.requireNonEmptyString(opts.dataDir, "create: opts.dataDir", RestoreError, "restore/no-datadir");
97
89
  _validateStorage(opts.storage);
@@ -105,6 +97,19 @@ function create(opts) {
105
97
  var passphrase = opts.passphrase;
106
98
  var rollbackRoot = opts.rollbackRoot || (dataDir + ".rollbacks");
107
99
  var auditOn = opts.audit !== false;
100
+ // Manifest-signature policy. The framework signs bundles best-effort (an
101
+ // unsigned bundle is the documented CLI / standalone / worker case), so the
102
+ // non-opt-in integrity default is the per-blob AEAD path-binding below
103
+ // (which defeats the blob-remap attack on EVERY bundle, signed or not);
104
+ // requireSignature is the additional provenance policy operators under
105
+ // HIPAA/PCI opt into to mandate a verified signer. expectedFingerprint pins
106
+ // a signer; verifySignature:false allows a cold/cross-org restore. All
107
+ // three are threaded into restoreBundle.extract on every run — previously
108
+ // omitted entirely, so a present signature was never even verified and a
109
+ // requireSignature policy could not be enforced (CWE-347).
110
+ var requireSignature = opts.requireSignature === true;
111
+ var expectedFingerprint = opts.expectedFingerprint;
112
+ var verifySignature = opts.verifySignature;
108
113
 
109
114
  // Preflight footprint caps. Defended against storage that returns a
110
115
  // tampered or oversized bundle: we cap both the storage-reported size
@@ -152,15 +157,7 @@ function create(opts) {
152
157
  return { totalBytes: totalBytes, fileCount: fileCount };
153
158
  }
154
159
 
155
- function _emitAudit(action, info, outcome) {
156
- if (!auditOn) return;
157
- audit().safeEmit({
158
- action: action,
159
- outcome: outcome,
160
- metadata: info || {},
161
- reason: info && info.reason ? info.reason : null,
162
- });
163
- }
160
+ var _emitAudit = auditEmit.gatedReasonEmitter({ audit: auditOn });
164
161
 
165
162
  async function list() { return await storage.listBundles(); }
166
163
 
@@ -288,6 +285,9 @@ function create(opts) {
288
285
  passphrase: passphrase,
289
286
  filter: runOpts.filter,
290
287
  progressCallback: runOpts.progressCallback,
288
+ requireSignature: requireSignature,
289
+ expectedFingerprint: expectedFingerprint,
290
+ verifySignature: verifySignature,
291
291
  });
292
292
  } catch (e) {
293
293
  _cleanupTmp();
@@ -300,6 +300,8 @@ function create(opts) {
300
300
  else if (code === "restore-bundle/missing-manifest") mappedCode = "restore/missing-manifest";
301
301
  else if (code === "restore-bundle/missing-blob") mappedCode = "restore/missing-blob";
302
302
  else if (code === "restore-bundle/size-mismatch") mappedCode = "restore/size-mismatch";
303
+ else if (code === "restore-bundle/missing-signature") mappedCode = "restore/missing-signature";
304
+ else if (code === "restore-bundle/bad-signature") mappedCode = "restore/bad-signature";
303
305
  _emitAudit("restore.failure",
304
306
  { bundleId: bundleId, reason: (e && e.message) || String(e) }, "failure");
305
307
  throw new RestoreError(mappedCode,
package/lib/retention.js CHANGED
@@ -47,6 +47,7 @@ var lazyRequire = require("./lazy-require");
47
47
  var validateOpts = require("./validate-opts");
48
48
  var safeSql = require("./safe-sql");
49
49
  var sql = require("./sql");
50
+ var numericBounds = require("./numeric-bounds");
50
51
  var { defineClass } = require("./framework-error");
51
52
 
52
53
  var audit = lazyRequire(function () { return require("./audit"); });
@@ -116,11 +117,8 @@ function _validateRule(rule) {
116
117
  throw _err("BAD_RULE",
117
118
  "rule.action string must be 'erase' / 'delete' / 'soft-delete', got " + JSON.stringify(action));
118
119
  }
119
- if (rule.batchSize !== undefined &&
120
- (typeof rule.batchSize !== "number" || !isFinite(rule.batchSize) ||
121
- rule.batchSize <= 0 || Math.floor(rule.batchSize) !== rule.batchSize)) {
122
- throw _err("BAD_RULE", "rule.batchSize must be a positive integer");
123
- }
120
+ numericBounds.requirePositiveFiniteIntIfPresent(rule.batchSize,
121
+ "rule.batchSize", RetentionError, "BAD_RULE");
124
122
  if (rule.softDeleteField !== undefined &&
125
123
  (typeof rule.softDeleteField !== "string" || rule.softDeleteField.length === 0)) {
126
124
  throw _err("BAD_RULE", "rule.softDeleteField must be a non-empty string");
@@ -433,17 +431,29 @@ function create(opts) {
433
431
 
434
432
  try {
435
433
  var moreRows = true;
434
+ // Keyset pagination cursor. Without it the loop re-selects the SAME batch
435
+ // forever whenever a full batch of rows is NOT removed from the candidate
436
+ // set by its action — legal-hold-skipped, "warn"-stage, errored, and
437
+ // (critically) EVERY row under dryRun mutate nothing, so a LIMIT-from-the-
438
+ // top query returns the identical rows each pass and `rows.length ===
439
+ // batchSize` never goes false. Ordering by _id and advancing past the last
440
+ // row seen guarantees forward progress regardless of whether a row was
441
+ // actioned. (Deleted / anonymized rows are also simply skipped past.)
442
+ var lastId = null;
436
443
  while (moreRows) {
437
444
  var rows;
438
445
  // The candidate WHERE-clause: age + not-already-erased + not-on-legal-hold +
439
- // (when soft-delete is configured) not-already-soft-deleted. Built
440
- // through b.sql so the operator-supplied table / ageField / softDeleteField
441
- // identifiers are quoted by construction and every value binds as a
442
- // placeholder (the '' empty-string compare included — no embedded literal).
446
+ // (when soft-delete is configured) not-already-soft-deleted + keyset cursor.
447
+ // Built through b.sql so the operator-supplied table / ageField /
448
+ // softDeleteField identifiers are quoted by construction and every value
449
+ // binds as a placeholder (the '' empty-string compare included — no
450
+ // embedded literal).
443
451
  function _candidateBase() {
444
452
  var qb = sql.select(rule.table, SQL_OPTS)
445
453
  .where(rule.ageField, "<=", cutoff);
446
454
  if (rule.softDeleteField) qb.whereNull(rule.softDeleteField);
455
+ if (lastId !== null) qb.where("_id", ">", lastId);
456
+ qb.orderBy("_id", "asc");
447
457
  return qb;
448
458
  }
449
459
  var selStmt;
@@ -506,6 +516,10 @@ function create(opts) {
506
516
  reason: (e && e.message) || String(e) });
507
517
  }
508
518
  }
519
+ // Advance the keyset cursor past this batch so already-seen rows
520
+ // (including skipped / warned / errored / dry-run rows that stay in the
521
+ // candidate set) are never re-selected — the loop-termination guarantee.
522
+ lastId = rows[rows.length - 1]._id;
509
523
  if (rows.length < rule.batchSize) moreRows = false;
510
524
  }
511
525
  } catch (e) {
package/lib/router.js CHANGED
@@ -36,6 +36,7 @@ var http = require("node:http");
36
36
  var http2 = require("node:http2");
37
37
  var nodeFs = require("node:fs");
38
38
  var nodePath = require("node:path");
39
+ var atomicFile = require("./atomic-file");
39
40
  var C = require("./constants");
40
41
  var requestHelpers = require("./request-helpers");
41
42
  var lazyRequire = require("./lazy-require");
@@ -147,7 +148,11 @@ function _makeSchemaValidator(spec) {
147
148
  if (!qq.ok) return _writeValidationError(req, res, "query", qq.errors);
148
149
  req.query = qq.value;
149
150
  }
150
- if (spec.body && req.body !== undefined) {
151
+ if (spec.body) {
152
+ // Validate even when req.body is undefined (no body parsed / empty POST):
153
+ // a required body schema must report the violation, not be silently
154
+ // skipped. safeParse(undefined) fails a non-optional schema and passes an
155
+ // optional()/default() one. Mirrors the always-run params/query checks.
151
156
  var bb = spec.body.safeParse(req.body);
152
157
  if (!bb.ok) return _writeValidationError(req, res, "body", bb.errors);
153
158
  req.body = bb.value;
@@ -1425,11 +1430,19 @@ function serveStatic(dir) {
1425
1430
  // that shares the root's name as a prefix — e.g. root `/srv/public`
1426
1431
  // vs `/srv/public-evil` — cannot satisfy the containment check.
1427
1432
  if (filePath !== root && !filePath.startsWith(root + nodePath.sep)) return next();
1428
- if (!nodeFs.existsSync(filePath) || nodeFs.statSync(filePath).isDirectory()) return next();
1433
+ // Open the (lexically-confined) path ONCE with O_NOFOLLOW and bind existence,
1434
+ // stat, and streaming to that single fd. The previous existsSync → statSync →
1435
+ // statSync → createReadStream sequence was a triple check-then-read TOCTOU
1436
+ // (CWE-367) on a request-derived pathname, and the plain createReadStream
1437
+ // would follow a symlink swapped in after the lexical check (CWE-22).
1438
+ // Opening before writeHead lets a refusal still fall through to next().
1439
+ var fd;
1440
+ try { fd = atomicFile.openNoFollowSync(filePath); } catch (_e) { return next(); }
1441
+ var stat = nodeFs.fstatSync(fd);
1442
+ if (stat.isDirectory()) { nodeFs.closeSync(fd); return next(); }
1429
1443
 
1430
1444
  var ext = nodePath.extname(filePath).toLowerCase();
1431
1445
  var mime = MIME_TYPES[ext] || "application/octet-stream";
1432
- var stat = nodeFs.statSync(filePath);
1433
1446
  var hasVersion = req.url && req.url.includes("?v=");
1434
1447
  var cacheControl = hasVersion
1435
1448
  ? "public, max-age=31536000, immutable"
@@ -1439,7 +1452,7 @@ function serveStatic(dir) {
1439
1452
  "Content-Length": stat.size,
1440
1453
  "Cache-Control": cacheControl,
1441
1454
  });
1442
- nodeFs.createReadStream(filePath).pipe(res);
1455
+ nodeFs.createReadStream(filePath, { fd: fd }).pipe(res);
1443
1456
  };
1444
1457
  }
1445
1458