@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/external-db.js
CHANGED
|
@@ -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
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
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({
|
package/lib/framework-error.js
CHANGED
|
@@ -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
|
+
};
|