@blamejs/core 0.14.26 → 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 +6 -0
- package/README.md +2 -2
- package/index.js +4 -0
- package/lib/agent-envelope-mac.js +104 -0
- package/lib/agent-event-bus.js +105 -4
- package/lib/agent-posture-chain.js +8 -42
- package/lib/ai-content-detect.js +9 -10
- package/lib/api-key.js +107 -74
- package/lib/atomic-file.js +62 -4
- 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 +249 -123
- package/lib/auth/openid-federation.js +108 -47
- 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 +169 -4
- package/lib/consent.js +73 -24
- package/lib/constants.js +16 -11
- package/lib/crypto-field.js +474 -92
- 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/error-page.js +14 -1
- package/lib/external-db-migrate.js +229 -139
- package/lib/external-db.js +25 -15
- package/lib/file-upload.js +52 -7
- package/lib/framework-error.js +14 -1
- package/lib/framework-files.js +73 -0
- package/lib/framework-schema.js +695 -394
- package/lib/gate-contract.js +649 -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 +37 -9
- 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-server-jmap.js +117 -12
- package/lib/mail-spam-score.js +2 -6
- package/lib/mail-store.js +287 -154
- package/lib/middleware/body-parser.js +71 -25
- package/lib/middleware/csrf-protect.js +19 -8
- 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 +57 -3
- package/lib/object-store/sigv4.js +10 -0
- package/lib/observability.js +87 -0
- package/lib/otel-export.js +25 -1
- package/lib/outbox.js +136 -82
- package/lib/parsers/safe-xml.js +47 -7
- 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/redact.js +68 -11
- package/lib/redis-client.js +160 -31
- package/lib/retention.js +82 -39
- package/lib/router.js +212 -5
- 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/ssrf-guard.js +51 -4
- package/lib/static.js +177 -34
- 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 +35 -5
- 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/file-upload.js
CHANGED
|
@@ -22,8 +22,12 @@
|
|
|
22
22
|
* content gate inspects the reassembled buffer, so it runs on uploads
|
|
23
23
|
* up to `maxStreamReassemblyBytes` (default 64 MiB); a larger upload
|
|
24
24
|
* is handed to `onFinalize` as a stream and the byte-content gate is
|
|
25
|
-
* skipped (MIME-sniff + filename gates still run
|
|
26
|
-
*
|
|
25
|
+
* skipped (MIME-sniff + filename gates still run). Every skip path —
|
|
26
|
+
* the upload streamed past the reassembly cap, no gate is registered
|
|
27
|
+
* for the file's extension, or `contentSafety: null` disabled scanning
|
|
28
|
+
* — emits a `fileUpload.content_safety_skipped` audit whose `reason`
|
|
29
|
+
* names the cause, so a security review of the audit log can tell which
|
|
30
|
+
* uploads reached storage without a content scan and why. To guarantee
|
|
27
31
|
* content-gating of a type, cap `maxFileBytes` at or below
|
|
28
32
|
* `maxStreamReassemblyBytes`. Per-chunk hooks
|
|
29
33
|
* (`onChunk`) are the integration point for virus scanners and
|
|
@@ -475,6 +479,32 @@ function create(opts) {
|
|
|
475
479
|
if (opts.observability) opts.observability.safeEvent(name, value, labels || {});
|
|
476
480
|
}
|
|
477
481
|
|
|
482
|
+
// Emit an audit row whenever the byte-level content-safety scan is
|
|
483
|
+
// SKIPPED for a finalized upload — so a security review of the audit
|
|
484
|
+
// log can tell that bytes reached storage without passing the
|
|
485
|
+
// content gate, and WHY. Without this, every skip path (operator
|
|
486
|
+
// opt-out, no gate registered for the file's extension, or the upload
|
|
487
|
+
// streamed past maxStreamReassemblyBytes) was silent: the audit log
|
|
488
|
+
// showed a clean `fileUpload.finalize` success indistinguishable from
|
|
489
|
+
// a scanned upload. `reason` names the skip cause so operators can
|
|
490
|
+
// alert / lower maxStreamReassemblyBytes / register the missing gate.
|
|
491
|
+
// Observability-only: `_emitAudit` wraps audit.safeEmit in try/catch
|
|
492
|
+
// (drop-silent — by design) so a throwing sink never breaks the upload.
|
|
493
|
+
function _emitContentSafetySkipped(uploadId, actor, reason, ext, size) {
|
|
494
|
+
_emitObs("fileUpload.content_safety_skipped", 1, { reason: reason, ext: ext || "" });
|
|
495
|
+
// outcome "success" — the upload itself finalized; the audit records
|
|
496
|
+
// that the byte-level scan did NOT run, with `reason` naming why
|
|
497
|
+
// (the only outcomes the audit chain accepts are success / failure /
|
|
498
|
+
// denied, so the skip-cause lives in `reason` + `metadata`).
|
|
499
|
+
_emitAudit("fileUpload.content_safety_skipped", {
|
|
500
|
+
actor: requestHelpers.extractActorContext(actor),
|
|
501
|
+
resource: { kind: "fileUpload", id: uploadId },
|
|
502
|
+
outcome: "success",
|
|
503
|
+
reason: reason,
|
|
504
|
+
metadata: { uploadId: uploadId, ext: ext || null, size: size, reason: reason },
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
478
508
|
// Staging dir mode 0o700 — only the framework process reads its own
|
|
479
509
|
// staging files.
|
|
480
510
|
atomicFile.ensureDir(stagingDir, 0o700);
|
|
@@ -1088,12 +1118,27 @@ function create(opts) {
|
|
|
1088
1118
|
// upload streamed past maxStreamReassemblyBytes and was never
|
|
1089
1119
|
// reassembled into a buffer the byte-level gate can inspect. The
|
|
1090
1120
|
// MIME-sniff and filename gates still ran; the per-extension
|
|
1091
|
-
// content gate did NOT.
|
|
1092
|
-
//
|
|
1093
|
-
//
|
|
1094
|
-
|
|
1095
|
-
|
|
1121
|
+
// content gate did NOT. Audit the skip (with the streamed reason)
|
|
1122
|
+
// so operators can alert, lower maxStreamReassemblyBytes, or cap
|
|
1123
|
+
// maxFileBytes to force content-gating of this type.
|
|
1124
|
+
_emitContentSafetySkipped(uploadId, actor, "streamed-over-reassembly-cap",
|
|
1125
|
+
safetyExt, verified.totalBytes);
|
|
1126
|
+
} else {
|
|
1127
|
+
// contentSafety is wired but no gate is registered for this file's
|
|
1128
|
+
// extension — the byte-level scan does not run. Audit the skip so
|
|
1129
|
+
// a review can tell the upload bypassed content scanning (and
|
|
1130
|
+
// register a gate for the extension if it should be scanned).
|
|
1131
|
+
_emitContentSafetySkipped(uploadId, actor, "no-gate-for-extension",
|
|
1132
|
+
safetyExt, verified.totalBytes);
|
|
1096
1133
|
}
|
|
1134
|
+
} else {
|
|
1135
|
+
// Content-safety scanning is disabled for this upload manager
|
|
1136
|
+
// (contentSafety: null opt-out at create()). The create-time audit
|
|
1137
|
+
// recorded the disable; this per-upload audit makes the bypass
|
|
1138
|
+
// visible at the point bytes reached storage.
|
|
1139
|
+
_emitContentSafetySkipped(uploadId, actor, "content-safety-disabled",
|
|
1140
|
+
nodePath.extname(filename).toLowerCase(),
|
|
1141
|
+
verified.totalBytes);
|
|
1097
1142
|
}
|
|
1098
1143
|
|
|
1099
1144
|
// Hand to operator's onFinalize.
|
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,
|
|
@@ -557,7 +567,9 @@ var WatcherError = defineClass("WatcherError", { alwaysPermane
|
|
|
557
567
|
// caller-shape misuse or an irrecoverable on-disk condition.
|
|
558
568
|
var LocalDbThinError = defineClass("LocalDbThinError", { alwaysPermanent: true });
|
|
559
569
|
// RouterError covers operator-shape violations on the router primitive:
|
|
560
|
-
// invalid `allowedRedirectOrigins` opt at create time,
|
|
570
|
+
// invalid `allowedRedirectOrigins` opt at create time, a malformed
|
|
571
|
+
// `use()` mount (non-string / non-array prefix, a prefix not beginning
|
|
572
|
+
// with "/", a missing or non-function middleware), and cross-origin
|
|
561
573
|
// `res.redirect()` targets that are not on the allowlist. alwaysPermanent
|
|
562
574
|
// — every case is config-time programming bug or an outbound-redirect
|
|
563
575
|
// shape error that retry will not recover.
|
|
@@ -685,6 +697,7 @@ module.exports = {
|
|
|
685
697
|
GuardHtmlError: GuardHtmlError,
|
|
686
698
|
GuardSvgError: GuardSvgError,
|
|
687
699
|
GuardFilenameError: GuardFilenameError,
|
|
700
|
+
GuardSqlError: GuardSqlError,
|
|
688
701
|
GuardArchiveError: GuardArchiveError,
|
|
689
702
|
GuardJsonError: GuardJsonError,
|
|
690
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
|
+
};
|