@blamejs/core 0.14.27 → 0.15.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 +4 -0
- package/README.md +2 -2
- package/index.js +4 -0
- package/lib/ai-content-detect.js +9 -10
- package/lib/api-key.js +107 -74
- package/lib/atomic-file.js +29 -1
- package/lib/audit-chain.js +47 -11
- package/lib/audit-sign.js +77 -2
- package/lib/audit-tools.js +79 -51
- package/lib/audit.js +218 -100
- package/lib/backup/index.js +13 -10
- package/lib/break-glass.js +202 -144
- package/lib/cache.js +174 -105
- package/lib/chain-writer.js +38 -16
- package/lib/cli.js +19 -14
- package/lib/cluster-provider-db.js +130 -104
- package/lib/cluster-storage.js +119 -22
- package/lib/cluster.js +119 -71
- package/lib/compliance.js +22 -0
- package/lib/consent.js +73 -24
- package/lib/constants.js +16 -11
- package/lib/crypto-field.js +387 -91
- package/lib/db-declare-row-policy.js +35 -22
- package/lib/db-file-lifecycle.js +3 -2
- package/lib/db-query.js +497 -255
- package/lib/db-schema.js +209 -44
- package/lib/db.js +176 -95
- package/lib/external-db-migrate.js +229 -139
- package/lib/external-db.js +25 -15
- package/lib/framework-error.js +11 -0
- package/lib/framework-files.js +73 -0
- package/lib/framework-schema.js +695 -394
- package/lib/gate-contract.js +596 -1
- package/lib/guard-agent-registry.js +26 -44
- package/lib/guard-all.js +1 -0
- package/lib/guard-auth.js +42 -112
- package/lib/guard-cidr.js +33 -154
- package/lib/guard-csv.js +46 -113
- package/lib/guard-domain.js +34 -157
- package/lib/guard-dsn.js +27 -43
- package/lib/guard-email.js +47 -69
- package/lib/guard-envelope.js +19 -32
- package/lib/guard-event-bus-payload.js +24 -42
- package/lib/guard-event-bus-topic.js +25 -43
- package/lib/guard-filename.js +42 -106
- package/lib/guard-graphql.js +42 -123
- package/lib/guard-html.js +53 -108
- package/lib/guard-idempotency-key.js +24 -42
- package/lib/guard-image.js +46 -103
- package/lib/guard-imap-command.js +18 -32
- package/lib/guard-jmap.js +16 -30
- package/lib/guard-json.js +38 -108
- package/lib/guard-jsonpath.js +38 -171
- package/lib/guard-jwt.js +49 -179
- package/lib/guard-list-id.js +25 -41
- package/lib/guard-list-unsubscribe.js +27 -43
- package/lib/guard-mail-compose.js +24 -42
- package/lib/guard-mail-move.js +26 -44
- package/lib/guard-mail-query.js +28 -46
- package/lib/guard-mail-reply.js +24 -42
- package/lib/guard-mail-sieve.js +24 -42
- package/lib/guard-managesieve-command.js +17 -31
- package/lib/guard-markdown.js +37 -104
- package/lib/guard-message-id.js +26 -45
- package/lib/guard-mime.js +39 -151
- package/lib/guard-oauth.js +54 -135
- package/lib/guard-pdf.js +45 -101
- package/lib/guard-pop3-command.js +21 -31
- package/lib/guard-posture-chain.js +24 -42
- package/lib/guard-regex.js +33 -107
- package/lib/guard-saga-config.js +24 -42
- package/lib/guard-shell.js +42 -172
- package/lib/guard-smtp-command.js +48 -54
- package/lib/guard-snapshot-envelope.js +24 -42
- package/lib/guard-sql.js +1491 -0
- package/lib/guard-stream-args.js +24 -43
- package/lib/guard-svg.js +47 -65
- package/lib/guard-template.js +35 -172
- package/lib/guard-tenant-id.js +26 -45
- package/lib/guard-time.js +32 -154
- package/lib/guard-trace-context.js +25 -44
- package/lib/guard-uuid.js +32 -153
- package/lib/guard-xml.js +38 -113
- package/lib/guard-yaml.js +51 -163
- package/lib/http-client.js +14 -0
- package/lib/inbox.js +120 -107
- package/lib/legal-hold.js +107 -50
- package/lib/log-stream-cloudwatch.js +47 -31
- package/lib/log-stream-otlp.js +32 -18
- package/lib/mail-crypto-smime.js +2 -6
- package/lib/mail-greylist.js +2 -6
- package/lib/mail-helo.js +2 -6
- package/lib/mail-journal.js +85 -64
- package/lib/mail-rbl.js +2 -6
- package/lib/mail-scan.js +2 -6
- package/lib/mail-spam-score.js +2 -6
- package/lib/mail-store.js +287 -154
- package/lib/middleware/fetch-metadata.js +17 -7
- package/lib/middleware/idempotency-key.js +54 -38
- package/lib/middleware/rate-limit.js +102 -32
- package/lib/middleware/security-headers.js +21 -5
- package/lib/migrations.js +108 -66
- package/lib/network-heartbeat.js +7 -0
- package/lib/nonce-store.js +31 -9
- package/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/object-store/azure-blob.js +31 -3
- package/lib/object-store/sigv4.js +10 -0
- package/lib/outbox.js +136 -82
- package/lib/pqc-agent.js +44 -0
- package/lib/pubsub-cluster.js +42 -20
- package/lib/queue-local.js +202 -139
- package/lib/queue-redis.js +9 -1
- package/lib/queue-sqs.js +6 -0
- package/lib/retention.js +82 -39
- package/lib/safe-dns.js +29 -45
- package/lib/safe-ical.js +18 -33
- package/lib/safe-icap.js +27 -43
- package/lib/safe-sieve.js +21 -40
- package/lib/safe-sql.js +124 -3
- package/lib/safe-vcard.js +18 -33
- package/lib/scheduler.js +35 -12
- package/lib/seeders.js +122 -74
- package/lib/session-stores.js +42 -14
- package/lib/session.js +109 -72
- package/lib/sql.js +3885 -0
- package/lib/static.js +45 -7
- package/lib/subject.js +55 -17
- package/lib/vault/index.js +3 -2
- package/lib/vault/passphrase-ops.js +3 -2
- package/lib/vault/rotate.js +104 -64
- package/lib/vendor-data.js +2 -0
- package/lib/websocket.js +16 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/mail-journal.js
CHANGED
|
@@ -72,7 +72,7 @@ var C = require("./constants");
|
|
|
72
72
|
var validateOpts = require("./validate-opts");
|
|
73
73
|
var numericBounds = require("./numeric-bounds");
|
|
74
74
|
var safeJson = require("./safe-json");
|
|
75
|
-
var
|
|
75
|
+
var sql = require("./sql");
|
|
76
76
|
var { defineClass } = require("./framework-error");
|
|
77
77
|
var lazyRequire = require("./lazy-require");
|
|
78
78
|
|
|
@@ -213,33 +213,40 @@ function create(opts) {
|
|
|
213
213
|
// / `messageId` / `archivedAt` / `sizeBytes` / `regimes` / `legalHold`
|
|
214
214
|
// queryable without unsealing the payload. The payload (headers +
|
|
215
215
|
// body) lives in the WORM bucket sealed via b.cryptoField.sealRow.
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
// `
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
var
|
|
225
|
-
var
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
216
|
+
//
|
|
217
|
+
// The journal table is an operator-namespaced local table (NOT a
|
|
218
|
+
// framework `_blamejs_` table that clusterStorage rewrites), so every
|
|
219
|
+
// statement composes b.sql with quoteName:true — b.sql validates the
|
|
220
|
+
// identifier through b.safeSql and emits the dialect-quoted form,
|
|
221
|
+
// running against opts.db (the local sqlite handle) directly. `_t()`
|
|
222
|
+
// opens each verb builder pre-bound to this table so the name resolves
|
|
223
|
+
// in exactly one place.
|
|
224
|
+
var rawTable = "_mail_journal_" + namespace.replace(/-/g, "_");
|
|
225
|
+
var TBL_OPTS = { dialect: "sqlite", quoteName: true };
|
|
226
|
+
function _t(verb) { return sql[verb](rawTable, TBL_OPTS); }
|
|
227
|
+
|
|
228
|
+
// Bootstrap DDL — CREATE TABLE + the archived_at / message_id indexes.
|
|
229
|
+
// runSql is a multi-statement helper, so the three b.sql DDL strings
|
|
230
|
+
// join with `;` into one call (each b.sql build is a single validated
|
|
231
|
+
// statement; the join is the multi-statement boundary runSql expects).
|
|
232
|
+
var ddl = [
|
|
233
|
+
sql.createTable(rawTable, [
|
|
234
|
+
{ name: "journal_id", type: "text", primaryKey: true },
|
|
235
|
+
{ name: "direction", type: "text", notNull: true },
|
|
236
|
+
{ name: "actor_id", type: "text" },
|
|
237
|
+
{ name: "message_id", type: "text" },
|
|
238
|
+
{ name: "archived_at", type: "int", notNull: true },
|
|
239
|
+
{ name: "size_bytes", type: "int", notNull: true },
|
|
240
|
+
{ name: "regimes", type: "text", notNull: true },
|
|
241
|
+
{ name: "floor_until", type: "int", notNull: true },
|
|
242
|
+
{ name: "legal_hold", type: "int", notNull: true, default: 0 },
|
|
243
|
+
{ name: "storage_key", type: "text", notNull: true, unique: true },
|
|
244
|
+
{ name: "sealed_payload", type: "blob", notNull: true },
|
|
245
|
+
], TBL_OPTS).sql,
|
|
246
|
+
sql.createIndex(rawTable + "_archived_at_idx", rawTable, ["archived_at"], TBL_OPTS).sql,
|
|
247
|
+
sql.createIndex(rawTable + "_message_id_idx", rawTable, ["message_id"], TBL_OPTS).sql,
|
|
248
|
+
].join(";");
|
|
249
|
+
opts.db.runSql(ddl);
|
|
243
250
|
|
|
244
251
|
async function record(req) {
|
|
245
252
|
validateOpts.requireObject(req, "mail.journal.record",
|
|
@@ -292,13 +299,24 @@ function create(opts) {
|
|
|
292
299
|
|
|
293
300
|
var regimesJson = JSON.stringify(opts.regimes);
|
|
294
301
|
var floorUntil = archivedAt + floorMs;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
// legal_hold is omitted from the INSERT so the column's
|
|
303
|
+
// `NOT NULL DEFAULT 0` applies (the prior inline `0` SQL literal) — b.sql
|
|
304
|
+
// binds every value it is given, so leaving the column out keeps the row
|
|
305
|
+
// unsealed-at-rest default without binding a redundant constant. b.sql
|
|
306
|
+
// quotes every column + binds every remaining value as a placeholder.
|
|
307
|
+
var insBuilt = _t("insert").values({
|
|
308
|
+
journal_id: journalId,
|
|
309
|
+
direction: req.direction,
|
|
310
|
+
actor_id: req.actorId,
|
|
311
|
+
message_id: req.messageId,
|
|
312
|
+
archived_at: archivedAt,
|
|
313
|
+
size_bytes: sizeBytes,
|
|
314
|
+
regimes: regimesJson,
|
|
315
|
+
floor_until: floorUntil,
|
|
316
|
+
storage_key: storageKey,
|
|
317
|
+
sealed_payload: sealedBlob,
|
|
318
|
+
}).toSql();
|
|
319
|
+
opts.db.runSql(insBuilt.sql, insBuilt.params);
|
|
302
320
|
|
|
303
321
|
_emit("mail.journal.record", "success", {
|
|
304
322
|
journalId: journalId,
|
|
@@ -317,11 +335,12 @@ function create(opts) {
|
|
|
317
335
|
throw new MailJournalError("mail-journal/bad-id",
|
|
318
336
|
"mail.journal.getById: journalId must be a non-empty string");
|
|
319
337
|
}
|
|
320
|
-
var
|
|
321
|
-
"
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
338
|
+
var gbBuilt = _t("select")
|
|
339
|
+
.columns(["direction", "message_id", "archived_at", "size_bytes", "regimes",
|
|
340
|
+
"floor_until", "legal_hold", "storage_key", "sealed_payload"])
|
|
341
|
+
.where("journal_id", journalId)
|
|
342
|
+
.toSql();
|
|
343
|
+
var rows = opts.db.runSql(gbBuilt.sql, gbBuilt.params);
|
|
325
344
|
if (!rows || rows.length === 0) return null;
|
|
326
345
|
var r = rows[0];
|
|
327
346
|
var unsealed = safeJson.parse(opts.vault.unseal(r.sealed_payload));
|
|
@@ -344,30 +363,27 @@ function create(opts) {
|
|
|
344
363
|
|
|
345
364
|
function list(filter) {
|
|
346
365
|
filter = filter || {};
|
|
366
|
+
var limit = numericBounds.isPositiveFiniteInt(filter.limit) ? Math.min(filter.limit, 1000) : 100; // list page cap
|
|
367
|
+
// Each filter term is an optional .where() leaf (AND-composed); b.sql
|
|
368
|
+
// quotes the columns + binds the values. A diagnostic clause list is
|
|
369
|
+
// kept for the audit metadata (the prior `filter: clauses` field).
|
|
347
370
|
var clauses = [];
|
|
348
|
-
var
|
|
371
|
+
var qb = _t("select").columns(["journal_id", "direction", "actor_id", "message_id",
|
|
372
|
+
"archived_at", "size_bytes", "regimes", "floor_until", "legal_hold", "storage_key"]);
|
|
349
373
|
if (filter.direction && ALLOWED_DIRECTIONS[filter.direction]) {
|
|
350
|
-
clauses.push("direction = ?");
|
|
351
|
-
args.push(filter.direction);
|
|
374
|
+
qb.where("direction", filter.direction); clauses.push("direction = ?");
|
|
352
375
|
}
|
|
353
376
|
if (typeof filter.since === "number" && numericBounds.isPositiveFiniteInt(filter.since)) {
|
|
354
|
-
clauses.push("archived_at >= ?");
|
|
355
|
-
args.push(filter.since);
|
|
377
|
+
qb.whereOp("archived_at", ">=", filter.since); clauses.push("archived_at >= ?");
|
|
356
378
|
}
|
|
357
379
|
if (typeof filter.until === "number" && numericBounds.isPositiveFiniteInt(filter.until)) {
|
|
358
|
-
clauses.push("archived_at < ?");
|
|
359
|
-
args.push(filter.until);
|
|
380
|
+
qb.whereOp("archived_at", "<", filter.until); clauses.push("archived_at < ?");
|
|
360
381
|
}
|
|
361
382
|
if (filter.actorId && typeof filter.actorId === "string") {
|
|
362
|
-
clauses.push("actor_id = ?");
|
|
363
|
-
args.push(filter.actorId);
|
|
383
|
+
qb.where("actor_id", filter.actorId); clauses.push("actor_id = ?");
|
|
364
384
|
}
|
|
365
|
-
var
|
|
366
|
-
var
|
|
367
|
-
var sql = "SELECT journal_id, direction, actor_id, message_id, archived_at, " +
|
|
368
|
-
"size_bytes, regimes, floor_until, legal_hold, storage_key FROM " +
|
|
369
|
-
qTable + where + " ORDER BY archived_at DESC LIMIT " + limit + ";";
|
|
370
|
-
var rows = opts.db.runSql(sql, args);
|
|
385
|
+
var listBuilt = qb.orderBy("archived_at", "desc").limit(limit).toSql();
|
|
386
|
+
var rows = opts.db.runSql(listBuilt.sql, listBuilt.params);
|
|
371
387
|
_emit("mail.journal.list", "success", { count: rows ? rows.length : 0, filter: clauses });
|
|
372
388
|
return (rows || []).map(function (r) {
|
|
373
389
|
return {
|
|
@@ -387,11 +403,15 @@ function create(opts) {
|
|
|
387
403
|
|
|
388
404
|
function expireSurface(now) {
|
|
389
405
|
if (now === undefined) now = Date.now();
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
406
|
+
// legal_hold = 0 binds as a value (the prior inline `0` literal).
|
|
407
|
+
var esBuilt = _t("select")
|
|
408
|
+
.columns(["journal_id", "archived_at", "floor_until", "message_id", "regimes"])
|
|
409
|
+
.whereOp("floor_until", "<", now)
|
|
410
|
+
.where("legal_hold", 0)
|
|
411
|
+
.orderBy("archived_at", "asc")
|
|
412
|
+
.limit(1000) // expiry-surface cap
|
|
413
|
+
.toSql();
|
|
414
|
+
var rows = opts.db.runSql(esBuilt.sql, esBuilt.params);
|
|
395
415
|
_emit("mail.journal.expire_surface", "success", { count: rows ? rows.length : 0, now: now });
|
|
396
416
|
return (rows || []).map(function (r) {
|
|
397
417
|
return {
|
|
@@ -409,10 +429,11 @@ function create(opts) {
|
|
|
409
429
|
throw new MailJournalError("mail-journal/bad-id",
|
|
410
430
|
"mail.journal.setLegalHold: journalId required");
|
|
411
431
|
}
|
|
412
|
-
|
|
413
|
-
"
|
|
414
|
-
|
|
415
|
-
|
|
432
|
+
var lhBuilt = _t("update")
|
|
433
|
+
.set("legal_hold", onHold ? 1 : 0)
|
|
434
|
+
.where("journal_id", journalId)
|
|
435
|
+
.toSql();
|
|
436
|
+
opts.db.runSql(lhBuilt.sql, lhBuilt.params);
|
|
416
437
|
_emit("mail.journal.legal_hold_change", "success", { journalId: journalId, onHold: !!onHold });
|
|
417
438
|
}
|
|
418
439
|
|
package/lib/mail-rbl.js
CHANGED
|
@@ -86,6 +86,7 @@ var C = require("./constants");
|
|
|
86
86
|
var { defineClass } = require("./framework-error");
|
|
87
87
|
var lazyRequire = require("./lazy-require");
|
|
88
88
|
var ipUtils = require("./ip-utils");
|
|
89
|
+
var gateContract = require("./gate-contract");
|
|
89
90
|
|
|
90
91
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
91
92
|
|
|
@@ -105,12 +106,7 @@ var PROFILES = Object.freeze({
|
|
|
105
106
|
permissive: { maxConcurrent: 32, perListTimeoutMs: C.TIME.seconds(20), maxListsPerQuery: 64 }, // list-count cap
|
|
106
107
|
});
|
|
107
108
|
|
|
108
|
-
var COMPLIANCE_POSTURES =
|
|
109
|
-
hipaa: "strict",
|
|
110
|
-
"pci-dss": "strict",
|
|
111
|
-
gdpr: "strict",
|
|
112
|
-
soc2: "strict",
|
|
113
|
-
});
|
|
109
|
+
var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
|
|
114
110
|
|
|
115
111
|
/**
|
|
116
112
|
* @primitive b.mail.rbl.create
|
package/lib/mail-scan.js
CHANGED
|
@@ -85,6 +85,7 @@ var lazyRequire = require("./lazy-require");
|
|
|
85
85
|
var validateOpts = require("./validate-opts");
|
|
86
86
|
var numericBounds = require("./numeric-bounds");
|
|
87
87
|
var safeIcap = require("./safe-icap");
|
|
88
|
+
var gateContract = require("./gate-contract");
|
|
88
89
|
|
|
89
90
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
90
91
|
var guardArchive = lazyRequire(function () { return require("./guard-archive"); });
|
|
@@ -107,12 +108,7 @@ var PROFILES = Object.freeze({
|
|
|
107
108
|
permissive: { timeoutMs: C.TIME.seconds(120), maxMessageBytes: C.BYTES.mib(150), maxResponseBytes: C.BYTES.mib(300) }, // operator-facing default mailbox cap
|
|
108
109
|
});
|
|
109
110
|
|
|
110
|
-
var COMPLIANCE_POSTURES =
|
|
111
|
-
hipaa: "strict",
|
|
112
|
-
"pci-dss": "strict",
|
|
113
|
-
gdpr: "strict",
|
|
114
|
-
soc2: "strict",
|
|
115
|
-
});
|
|
111
|
+
var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
|
|
116
112
|
|
|
117
113
|
var ALLOWED_PROTOCOLS = Object.freeze({
|
|
118
114
|
"icap": true,
|
package/lib/mail-spam-score.js
CHANGED
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
var { defineClass } = require("./framework-error");
|
|
71
71
|
var lazyRequire = require("./lazy-require");
|
|
72
72
|
var validateOpts = require("./validate-opts");
|
|
73
|
+
var gateContract = require("./gate-contract");
|
|
73
74
|
|
|
74
75
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
75
76
|
|
|
@@ -90,12 +91,7 @@ var PROFILES = Object.freeze({
|
|
|
90
91
|
permissive: { threshold: 10.0, maxReasons: MAX_REASONS, maxReasonBytes: MAX_REASON_BYTES },
|
|
91
92
|
});
|
|
92
93
|
|
|
93
|
-
var COMPLIANCE_POSTURES =
|
|
94
|
-
hipaa: "strict",
|
|
95
|
-
"pci-dss": "strict",
|
|
96
|
-
gdpr: "strict",
|
|
97
|
-
soc2: "strict",
|
|
98
|
-
});
|
|
94
|
+
var COMPLIANCE_POSTURES = gateContract.ALL_STRICT_POSTURES;
|
|
99
95
|
|
|
100
96
|
/**
|
|
101
97
|
* @primitive b.mail.spamScore.create
|