@blamejs/core 0.4.28 → 0.4.29

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,7 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.4.x
10
10
 
11
+ - **0.4.28** (2026-04-30) — primitive-drift sweep: inline-require hoisting + safeAsync.sleep + time-math
11
12
  - **0.4.27** (2026-04-30) — primitive-drift sweep: regex + escape consolidation, IPv6 completion
12
13
  - **0.4.26** (2026-04-30) — primitive-drift sweep: middleware audit context + safeUrl
13
14
  - **0.4.25** (2026-04-30) — b.objectStore.bucketOps: bucket-level operations (SigV4)
package/lib/api-key.js CHANGED
@@ -277,16 +277,10 @@ function create(opts) {
277
277
  // The audit chain treats null fields as "unknown", so partial
278
278
  // context is always safe.
279
279
  function _actor(callerOpts, userId) {
280
- var override = {};
281
- if (userId) override.userId = userId;
282
- if (callerOpts && callerOpts.context && typeof callerOpts.context === "object") {
283
- for (var k in callerOpts.context) {
284
- if (Object.prototype.hasOwnProperty.call(callerOpts.context, k)) {
285
- override[k] = callerOpts.context[k];
286
- }
287
- }
288
- }
289
- return requestHelpers.extractActorContext(callerOpts && callerOpts.req, override);
280
+ return requestHelpers.resolveActorWithOverride(
281
+ callerOpts,
282
+ userId ? { userId: userId } : null
283
+ );
290
284
  }
291
285
 
292
286
  function _selectAll() {
@@ -57,9 +57,17 @@ var atomicFile = require("./atomic-file");
57
57
  var auditChain = require("./audit-chain");
58
58
  var auditSign = require("./audit-sign");
59
59
  var backupCrypto = require("./backup/crypto");
60
+ var clusterStorage = require("./cluster-storage");
61
+ var lazyRequire = require("./lazy-require");
60
62
  var jsonSafe = require("./safe-json");
61
63
  var { defineClass } = require("./framework-error");
62
64
 
65
+ // Lazy `db` — db requires audit at top-of-file, audit transitively
66
+ // reaches into audit-tools via the operator-supplied default fns,
67
+ // so importing db at audit-tools' top would close the cycle. Lazy
68
+ // keeps the load order one-way.
69
+ var db = lazyRequire(function () { return require("./db"); });
70
+
63
71
  var AuditToolsError = defineClass("AuditToolsError", { alwaysPermanent: true });
64
72
 
65
73
  var BUNDLE_FORMAT = "blamejs-audit-bundle-v1";
@@ -197,7 +205,6 @@ function _verifyChainSlice(rows, startPrevHash) {
197
205
  // cluster-storage reader so the tooling works in both single-node and
198
206
  // cluster deployments without the caller knowing which mode is active.
199
207
  async function _defaultReadRows(criteria) {
200
- var clusterStorage = require("./cluster-storage");
201
208
  var sql = 'SELECT * FROM "audit_log"';
202
209
  var conds = [];
203
210
  var params = [];
@@ -213,7 +220,6 @@ async function _defaultReadRows(criteria) {
213
220
  }
214
221
 
215
222
  async function _defaultReadCoveringCheckpoint(lastCounter) {
216
- var clusterStorage = require("./cluster-storage");
217
223
  return clusterStorage.executeOne(
218
224
  "SELECT * FROM audit_checkpoints " +
219
225
  "WHERE atMonotonicCounter >= ? " +
@@ -224,7 +230,6 @@ async function _defaultReadCoveringCheckpoint(lastCounter) {
224
230
 
225
231
  async function _defaultReadPredecessorRowHash(firstCounter) {
226
232
  if (firstCounter <= 1) return auditChain.ZERO_HASH;
227
- var clusterStorage = require("./cluster-storage");
228
233
  var row = await clusterStorage.executeOne(
229
234
  "SELECT rowHash FROM audit_log WHERE monotonicCounter = ?",
230
235
  [firstCounter - 1]
@@ -640,16 +645,13 @@ async function purge(opts) {
640
645
  }
641
646
 
642
647
  async function _defaultReadPurgeAnchor() {
643
- var clusterStorage = require("./cluster-storage");
644
648
  return clusterStorage.executeOne(
645
649
  "SELECT * FROM _blamejs_audit_purge_anchor WHERE scope = 'audit'"
646
650
  );
647
651
  }
648
652
 
649
653
  async function _defaultApplyPurge(args) {
650
- var db = require("./db");
651
- var clusterStorage = require("./cluster-storage");
652
- var del = await db.purgeAuditChain({ lastPurgedCounter: args.lastPurgedCounter });
654
+ var del = await db().purgeAuditChain({ lastPurgedCounter: args.lastPurgedCounter });
653
655
  // UPSERT the single-row anchor. SQLite + Postgres both support
654
656
  // INSERT ... ON CONFLICT(scope) DO UPDATE.
655
657
  await clusterStorage.execute(
package/lib/auth/oauth.js CHANGED
@@ -106,11 +106,18 @@
106
106
 
107
107
  var nodeCrypto = require("node:crypto");
108
108
  var cache = require("../cache");
109
+ var safeJson = require("../safe-json");
109
110
  var safeUrl = require("../safe-url");
110
111
  var C = require("../constants");
111
112
  var { defineClass } = require("../framework-error");
112
113
  var { boot } = require("../log");
113
114
 
115
+ // Cap on responses parsed from upstream OAuth providers. Token /
116
+ // userinfo / discovery responses are tiny in spec; 256 KiB leaves
117
+ // huge headroom and prevents a hostile (or compromised) upstream
118
+ // from staging a parse-bomb against the framework.
119
+ var OAUTH_MAX_RESPONSE_BYTES = C.BYTES.kib(256);
120
+
114
121
  var OAuthError = defineClass("OAuthError", { alwaysPermanent: true });
115
122
  var log = boot("auth.oauth");
116
123
 
@@ -360,7 +367,7 @@ function create(opts) {
360
367
  url + " returned " + res.statusCode + ": " + bodyText.slice(0, 500));
361
368
  }
362
369
  if (!res.body) return null;
363
- try { return JSON.parse(res.body.toString("utf8")); }
370
+ try { return safeJson.parse(res.body.toString("utf8"), { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
364
371
  catch (e) {
365
372
  throw new OAuthError("auth-oauth/bad-json",
366
373
  url + " response not JSON: " + ((e && e.message) || String(e)));
@@ -547,7 +554,7 @@ function create(opts) {
547
554
  endpoint + " returned " + res.statusCode + ": " + text.slice(0, 500));
548
555
  }
549
556
  var parsed;
550
- try { parsed = JSON.parse(text); }
557
+ try { parsed = safeJson.parse(text, { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
551
558
  catch (e) {
552
559
  throw new OAuthError("auth-oauth/bad-token-json",
553
560
  "token endpoint response not JSON: " + ((e && e.message) || String(e)));
@@ -604,8 +611,8 @@ function create(opts) {
604
611
  }
605
612
  var header, payload;
606
613
  try {
607
- header = JSON.parse(_b64urlDecode(parts[0]).toString("utf8"));
608
- payload = JSON.parse(_b64urlDecode(parts[1]).toString("utf8"));
614
+ header = safeJson.parse(_b64urlDecode(parts[0]).toString("utf8"), { maxBytes: OAUTH_MAX_RESPONSE_BYTES });
615
+ payload = safeJson.parse(_b64urlDecode(parts[1]).toString("utf8"), { maxBytes: OAUTH_MAX_RESPONSE_BYTES });
609
616
  } catch (e) {
610
617
  throw new OAuthError("auth-oauth/malformed-jwt",
611
618
  "ID token header/payload base64 decode failed: " + ((e && e.message) || String(e)));
@@ -69,6 +69,7 @@
69
69
  */
70
70
 
71
71
  var C = require("../constants");
72
+ var safeJson = require("../safe-json");
72
73
  var { FrameworkError } = require("../framework-error");
73
74
 
74
75
  class BackupManifestError extends FrameworkError {
@@ -250,7 +251,9 @@ function parse(jsonStr) {
250
251
  }
251
252
  var s = Buffer.isBuffer(jsonStr) ? jsonStr.toString("utf8") : jsonStr;
252
253
  var obj;
253
- try { obj = JSON.parse(s); }
254
+ // Backup manifests are file-bounded operator-supplied input. 16 MiB
255
+ // is generous; real manifests are kilobytes.
256
+ try { obj = safeJson.parse(s, { maxBytes: C.BYTES.mib(16) }); }
254
257
  catch (e) {
255
258
  throw new BackupManifestError("backup-manifest/bad-json",
256
259
  "parse: not valid JSON: " + ((e && e.message) || String(e)));
package/lib/cache.js CHANGED
@@ -683,15 +683,7 @@ function create(opts) {
683
683
  }
684
684
 
685
685
  function _actor(callerOpts) {
686
- var override = {};
687
- if (callerOpts && callerOpts.context && typeof callerOpts.context === "object") {
688
- for (var k in callerOpts.context) {
689
- if (Object.prototype.hasOwnProperty.call(callerOpts.context, k)) {
690
- override[k] = callerOpts.context[k];
691
- }
692
- }
693
- }
694
- return requestHelpers.extractActorContext(callerOpts && callerOpts.req, override);
686
+ return requestHelpers.resolveActorWithOverride(callerOpts);
695
687
  }
696
688
 
697
689
  function _backendFailedAudit(op, err) {
package/lib/mail.js CHANGED
@@ -62,15 +62,22 @@
62
62
  * resend-*. Vendor-specific presets carry their own code prefix so
63
63
  * diagnostic logs identify the provider that rejected the message.
64
64
  */
65
+ var C = require("./constants");
65
66
  var lazyRequire = require("./lazy-require");
67
+ var nodeCrypto = require("node:crypto");
66
68
  var audit = lazyRequire(function () { return require("./audit"); });
67
69
  var httpClient = lazyRequire(function () { return require("./http-client"); });
68
70
  var net = lazyRequire(function () { return require("net"); });
69
71
  var tls = lazyRequire(function () { return require("tls"); });
72
+ var safeJson = require("./safe-json");
70
73
  var safeSchema = require("./safe-schema");
71
74
  var validateOpts = require("./validate-opts");
72
75
  var { FrameworkError } = require("./framework-error");
73
76
 
77
+ // Cap on responses parsed from upstream mail providers (Resend, etc.).
78
+ // Vendor responses are tiny in spec; 256 KiB is generous headroom.
79
+ var MAIL_RESPONSE_MAX_BYTES = C.BYTES.kib(256);
80
+
74
81
  class MailError extends FrameworkError {
75
82
  constructor(code, message, permanent, statusCode) {
76
83
  super(message, code);
@@ -286,7 +293,11 @@ function memoryTransport() {
286
293
  // to send AUTH or DATA in cleartext if the upgrade is rejected.
287
294
 
288
295
  function _newBoundary(label) {
289
- return "blamejs-" + label + "-" + Date.now() + "-" + Math.floor(Math.random() * 1e9);
296
+ // crypto.randomBytes for the boundary suffix matches the framework
297
+ // convention. RFC 5322 only requires uniqueness within a message,
298
+ // but consistency with how every other identifier in lib/ is built
299
+ // wins over premature differentiation.
300
+ return "blamejs-" + label + "-" + Date.now() + "-" + nodeCrypto.randomBytes(8).toString("hex");
290
301
  }
291
302
 
292
303
  // base64-encode the buffer with line wrapping at 76 chars (RFC 2045
@@ -773,7 +784,7 @@ function resendTransport(opts) {
773
784
  interpret: function (res) {
774
785
  var text = res.body ? res.body.toString("utf8") : "";
775
786
  var data;
776
- try { data = JSON.parse(text); }
787
+ try { data = safeJson.parse(text, { maxBytes: MAIL_RESPONSE_MAX_BYTES }); }
777
788
  catch (_e) {
778
789
  throw new MailError("mail/resend-bad-response",
779
790
  "resend response was not JSON: " + text.slice(0, 200), false);
@@ -100,6 +100,8 @@ var crypto = require("../crypto");
100
100
  var C = require("../constants");
101
101
  var lazyRequire = require("../lazy-require");
102
102
  var nonceStoreLib = require("../nonce-store");
103
+ var requestHelpers = require("../request-helpers");
104
+ var safeJson = require("../safe-json");
103
105
  var validateOpts = require("../validate-opts");
104
106
  var { defineClass } = require("../framework-error");
105
107
 
@@ -172,10 +174,19 @@ function create(opts) {
172
174
  validateOpts(opts, [
173
175
  "keypair", "keypairs", "replayWindowMs", "pruneIntervalMs",
174
176
  "nonceStore", "exemptPaths", "contentTypes", "audit",
177
+ "maxDecryptedBytes",
175
178
  ], "middleware.apiEncrypt");
176
179
  var keypairs = _resolveKeypairs(opts);
177
180
  var activeKeypair = keypairs[0];
178
181
  var replayWindowMs = opts.replayWindowMs || DEFAULT_REPLAY_WINDOW_MS;
182
+ // Cap on decrypted-payload size handed to safeJson.parse. Defaults
183
+ // to 4 MiB (bodyParser's default 1 MiB plus headroom for crypto +
184
+ // base64 round-trip). Operators with chunkier inbound payloads
185
+ // raise this; the framework refuses to parse anything larger as a
186
+ // parse-bomb defense.
187
+ var maxDecryptedBytes = opts.maxDecryptedBytes != null
188
+ ? opts.maxDecryptedBytes
189
+ : C.BYTES.mib(4);
179
190
  // The spec calls for a sweep cadence of replayWindowMs/2 — short
180
191
  // enough that expired nonces don't pile up but not so frequent the
181
192
  // sweep query becomes a hot path. Operators can override.
@@ -230,7 +241,7 @@ function create(opts) {
230
241
  };
231
242
  if (auditOn) {
232
243
  audit().safeEmit({
233
- actor: { ip: info.ip },
244
+ actor: requestHelpers.extractActorContext(req),
234
245
  action: "system.api_encrypt.failure",
235
246
  outcome: "denied",
236
247
  reason: reason,
@@ -348,7 +359,7 @@ function create(opts) {
348
359
  try {
349
360
  var ctBuf = Buffer.from(ct, "base64");
350
361
  var ptBuf = crypto.decryptPacked(ctBuf, sessionKey);
351
- clearObj = JSON.parse(ptBuf.toString("utf8"));
362
+ clearObj = safeJson.parse(ptBuf.toString("utf8"), { maxBytes: maxDecryptedBytes });
352
363
  } catch (_e) {
353
364
  _emitFailure(req, "tag");
354
365
  return _writeRejection(res, 400, { error: "encrypted-payload-rejected" });
@@ -403,7 +414,7 @@ function create(opts) {
403
414
 
404
415
  function client(opts) {
405
416
  opts = opts || {};
406
- validateOpts(opts, ["pubkey"], "middleware.apiEncrypt.client");
417
+ validateOpts(opts, ["pubkey", "maxDecryptedBytes"], "middleware.apiEncrypt.client");
407
418
  if (!opts.pubkey || typeof opts.pubkey !== "object") {
408
419
  throw _err("CLIENT_INVALID_PUBKEY",
409
420
  "apiEncrypt.client: opts.pubkey is required ({ publicKey, ecPublicKey })", 500);
@@ -414,6 +425,9 @@ function client(opts) {
414
425
  "apiEncrypt.client: pubkey.publicKey + ecPublicKey must be PEM strings", 500);
415
426
  }
416
427
  var pubkey = opts.pubkey;
428
+ var maxDecryptedBytes = opts.maxDecryptedBytes != null
429
+ ? opts.maxDecryptedBytes
430
+ : C.BYTES.mib(4);
417
431
 
418
432
  function encryptRequest(payload) {
419
433
  if (payload === undefined) payload = null;
@@ -441,7 +455,7 @@ function client(opts) {
441
455
  }
442
456
  var resCtBuf = Buffer.from(responseBody._ct, "base64");
443
457
  var resPtBuf = crypto.decryptPacked(resCtBuf, sessionKey);
444
- return JSON.parse(resPtBuf.toString("utf8"));
458
+ return safeJson.parse(resPtBuf.toString("utf8"), { maxBytes: maxDecryptedBytes });
445
459
  },
446
460
  };
447
461
  }
@@ -474,13 +488,16 @@ function client(opts) {
474
488
  function httpClientEncrypted(opts) {
475
489
  opts = opts || {};
476
490
  validateOpts(opts, [
477
- "pubkey", "baseUrl", "headers", "method",
491
+ "pubkey", "baseUrl", "headers", "method", "maxDecryptedBytes",
478
492
  ], "middleware.apiEncrypt.httpClient");
479
493
  if (!opts.pubkey) {
480
494
  throw _err("CLIENT_INVALID_PUBKEY",
481
495
  "httpClient.encrypted: opts.pubkey is required (the callee's bootstrap doc)", 500);
482
496
  }
483
- var clientCtx = client({ pubkey: opts.pubkey });
497
+ var maxDecryptedBytes = opts.maxDecryptedBytes != null
498
+ ? opts.maxDecryptedBytes
499
+ : C.BYTES.mib(4);
500
+ var clientCtx = client({ pubkey: opts.pubkey, maxDecryptedBytes: maxDecryptedBytes });
484
501
  var baseUrl = opts.baseUrl ? String(opts.baseUrl).replace(/\/$/, "") : "";
485
502
  var defaultHdrs = opts.headers || {};
486
503
  var defaultMethod = opts.method || "POST";
@@ -531,7 +548,7 @@ function httpClientEncrypted(opts) {
531
548
  return { statusCode: resp.statusCode, headers: resp.headers, body: null };
532
549
  }
533
550
  var parsed;
534
- try { parsed = JSON.parse(resp.body.toString("utf8")); }
551
+ try { parsed = safeJson.parse(resp.body.toString("utf8"), { maxBytes: maxDecryptedBytes }); }
535
552
  catch (e) {
536
553
  throw _err("CLIENT_RESPONSE_NOT_JSON",
537
554
  "httpClient.encrypted: response body is not valid JSON: " + e.message);
@@ -43,6 +43,7 @@
43
43
  */
44
44
  var lazyRequire = require("../lazy-require");
45
45
  var cookies = require("../cookies");
46
+ var requestHelpers = require("../request-helpers");
46
47
  var validateOpts = require("../validate-opts");
47
48
  var session = lazyRequire(function () { return require("../session"); });
48
49
  var audit = lazyRequire(function () { return require("../audit"); });
@@ -120,7 +121,7 @@ function create(opts) {
120
121
  audit().emit({
121
122
  action: "auth.session.user_loader_threw",
122
123
  outcome: "failure",
123
- actor: { userId: verified.userId, ip: req.socket && req.socket.remoteAddress },
124
+ actor: requestHelpers.extractActorContext(req, { userId: verified.userId }),
124
125
  reason: (e && e.message) || String(e),
125
126
  });
126
127
  } catch (_ignored) { /* audit best-effort */ }
@@ -135,7 +136,7 @@ function create(opts) {
135
136
  audit().emit({
136
137
  action: "auth.session.user_unloadable",
137
138
  outcome: "failure",
138
- actor: { userId: verified.userId, ip: req.socket && req.socket.remoteAddress },
139
+ actor: requestHelpers.extractActorContext(req, { userId: verified.userId }),
139
140
  });
140
141
  } catch (_ignored) { /* audit best-effort */ }
141
142
  }
@@ -44,10 +44,20 @@
44
44
  */
45
45
  var lazyRequire = require("../lazy-require");
46
46
  var audit = lazyRequire(function () { return require("../audit"); });
47
+ var requestHelpers = require("../request-helpers");
47
48
  var safeUrl = require("../safe-url");
48
49
  var validateOpts = require("../validate-opts");
49
50
  var { defineClass } = require("../framework-error");
50
51
 
52
+ // CORS audit events trust the proxy header for actor.ip — same
53
+ // semantics as bot-guard (the apparent origin is what matters when
54
+ // blocking cross-origin attempts behind a CDN).
55
+ function _xffIp(req) {
56
+ var xff = req.headers && req.headers["x-forwarded-for"];
57
+ if (xff) return String(xff).split(",")[0].trim();
58
+ return (req.socket && req.socket.remoteAddress) || null;
59
+ }
60
+
51
61
  var CorsError = defineClass("CorsError", { alwaysPermanent: true });
52
62
 
53
63
  function _matchOrigin(origin, allowList) {
@@ -188,7 +198,7 @@ function create(opts) {
188
198
  if (refuseUnknown) {
189
199
  try {
190
200
  audit().emit({
191
- actor: { ip: (req.headers && req.headers["x-forwarded-for"]) || (req.socket && req.socket.remoteAddress) },
201
+ actor: requestHelpers.extractActorContext(req, { ip: _xffIp(req) }),
192
202
  action: "system.cors.block",
193
203
  outcome: "denied",
194
204
  reason: "origin not in allow-list",
package/lib/notify.js CHANGED
@@ -370,15 +370,7 @@ function create(opts) {
370
370
  }
371
371
 
372
372
  function _actor(callerOpts) {
373
- var override = {};
374
- if (callerOpts && callerOpts.context && typeof callerOpts.context === "object") {
375
- for (var k in callerOpts.context) {
376
- if (Object.prototype.hasOwnProperty.call(callerOpts.context, k)) {
377
- override[k] = callerOpts.context[k];
378
- }
379
- }
380
- }
381
- return requestHelpers.extractActorContext(callerOpts && callerOpts.req, override);
373
+ return requestHelpers.resolveActorWithOverride(callerOpts);
382
374
  }
383
375
 
384
376
  function _redactedMetadata(channel, message, extra) {
@@ -87,6 +87,31 @@ function extractActorContext(req, override) {
87
87
  return ctx;
88
88
  }
89
89
 
90
+ // Convenience wrapper for primitives that accept an optional
91
+ // `{ req, context }` shape and want to thread it into an audit-emit
92
+ // `actor` field. Replaces the four near-identical `_actor()` helpers
93
+ // that lived in api-key, cache, seeders, and notify before v0.4.29.
94
+ //
95
+ // callerOpts: operator-supplied `{ req?, context? }` (e.g. the
96
+ // primitive's call-site opts bag)
97
+ // baseOverride: optional seed values applied BEFORE callerOpts.context
98
+ // so `context` wins. api-key seeds `{ userId }` here so
99
+ // the resolved key's owner becomes the default actor
100
+ // unless the operator passes their own context.userId.
101
+ //
102
+ // Returns the same shape as extractActorContext.
103
+ function resolveActorWithOverride(callerOpts, baseOverride) {
104
+ var override = baseOverride ? Object.assign({}, baseOverride) : {};
105
+ if (callerOpts && callerOpts.context && typeof callerOpts.context === "object") {
106
+ for (var k in callerOpts.context) {
107
+ if (Object.prototype.hasOwnProperty.call(callerOpts.context, k)) {
108
+ override[k] = callerOpts.context[k];
109
+ }
110
+ }
111
+ }
112
+ return extractActorContext(callerOpts && callerOpts.req, override);
113
+ }
114
+
90
115
  function resolveRoute(req) {
91
116
  if (req && typeof req.routePattern === "string" && req.routePattern.length > 0) {
92
117
  return req.routePattern;
@@ -171,8 +196,9 @@ function parseQualityList(headerValue, opts) {
171
196
  }
172
197
 
173
198
  module.exports = {
174
- resolveRoute: resolveRoute,
175
- captureResponseStatus: captureResponseStatus,
176
- extractActorContext: extractActorContext,
177
- parseQualityList: parseQualityList,
199
+ resolveRoute: resolveRoute,
200
+ captureResponseStatus: captureResponseStatus,
201
+ extractActorContext: extractActorContext,
202
+ resolveActorWithOverride: resolveActorWithOverride,
203
+ parseQualityList: parseQualityList,
178
204
  };
@@ -56,6 +56,8 @@
56
56
  var fs = require("fs");
57
57
  var path = require("path");
58
58
  var atomicFile = require("./atomic-file");
59
+ var C = require("./constants");
60
+ var safeJson = require("./safe-json");
59
61
  var { defineClass } = require("./framework-error");
60
62
 
61
63
  var RestoreRollbackError = defineClass("RestoreRollbackError", { alwaysPermanent: true });
@@ -199,7 +201,7 @@ function list(opts) {
199
201
  var markerPath = p + ".marker.json";
200
202
  var marker = null;
201
203
  if (fs.existsSync(markerPath)) {
202
- try { marker = JSON.parse(fs.readFileSync(markerPath, "utf8")); }
204
+ try { marker = safeJson.parse(fs.readFileSync(markerPath, "utf8"), { maxBytes: C.BYTES.kib(64) }); }
203
205
  catch (_e) { marker = null; }
204
206
  }
205
207
  var stat;
package/lib/seeders.js CHANGED
@@ -404,15 +404,7 @@ function create(opts) {
404
404
  }
405
405
 
406
406
  function _actor(callerOpts) {
407
- var override = {};
408
- if (callerOpts && callerOpts.context && typeof callerOpts.context === "object") {
409
- for (var k in callerOpts.context) {
410
- if (Object.prototype.hasOwnProperty.call(callerOpts.context, k)) {
411
- override[k] = callerOpts.context[k];
412
- }
413
- }
414
- }
415
- return requestHelpers.extractActorContext(callerOpts && callerOpts.req, override);
407
+ return requestHelpers.resolveActorWithOverride(callerOpts);
416
408
  }
417
409
 
418
410
  function _appliedRows(db, env) {
@@ -56,6 +56,7 @@ var C = require("../constants");
56
56
  var cryptoField = require("../crypto-field");
57
57
  var cryptoLib = require("../crypto");
58
58
  var lazyRequire = require("../lazy-require");
59
+ var safeJson = require("../safe-json");
59
60
  var vaultWrap = lazyRequire(function () { return require("./wrap"); });
60
61
  var { defineClass } = require("../framework-error");
61
62
 
@@ -479,7 +480,10 @@ function _rotateOverflow(db, table, oldKeys, newKeys, batchSize, progress, warni
479
480
  for (var i = 0; i < rows.length; i++) {
480
481
  var row = rows[i];
481
482
  var doc;
482
- try { doc = JSON.parse(row.data); }
483
+ // Vault overflow rows are framework-written but read at rotate
484
+ // time — gate the parse via safeJson so a corrupted row can't
485
+ // stage a parse-bomb against the rotation worker.
486
+ try { doc = safeJson.parse(row.data, { maxBytes: C.BYTES.mib(16) }); }
483
487
  catch (_e) {
484
488
  warnings.push("malformed overflow JSON at " + table + "._id=" + row._id + " — left unrotated");
485
489
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.4.28",
3
+ "version": "0.4.29",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",