@blamejs/core 0.14.27 → 0.15.1

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 +6 -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 +158 -77
  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 +228 -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 +82 -29
  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 +517 -256
  26. package/lib/db-schema.js +209 -44
  27. package/lib/db.js +202 -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 +293 -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 +116 -72
  125. package/lib/sql.js +3885 -0
  126. package/lib/static.js +45 -7
  127. package/lib/subject.js +89 -49
  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
@@ -52,6 +52,10 @@ var log = boot("external-db");
52
52
  var audit = lazyRequire(function () { return require("./audit"); });
53
53
  var db = lazyRequire(function () { return require("./db"); });
54
54
  var observability = lazyRequire(function () { return require("./observability"); });
55
+ // b.sql composes the framework's own internal queries against a backend
56
+ // (e.g. the pg_roles hardening scan). Lazy because b.sql -> framework-schema
57
+ // -> external-db would cycle at module load; resolved when a query runs.
58
+ var sql = lazyRequire(function () { return require("./sql"); });
55
59
 
56
60
  function _emitMetric(name, value, labels) {
57
61
  try { observability().event(name, value, labels || {}); }
@@ -79,7 +83,7 @@ function _emitMetric(name, value, labels) {
79
83
  // cannot backtrack polynomially (CWE-1333 ReDoS).
80
84
  var _STATEMENT_CLASS_RE = /^(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/|--[^\n]*\n)*([A-Za-z]+)/;
81
85
  var _STATEMENT_CLASS_MAP = Object.freeze({
82
- SELECT: "SELECT", VALUES: "SELECT", TABLE: "SELECT",
86
+ SELECT: "SELECT", VALUES: "SELECT", TABLE: "SELECT", // allow:hand-rolled-sql — leading-keyword classifier table, not composed SQL
83
87
  SHOW: "READ_INFO", DESCRIBE: "READ_INFO", DESC: "READ_INFO",
84
88
  PRAGMA: "READ_INFO", USE: "READ_INFO",
85
89
  INSERT: "DML", UPDATE: "DML", DELETE: "DML", MERGE: "DML",
@@ -762,7 +766,7 @@ function init(opts) {
762
766
  return async function () {
763
767
  var client = await cn();
764
768
  try {
765
- await qn(client, "SET application_name TO " + quotedAppName, []);
769
+ await qn(client, "SET application_name TO " + quotedAppName, []); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
766
770
  } catch (_e) {
767
771
  // Best-effort. Real Postgres always supports SET
768
772
  // application_name; a driver that refuses it is a shim
@@ -1210,10 +1214,10 @@ async function transaction(fn, opts) {
1210
1214
  try {
1211
1215
  await b.beginTx(client);
1212
1216
  if (typeof stmtTimeoutMs === "number" && isFinite(stmtTimeoutMs) && stmtTimeoutMs > 0) {
1213
- await b.query(client, "SET LOCAL statement_timeout = " + Math.floor(stmtTimeoutMs), []);
1217
+ await b.query(client, "SET LOCAL statement_timeout = " + Math.floor(stmtTimeoutMs), []); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
1214
1218
  }
1215
1219
  if (typeof idleTimeoutMs === "number" && isFinite(idleTimeoutMs) && idleTimeoutMs > 0) {
1216
- await b.query(client, "SET LOCAL idle_in_transaction_session_timeout = " + Math.floor(idleTimeoutMs), []);
1220
+ await b.query(client, "SET LOCAL idle_in_transaction_session_timeout = " + Math.floor(idleTimeoutMs), []); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
1217
1221
  }
1218
1222
  for (var gi = 0; gi < prebuiltGucs.length; gi++) {
1219
1223
  await b.query(client, prebuiltGucs[gi], []);
@@ -1312,7 +1316,7 @@ async function _pingBackend(b) {
1312
1316
  var client = await b.pool.acquire();
1313
1317
  try {
1314
1318
  if (b.ping) await b.ping(client);
1315
- else await b.query(client, "SELECT 1", []);
1319
+ else await b.query(client, "SELECT 1", []); // allow:hand-rolled-sql — fixed connectivity ping (no table / b.sql verb)
1316
1320
  b.pool.release(client);
1317
1321
  return { ok: true, breakerState: b.breaker.getState(), pool: b.pool.stats() };
1318
1322
  } catch (e) {
@@ -1451,7 +1455,7 @@ function _buildSessionGucsStatements(sessionGucs) {
1451
1455
  "sessionGucs['" + name + "']: value must be a string, finite number, or boolean (got " +
1452
1456
  typeof value + ")", true);
1453
1457
  }
1454
- out.push("SET LOCAL " + qName + " = " + literal);
1458
+ out.push("SET LOCAL " + qName + " = " + literal); // allow:hand-rolled-sql — Postgres session GUC SET (no table; not a b.sql verb)
1455
1459
  }
1456
1460
  return out;
1457
1461
  }
@@ -2143,22 +2147,27 @@ function _connectAs(rawConnect, query, opts) {
2143
2147
 
2144
2148
  // Pre-compute the SET statements once — every fresh client runs the
2145
2149
  // same list, so building it per-connect would burn microbenchmarks.
2150
+ // Postgres session-config statements (SET ROLE / search_path /
2151
+ // application_name / statement_timeout / GUCs). These are session-state
2152
+ // commands, not table DML — b.sql has no SET verb, so they stay
2153
+ // hand-composed (identifiers double-quoted, string values single-quote
2154
+ // escaped, numerics rendered after a finite-check below).
2146
2155
  var stmts = [];
2147
2156
  if (opts.role) {
2148
- stmts.push('SET ROLE "' + opts.role + '"');
2157
+ stmts.push('SET ROLE "' + opts.role + '"'); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
2149
2158
  }
2150
2159
  if (pathSegments) {
2151
2160
  var pathSql = pathSegments.map(function (s) { return '"' + s + '"'; }).join(", ");
2152
- stmts.push("SET search_path TO " + pathSql);
2161
+ stmts.push("SET search_path TO " + pathSql); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
2153
2162
  }
2154
2163
  if (opts.applicationName !== undefined) {
2155
2164
  // Single-quoted string literal — SQL-standard escape doubles embedded
2156
2165
  // single quotes.
2157
2166
  var an = String(opts.applicationName).replace(/'/g, "''");
2158
- stmts.push("SET application_name TO '" + an + "'");
2167
+ stmts.push("SET application_name TO '" + an + "'"); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
2159
2168
  }
2160
2169
  if (opts.statementTimeoutMs !== undefined) {
2161
- stmts.push("SET statement_timeout TO " + opts.statementTimeoutMs);
2170
+ stmts.push("SET statement_timeout TO " + opts.statementTimeoutMs); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
2162
2171
  }
2163
2172
  if (opts.gucs) {
2164
2173
  for (var gn in opts.gucs) {
@@ -2467,11 +2476,12 @@ async function assertRoleHardening(opts) {
2467
2476
  var ignoreSystem = opts.ignoreSystem !== false; // default true
2468
2477
  var rows;
2469
2478
  try {
2470
- var res = await query(
2471
- "SELECT rolname FROM pg_roles ORDER BY rolname",
2472
- [],
2473
- { backend: backendName }
2474
- );
2479
+ // pg_roles is a Postgres system catalog (this path is Postgres-only —
2480
+ // guarded above), so b.sql emits the bare unquoted catalog name with a
2481
+ // quoted projection. No params, no `?`, so no placeholder translation.
2482
+ var rolesBuilt = sql().select("pg_roles", { dialect: "postgres" })
2483
+ .columns(["rolname"]).orderBy("rolname", "asc").toSql();
2484
+ var res = await query(rolesBuilt.sql, rolesBuilt.params, { backend: backendName });
2475
2485
  rows = (res && res.rows) || [];
2476
2486
  } catch (e) {
2477
2487
  audit().safeEmit({
@@ -215,6 +215,16 @@ var GuardSvgError = defineClass("GuardSvgError", { alwaysPermane
215
215
  // (Windows strips them silently), unicode bidi/RTLO file-name spoofing,
216
216
  // overlong UTF-8 encoding, length caps. alwaysPermanent.
217
217
  var GuardFilenameError = defineClass("GuardFilenameError", { alwaysPermanent: true });
218
+ // GuardSqlError covers raw-SQL refusals from the b.guardSql guard: the
219
+ // OS-reach floor (file / exec / FDW / extension / privilege-pivot
220
+ // across Postgres / SQLite / MySQL), stacked statements, comment
221
+ // smuggling, embedded string literals in a fragment, invalid UTF-8
222
+ // (CVE-2025-1094 encoding-bypass class), time-based probes, schema
223
+ // recon, and the migration DDL-verb allowlist. DOT-style codes
224
+ // (sql.refuse / sql.stacked / sql.file-access / ...) so they don't
225
+ // collide with SafeSqlError's slash codes (sql/bad-shape / ...).
226
+ // alwaysPermanent.
227
+ var GuardSqlError = defineClass("GuardSqlError", { alwaysPermanent: true });
218
228
  // GuardArchiveError covers archive-shape violations: zip-slip path
219
229
  // traversal, symlink + hardlink escape, decompression-ratio bombs,
220
230
  // nested-archive depth, file-count + total-size + per-entry-size caps,
@@ -687,6 +697,7 @@ module.exports = {
687
697
  GuardHtmlError: GuardHtmlError,
688
698
  GuardSvgError: GuardSvgError,
689
699
  GuardFilenameError: GuardFilenameError,
700
+ GuardSqlError: GuardSqlError,
690
701
  GuardArchiveError: GuardArchiveError,
691
702
  GuardJsonError: GuardJsonError,
692
703
  GuardYamlError: GuardYamlError,
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ // framework-files — the single source of truth for the framework's on-disk
4
+ // state file names. Centralized (mirroring framework-schema's table-name
5
+ // registry) so a rename / relocation is a one-line change and no module
6
+ // hardcodes the literal. Every owner resolves its file name through
7
+ // fileName(logical) instead of embedding the string; the codebase-patterns
8
+ // `no-hardcoded-framework-file-name` detector drives the remaining owners
9
+ // onto this registry in reverse (a file that still hardcodes a registered
10
+ // name fails the gate once it leaves the migration backlog).
11
+ //
12
+ // Internal infrastructure (not a public b.* namespace) — consumed by db /
13
+ // vault / audit / backup the way constants.js is.
14
+
15
+ var { FrameworkError } = require("./framework-error");
16
+
17
+ // Canonical state file names. Each is a BARE file name (no path) joined onto
18
+ // the operator's dataDir / a sub-path by the owner. Security-/durability-
19
+ // sensitive files only — templated names (e.g. the hashed working-db file)
20
+ // are not registered here.
21
+ var DEFAULT_FILE_NAMES = Object.freeze({
22
+ dbEnc: "db.enc", // encrypted-at-rest database ciphertext
23
+ dbKeyEnc: "db.key.enc", // sealed database encryption key
24
+ vaultKey: "vault.key", // sealed vault keypair
25
+ auditTip: "audit.tip", // audit rollback-detection sidecar
26
+ auditSignKey: "audit-sign.key", // sealed audit-signing keypair
27
+ rowsEnc: "rows.enc", // archive/backup rows ciphertext member
28
+ checkpointEnc: "checkpoint.enc", // archive/backup checkpoint ciphertext member
29
+ });
30
+
31
+ var _overrides = {};
32
+
33
+ // fileName(logical) — resolve a logical file key to its configured (or
34
+ // default) bare file name. Defensive request-shape reader: throws on an
35
+ // unknown logical key (a typo is a boot-time bug, not a runtime default).
36
+ function fileName(logical) {
37
+ if (Object.prototype.hasOwnProperty.call(_overrides, logical)) return _overrides[logical];
38
+ if (Object.prototype.hasOwnProperty.call(DEFAULT_FILE_NAMES, logical)) {
39
+ return DEFAULT_FILE_NAMES[logical];
40
+ }
41
+ throw new FrameworkError(
42
+ "frameworkFiles.fileName: unknown logical file '" + logical + "'",
43
+ "framework-files/unknown");
44
+ }
45
+
46
+ // setFileName(logical, name) — config-time override of a state file name.
47
+ // THROW on bad input (entry-point tier): the override must name a known
48
+ // logical key and be a bare file name (no path separators, no '..') so it
49
+ // can't redirect a sealed-key write outside the data dir.
50
+ function setFileName(logical, name) {
51
+ if (!Object.prototype.hasOwnProperty.call(DEFAULT_FILE_NAMES, logical)) {
52
+ throw new FrameworkError(
53
+ "frameworkFiles.setFileName: unknown logical file '" + logical + "'",
54
+ "framework-files/unknown");
55
+ }
56
+ if (typeof name !== "string" || name.length === 0 ||
57
+ name.indexOf("/") !== -1 || name.indexOf("\\") !== -1 || name.indexOf("..") !== -1) {
58
+ throw new FrameworkError(
59
+ "frameworkFiles.setFileName: name must be a non-empty bare file name " +
60
+ "(no path separators or '..')", "framework-files/bad-name");
61
+ }
62
+ _overrides[logical] = name;
63
+ }
64
+
65
+ // Test/boot helper — drop all overrides back to the defaults.
66
+ function _resetForTest() { _overrides = {}; }
67
+
68
+ module.exports = {
69
+ fileName: fileName,
70
+ setFileName: setFileName,
71
+ DEFAULT_FILE_NAMES: DEFAULT_FILE_NAMES,
72
+ _resetForTest: _resetForTest,
73
+ };