@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 +2 -0
- package/index.js +2 -0
- package/lib/api-key.js +4 -10
- package/lib/audit-tools.js +9 -7
- package/lib/audit.js +1 -0
- package/lib/auth/oauth.js +11 -4
- package/lib/backup/manifest.js +4 -1
- package/lib/break-glass.js +741 -0
- package/lib/cache.js +1 -9
- package/lib/db.js +64 -0
- package/lib/framework-schema.js +71 -0
- package/lib/mail.js +13 -2
- package/lib/middleware/api-encrypt.js +24 -7
- package/lib/middleware/attach-user.js +3 -2
- package/lib/middleware/cors.js +11 -1
- package/lib/notify.js +1 -9
- package/lib/request-helpers.js +30 -4
- package/lib/restore-rollback.js +3 -1
- package/lib/seeders.js +1 -9
- package/lib/vault/rotate.js +5 -1
- package/package.json +1 -1
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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() {
|
package/lib/audit-tools.js
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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 =
|
|
608
|
-
payload =
|
|
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)));
|
package/lib/backup/manifest.js
CHANGED
|
@@ -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
|
-
|
|
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)));
|