@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +2 -2
  3. package/index.js +4 -0
  4. package/lib/ai-content-detect.js +9 -10
  5. package/lib/api-key.js +107 -74
  6. package/lib/atomic-file.js +29 -1
  7. package/lib/audit-chain.js +47 -11
  8. package/lib/audit-sign.js +77 -2
  9. package/lib/audit-tools.js +79 -51
  10. package/lib/audit.js +218 -100
  11. package/lib/backup/index.js +13 -10
  12. package/lib/break-glass.js +202 -144
  13. package/lib/cache.js +174 -105
  14. package/lib/chain-writer.js +38 -16
  15. package/lib/cli.js +19 -14
  16. package/lib/cluster-provider-db.js +130 -104
  17. package/lib/cluster-storage.js +119 -22
  18. package/lib/cluster.js +119 -71
  19. package/lib/compliance.js +22 -0
  20. package/lib/consent.js +73 -24
  21. package/lib/constants.js +16 -11
  22. package/lib/crypto-field.js +387 -91
  23. package/lib/db-declare-row-policy.js +35 -22
  24. package/lib/db-file-lifecycle.js +3 -2
  25. package/lib/db-query.js +497 -255
  26. package/lib/db-schema.js +209 -44
  27. package/lib/db.js +176 -95
  28. package/lib/external-db-migrate.js +229 -139
  29. package/lib/external-db.js +25 -15
  30. package/lib/framework-error.js +11 -0
  31. package/lib/framework-files.js +73 -0
  32. package/lib/framework-schema.js +695 -394
  33. package/lib/gate-contract.js +596 -1
  34. package/lib/guard-agent-registry.js +26 -44
  35. package/lib/guard-all.js +1 -0
  36. package/lib/guard-auth.js +42 -112
  37. package/lib/guard-cidr.js +33 -154
  38. package/lib/guard-csv.js +46 -113
  39. package/lib/guard-domain.js +34 -157
  40. package/lib/guard-dsn.js +27 -43
  41. package/lib/guard-email.js +47 -69
  42. package/lib/guard-envelope.js +19 -32
  43. package/lib/guard-event-bus-payload.js +24 -42
  44. package/lib/guard-event-bus-topic.js +25 -43
  45. package/lib/guard-filename.js +42 -106
  46. package/lib/guard-graphql.js +42 -123
  47. package/lib/guard-html.js +53 -108
  48. package/lib/guard-idempotency-key.js +24 -42
  49. package/lib/guard-image.js +46 -103
  50. package/lib/guard-imap-command.js +18 -32
  51. package/lib/guard-jmap.js +16 -30
  52. package/lib/guard-json.js +38 -108
  53. package/lib/guard-jsonpath.js +38 -171
  54. package/lib/guard-jwt.js +49 -179
  55. package/lib/guard-list-id.js +25 -41
  56. package/lib/guard-list-unsubscribe.js +27 -43
  57. package/lib/guard-mail-compose.js +24 -42
  58. package/lib/guard-mail-move.js +26 -44
  59. package/lib/guard-mail-query.js +28 -46
  60. package/lib/guard-mail-reply.js +24 -42
  61. package/lib/guard-mail-sieve.js +24 -42
  62. package/lib/guard-managesieve-command.js +17 -31
  63. package/lib/guard-markdown.js +37 -104
  64. package/lib/guard-message-id.js +26 -45
  65. package/lib/guard-mime.js +39 -151
  66. package/lib/guard-oauth.js +54 -135
  67. package/lib/guard-pdf.js +45 -101
  68. package/lib/guard-pop3-command.js +21 -31
  69. package/lib/guard-posture-chain.js +24 -42
  70. package/lib/guard-regex.js +33 -107
  71. package/lib/guard-saga-config.js +24 -42
  72. package/lib/guard-shell.js +42 -172
  73. package/lib/guard-smtp-command.js +48 -54
  74. package/lib/guard-snapshot-envelope.js +24 -42
  75. package/lib/guard-sql.js +1491 -0
  76. package/lib/guard-stream-args.js +24 -43
  77. package/lib/guard-svg.js +47 -65
  78. package/lib/guard-template.js +35 -172
  79. package/lib/guard-tenant-id.js +26 -45
  80. package/lib/guard-time.js +32 -154
  81. package/lib/guard-trace-context.js +25 -44
  82. package/lib/guard-uuid.js +32 -153
  83. package/lib/guard-xml.js +38 -113
  84. package/lib/guard-yaml.js +51 -163
  85. package/lib/http-client.js +14 -0
  86. package/lib/inbox.js +120 -107
  87. package/lib/legal-hold.js +107 -50
  88. package/lib/log-stream-cloudwatch.js +47 -31
  89. package/lib/log-stream-otlp.js +32 -18
  90. package/lib/mail-crypto-smime.js +2 -6
  91. package/lib/mail-greylist.js +2 -6
  92. package/lib/mail-helo.js +2 -6
  93. package/lib/mail-journal.js +85 -64
  94. package/lib/mail-rbl.js +2 -6
  95. package/lib/mail-scan.js +2 -6
  96. package/lib/mail-spam-score.js +2 -6
  97. package/lib/mail-store.js +287 -154
  98. package/lib/middleware/fetch-metadata.js +17 -7
  99. package/lib/middleware/idempotency-key.js +54 -38
  100. package/lib/middleware/rate-limit.js +102 -32
  101. package/lib/middleware/security-headers.js +21 -5
  102. package/lib/migrations.js +108 -66
  103. package/lib/network-heartbeat.js +7 -0
  104. package/lib/nonce-store.js +31 -9
  105. package/lib/object-store/azure-blob-bucket-ops.js +9 -4
  106. package/lib/object-store/azure-blob.js +31 -3
  107. package/lib/object-store/sigv4.js +10 -0
  108. package/lib/outbox.js +136 -82
  109. package/lib/pqc-agent.js +44 -0
  110. package/lib/pubsub-cluster.js +42 -20
  111. package/lib/queue-local.js +202 -139
  112. package/lib/queue-redis.js +9 -1
  113. package/lib/queue-sqs.js +6 -0
  114. package/lib/retention.js +82 -39
  115. package/lib/safe-dns.js +29 -45
  116. package/lib/safe-ical.js +18 -33
  117. package/lib/safe-icap.js +27 -43
  118. package/lib/safe-sieve.js +21 -40
  119. package/lib/safe-sql.js +124 -3
  120. package/lib/safe-vcard.js +18 -33
  121. package/lib/scheduler.js +35 -12
  122. package/lib/seeders.js +122 -74
  123. package/lib/session-stores.js +42 -14
  124. package/lib/session.js +109 -72
  125. package/lib/sql.js +3885 -0
  126. package/lib/static.js +45 -7
  127. package/lib/subject.js +55 -17
  128. package/lib/vault/index.js +3 -2
  129. package/lib/vault/passphrase-ops.js +3 -2
  130. package/lib/vault/rotate.js +104 -64
  131. package/lib/vendor-data.js +2 -0
  132. package/lib/websocket.js +16 -0
  133. package/package.json +1 -1
  134. package/sbom.cdx.json +6 -6
@@ -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 safeSql = require("./safe-sql");
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
- // Route every identifier through safeSql.quoteIdentifier — the
217
- // shared substrate validates the unquoted name AND emits the
218
- // dialect-correct quoted form. Index names must be built from the
219
- // unquoted base then quoted independently; appending suffixes to
220
- // an already-quoted token produces invalid SQL like
221
- // `"_mail_journal_x"_archived_at_idx`.
222
- var rawTable = "_mail_journal_" + namespace.replace(/-/g, "_");
223
- var qTable = safeSql.quoteIdentifier(rawTable);
224
- var qIdxArchAt = safeSql.quoteIdentifier(rawTable + "_archived_at_idx");
225
- var qIdxMsgId = safeSql.quoteIdentifier(rawTable + "_message_id_idx");
226
- opts.db.runSql(
227
- "CREATE TABLE IF NOT EXISTS " + qTable + " (" +
228
- "journal_id TEXT PRIMARY KEY, " +
229
- "direction TEXT NOT NULL, " +
230
- "actor_id TEXT, " +
231
- "message_id TEXT, " +
232
- "archived_at INTEGER NOT NULL, " +
233
- "size_bytes INTEGER NOT NULL, " +
234
- "regimes TEXT NOT NULL, " +
235
- "floor_until INTEGER NOT NULL, " +
236
- "legal_hold INTEGER NOT NULL DEFAULT 0, " +
237
- "storage_key TEXT NOT NULL UNIQUE, " +
238
- "sealed_payload BLOB NOT NULL" +
239
- ");" +
240
- "CREATE INDEX IF NOT EXISTS " + qIdxArchAt + " ON " + qTable + " (archived_at);" +
241
- "CREATE INDEX IF NOT EXISTS " + qIdxMsgId + " ON " + qTable + " (message_id);"
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
- opts.db.runSql(
296
- "INSERT INTO " + qTable + " (journal_id, direction, actor_id, message_id, " +
297
- "archived_at, size_bytes, regimes, floor_until, legal_hold, storage_key, sealed_payload) " +
298
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?);",
299
- [journalId, req.direction, req.actorId, req.messageId,
300
- archivedAt, sizeBytes, regimesJson, floorUntil, storageKey, sealedBlob]
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 rows = opts.db.runSql(
321
- "SELECT direction, message_id, archived_at, size_bytes, regimes, floor_until, " +
322
- "legal_hold, storage_key, sealed_payload FROM " + qTable + " WHERE journal_id = ?;",
323
- [journalId]
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 args = [];
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 limit = numericBounds.isPositiveFiniteInt(filter.limit) ? Math.min(filter.limit, 1000) : 100; // list page cap
366
- var where = clauses.length > 0 ? " WHERE " + clauses.join(" AND ") : "";
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
- var rows = opts.db.runSql(
391
- "SELECT journal_id, archived_at, floor_until, message_id, regimes FROM " +
392
- qTable + " WHERE floor_until < ? AND legal_hold = 0 ORDER BY archived_at ASC LIMIT 1000;", // expiry-surface cap
393
- [now]
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
- opts.db.runSql(
413
- "UPDATE " + qTable + " SET legal_hold = ? WHERE journal_id = ?;",
414
- [onHold ? 1 : 0, journalId]
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 = Object.freeze({
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 = Object.freeze({
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,
@@ -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 = Object.freeze({
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