@blamejs/core 0.4.28 → 0.5.0

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,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.4.x
10
10
 
11
+ - **0.4.29** (2026-04-30) — primitive-drift sweep: second-pass remediation
12
+ - **0.4.28** (2026-04-30) — primitive-drift sweep: inline-require hoisting + safeAsync.sleep + time-math
11
13
  - **0.4.27** (2026-04-30) — primitive-drift sweep: regex + escape consolidation, IPv6 completion
12
14
  - **0.4.26** (2026-04-30) — primitive-drift sweep: middleware audit context + safeUrl
13
15
  - **0.4.25** (2026-04-30) — b.objectStore.bucketOps: bucket-level operations (SigV4)
package/index.js CHANGED
@@ -105,6 +105,7 @@ var staticServe = require("./lib/static");
105
105
  var forms = require("./lib/forms");
106
106
  var app = require("./lib/app");
107
107
  var jobs = require("./lib/jobs");
108
+ var breakGlass = require("./lib/break-glass");
108
109
  var mail = require("./lib/mail");
109
110
  var mailBounce = require("./lib/mail-bounce");
110
111
  var websocketChannels = require("./lib/websocket-channels");
@@ -206,6 +207,7 @@ module.exports = {
206
207
  forms: forms,
207
208
  createApp: app.createApp,
208
209
  jobs: jobs,
210
+ breakGlass: breakGlass,
209
211
  mail: mail,
210
212
  mailBounce: mailBounce,
211
213
  websocketChannels: websocketChannels,
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/audit.js CHANGED
@@ -201,6 +201,7 @@ var FRAMEWORK_NAMESPACES = [
201
201
  // Per-primitive namespaces — keep alphabetical
202
202
  "apikey", // b.apiKey
203
203
  "backup", // b.backup
204
+ "breakglass", // b.breakGlass — column-policy / row-enforcement step-up auth
204
205
  "cache", // b.cache
205
206
  "dkim", // b.mail.dkim (DKIM-Signature generation events)
206
207
  "mail", // b.mail (b.mail-bounce uses "system.mail.*")
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)));