@blamejs/core 0.4.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.
- package/CHANGELOG.md +230 -0
- package/LICENSE +201 -0
- package/LTS-CALENDAR.md +29 -0
- package/MIGRATING.md +7 -0
- package/NOTICE +59 -0
- package/README.md +100 -0
- package/bin/blamejs.js +13 -0
- package/index.js +253 -0
- package/lib/api-key.js +705 -0
- package/lib/api-snapshot.js +335 -0
- package/lib/app-shutdown.js +381 -0
- package/lib/app.js +364 -0
- package/lib/atomic-file.js +525 -0
- package/lib/audit-chain.js +168 -0
- package/lib/audit-sign.js +319 -0
- package/lib/audit-tools.js +682 -0
- package/lib/audit.js +753 -0
- package/lib/auth/jwt.js +280 -0
- package/lib/auth/oauth.js +691 -0
- package/lib/auth/passkey.js +185 -0
- package/lib/auth/password.js +139 -0
- package/lib/auth/totp.js +17 -0
- package/lib/auth-header.js +81 -0
- package/lib/backup/bundle.js +219 -0
- package/lib/backup/crypto.js +174 -0
- package/lib/backup/index.js +490 -0
- package/lib/backup/manifest.js +275 -0
- package/lib/bundler.js +295 -0
- package/lib/cache.js +819 -0
- package/lib/chain-writer.js +234 -0
- package/lib/cli-helpers.js +201 -0
- package/lib/cli.js +1377 -0
- package/lib/cluster-provider-db.js +245 -0
- package/lib/cluster-storage.js +166 -0
- package/lib/cluster.js +691 -0
- package/lib/consent.js +222 -0
- package/lib/constants.js +186 -0
- package/lib/cookies.js +293 -0
- package/lib/credential-hash.js +303 -0
- package/lib/crypto-field.js +159 -0
- package/lib/crypto.js +250 -0
- package/lib/db-query.js +297 -0
- package/lib/db-schema.js +250 -0
- package/lib/db.js +1054 -0
- package/lib/deprecate.js +226 -0
- package/lib/dev.js +324 -0
- package/lib/error-page.js +424 -0
- package/lib/events.js +135 -0
- package/lib/external-db.js +422 -0
- package/lib/forms.js +378 -0
- package/lib/framework-error.js +189 -0
- package/lib/framework-schema.js +604 -0
- package/lib/handlers.js +350 -0
- package/lib/html-balance.js +227 -0
- package/lib/http-client.js +615 -0
- package/lib/i18n.js +780 -0
- package/lib/jobs.js +181 -0
- package/lib/lazy-require.js +48 -0
- package/lib/log-stream-local.js +137 -0
- package/lib/log-stream-webhook.js +170 -0
- package/lib/log-stream.js +211 -0
- package/lib/log.js +355 -0
- package/lib/mail-bounce.js +507 -0
- package/lib/mail.js +701 -0
- package/lib/metrics.js +647 -0
- package/lib/middleware/api-encrypt.js +553 -0
- package/lib/middleware/attach-user.js +156 -0
- package/lib/middleware/body-parser.js +883 -0
- package/lib/middleware/bot-guard.js +148 -0
- package/lib/middleware/compression.js +436 -0
- package/lib/middleware/cors.js +236 -0
- package/lib/middleware/csp-nonce.js +332 -0
- package/lib/middleware/csrf-protect.js +275 -0
- package/lib/middleware/error-handler.js +46 -0
- package/lib/middleware/health.js +358 -0
- package/lib/middleware/index.js +52 -0
- package/lib/middleware/rate-limit.js +319 -0
- package/lib/middleware/request-id.js +53 -0
- package/lib/middleware/require-auth.js +95 -0
- package/lib/middleware/security-headers.js +91 -0
- package/lib/migrations.js +353 -0
- package/lib/mtls-ca.js +333 -0
- package/lib/mtls-engine-default.js +285 -0
- package/lib/nonce-store.js +177 -0
- package/lib/notify.js +643 -0
- package/lib/ntp-check.js +178 -0
- package/lib/object-store/azure-blob.js +467 -0
- package/lib/object-store/gcs.js +469 -0
- package/lib/object-store/http-put.js +153 -0
- package/lib/object-store/index.js +140 -0
- package/lib/object-store/local.js +163 -0
- package/lib/object-store/retry.js +15 -0
- package/lib/object-store/sigv4.js +535 -0
- package/lib/observability.js +114 -0
- package/lib/pagination.js +371 -0
- package/lib/parsers/index.js +64 -0
- package/lib/parsers/safe-csv.js +224 -0
- package/lib/parsers/safe-env.js +614 -0
- package/lib/parsers/safe-toml.js +745 -0
- package/lib/parsers/safe-xml.js +379 -0
- package/lib/parsers/safe-yaml.js +977 -0
- package/lib/permissions.js +430 -0
- package/lib/pqc-agent.js +85 -0
- package/lib/pqc-gate.js +266 -0
- package/lib/protocol-dispatcher.js +144 -0
- package/lib/queue-local.js +327 -0
- package/lib/queue.js +430 -0
- package/lib/redact.js +192 -0
- package/lib/render.js +193 -0
- package/lib/request-helpers.js +178 -0
- package/lib/restore-bundle.js +239 -0
- package/lib/restore-rollback.js +254 -0
- package/lib/restore.js +301 -0
- package/lib/retry.js +329 -0
- package/lib/router.js +437 -0
- package/lib/safe-async.js +520 -0
- package/lib/safe-buffer.js +162 -0
- package/lib/safe-json.js +532 -0
- package/lib/safe-schema.js +1176 -0
- package/lib/safe-sql.js +157 -0
- package/lib/safe-url.js +109 -0
- package/lib/scheduler.js +680 -0
- package/lib/seeders.js +622 -0
- package/lib/session.js +304 -0
- package/lib/slug.js +243 -0
- package/lib/static.js +268 -0
- package/lib/storage.js +470 -0
- package/lib/subject.js +281 -0
- package/lib/template.js +781 -0
- package/lib/testing.js +621 -0
- package/lib/totp.js +285 -0
- package/lib/tracing.js +484 -0
- package/lib/validate-opts.js +56 -0
- package/lib/vault/index.js +299 -0
- package/lib/vault/passphrase-ops.js +311 -0
- package/lib/vault/passphrase-source.js +198 -0
- package/lib/vault/rotate.js +761 -0
- package/lib/vault/wrap.js +289 -0
- package/lib/vendor/MANIFEST.json +84 -0
- package/lib/vendor/argon2/argon2.cjs +466 -0
- package/lib/vendor/argon2/argon2.d.cts +62 -0
- package/lib/vendor/argon2/package.json +1 -0
- package/lib/vendor/argon2/prebuilds/darwin-arm64/argon2.armv8.glibc.node +0 -0
- package/lib/vendor/argon2/prebuilds/darwin-x64/argon2.glibc.node +0 -0
- package/lib/vendor/argon2/prebuilds/freebsd-arm64/argon2.armv8.glibc.node +0 -0
- package/lib/vendor/argon2/prebuilds/freebsd-x64/argon2.glibc.node +0 -0
- package/lib/vendor/argon2/prebuilds/linux-arm/argon2.armv7.glibc.node +0 -0
- package/lib/vendor/argon2/prebuilds/linux-arm/argon2.armv7.musl.node +0 -0
- package/lib/vendor/argon2/prebuilds/linux-arm64/argon2.armv8.glibc.node +0 -0
- package/lib/vendor/argon2/prebuilds/linux-arm64/argon2.armv8.musl.node +0 -0
- package/lib/vendor/argon2/prebuilds/linux-x64/argon2.glibc.node +0 -0
- package/lib/vendor/argon2/prebuilds/linux-x64/argon2.musl.node +0 -0
- package/lib/vendor/argon2/prebuilds/win32-x64/argon2.glibc.node +0 -0
- package/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/webhook.js +632 -0
- package/lib/websocket-channels.js +413 -0
- package/lib/websocket.js +833 -0
- package/package.json +39 -0
package/lib/safe-sql.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SQL safety primitive — identifier validation and parameterized query
|
|
4
|
+
* helpers.
|
|
5
|
+
*
|
|
6
|
+
* The framework's own SQL is parameterized everywhere — values bind
|
|
7
|
+
* through node:sqlite or external-db drivers, never string-concatenated.
|
|
8
|
+
* That covers value injection. The remaining attack surface is
|
|
9
|
+
* IDENTIFIER injection: when a TABLE name or COLUMN name is interpolated
|
|
10
|
+
* into a SQL string, a malicious or misconfigured caller can break out
|
|
11
|
+
* of the intended structure. This is rare in framework-internal code
|
|
12
|
+
* (we control the names) but easy to get wrong if operator-supplied
|
|
13
|
+
* config flows in unchecked.
|
|
14
|
+
*
|
|
15
|
+
* This module provides the thin validator that any code interpolating
|
|
16
|
+
* identifiers should call first. It does NOT execute SQL; it just
|
|
17
|
+
* validates the inputs that get composed into SQL elsewhere.
|
|
18
|
+
*
|
|
19
|
+
* Public API:
|
|
20
|
+
* safeSql.validateIdentifier(name, opts?) throws on bad shape
|
|
21
|
+
* safeSql.quoteIdentifier(name, dialect?) returns "name" / `name`
|
|
22
|
+
* safeSql.assertOneOf(name, allowlist) throws unless in list
|
|
23
|
+
* safeSql.SafeSqlError error class
|
|
24
|
+
*
|
|
25
|
+
* Identifier rule (default): `^[A-Za-z_][A-Za-z0-9_]*$` with length
|
|
26
|
+
* 1–63 chars (Postgres NAMEDATALEN default; SQLite has no hard
|
|
27
|
+
* limit but matching the strictest dialect is the safe bound).
|
|
28
|
+
*
|
|
29
|
+
* Forbidden in identifiers regardless of regex:
|
|
30
|
+
* - SQL reserved words (a small allowlist, not exhaustive — operators
|
|
31
|
+
* who need reserved-word identifiers should quote them and accept
|
|
32
|
+
* the dialect-specific quoting)
|
|
33
|
+
* - Leading underscore prefixed `sqlite_` (SQLite-internal)
|
|
34
|
+
* - Embedded null byte
|
|
35
|
+
*
|
|
36
|
+
* Quoting:
|
|
37
|
+
* sqlite "name" (double-quote per SQL standard; SQLite
|
|
38
|
+
* accepts double quotes for identifiers
|
|
39
|
+
* per its quirks settings)
|
|
40
|
+
* postgres "name"
|
|
41
|
+
* mysql `name` (MySQL's backtick convention)
|
|
42
|
+
*
|
|
43
|
+
* Allowlist usage (recommended):
|
|
44
|
+
* var ALLOWED_TABLES = new Set(["audit_log", "consent_log", …]);
|
|
45
|
+
* safeSql.assertOneOf(operatorTableName, ALLOWED_TABLES);
|
|
46
|
+
* var sql = "INSERT INTO " + safeSql.quoteIdentifier(operatorTableName) + " …";
|
|
47
|
+
*
|
|
48
|
+
* The allowlist is the strongest guarantee. Operators with dynamic
|
|
49
|
+
* identifier needs (rare) use validateIdentifier alone, accepting
|
|
50
|
+
* that any string passing the regex is allowed.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// Reserved-word block list — the most dangerous to accept as a bare
|
|
54
|
+
// identifier. Not exhaustive; the allowlist pattern below is preferred.
|
|
55
|
+
var BANNED_IDENTIFIERS = new Set([
|
|
56
|
+
"select", "insert", "update", "delete", "drop", "create", "alter",
|
|
57
|
+
"truncate", "grant", "revoke", "union", "exec", "execute",
|
|
58
|
+
"where", "from", "join", "into", "values", "table", "database",
|
|
59
|
+
"schema", "index", "view", "trigger", "procedure", "function",
|
|
60
|
+
"begin", "commit", "rollback", "savepoint",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
// Default identifier shape — Postgres NAMEDATALEN (63 chars) is the
|
|
64
|
+
// strictest of the dialects we support, so use it as the bound.
|
|
65
|
+
var DEFAULT_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
66
|
+
var MAX_IDENTIFIER_LENGTH = 63;
|
|
67
|
+
|
|
68
|
+
var { FrameworkError } = require("./framework-error");
|
|
69
|
+
|
|
70
|
+
class SafeSqlError extends FrameworkError {
|
|
71
|
+
constructor(message, code) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = "SafeSqlError";
|
|
74
|
+
this.code = code || "sql/invalid";
|
|
75
|
+
this.isSafeSqlError = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateIdentifier(name, opts) {
|
|
80
|
+
opts = opts || {};
|
|
81
|
+
if (typeof name !== "string") {
|
|
82
|
+
throw new SafeSqlError("identifier must be a string (got " + typeof name + ")",
|
|
83
|
+
"sql/bad-type");
|
|
84
|
+
}
|
|
85
|
+
if (name.length === 0) {
|
|
86
|
+
throw new SafeSqlError("identifier must not be empty", "sql/empty");
|
|
87
|
+
}
|
|
88
|
+
if (name.length > MAX_IDENTIFIER_LENGTH) {
|
|
89
|
+
throw new SafeSqlError(
|
|
90
|
+
"identifier '" + name + "' exceeds maxLength " + MAX_IDENTIFIER_LENGTH,
|
|
91
|
+
"sql/too-long"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (name.indexOf("\0") >= 0) {
|
|
95
|
+
throw new SafeSqlError("identifier contains null byte", "sql/null-byte");
|
|
96
|
+
}
|
|
97
|
+
var pattern = opts.pattern || DEFAULT_IDENTIFIER_RE;
|
|
98
|
+
if (!pattern.test(name)) {
|
|
99
|
+
throw new SafeSqlError(
|
|
100
|
+
"identifier '" + name + "' does not match required pattern " + pattern,
|
|
101
|
+
"sql/bad-shape"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!opts.allowReserved && BANNED_IDENTIFIERS.has(name.toLowerCase())) {
|
|
105
|
+
throw new SafeSqlError(
|
|
106
|
+
"identifier '" + name + "' is a SQL reserved word",
|
|
107
|
+
"sql/reserved-word"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (!opts.allowSqliteInternal && /^sqlite_/i.test(name)) {
|
|
111
|
+
throw new SafeSqlError(
|
|
112
|
+
"identifier '" + name + "' uses the SQLite-internal 'sqlite_' prefix",
|
|
113
|
+
"sql/internal-prefix"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return name;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function quoteIdentifier(name, dialect) {
|
|
120
|
+
validateIdentifier(name);
|
|
121
|
+
dialect = (dialect || "sqlite").toLowerCase();
|
|
122
|
+
if (dialect === "mysql") return "`" + name + "`";
|
|
123
|
+
// sqlite + postgres both use double-quote per SQL standard
|
|
124
|
+
return '"' + name + '"';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function assertOneOf(name, allowlist) {
|
|
128
|
+
if (typeof name !== "string") {
|
|
129
|
+
throw new SafeSqlError("name must be a string", "sql/bad-type");
|
|
130
|
+
}
|
|
131
|
+
// Accept either Set or array
|
|
132
|
+
var ok = false;
|
|
133
|
+
if (allowlist && typeof allowlist.has === "function") {
|
|
134
|
+
ok = allowlist.has(name);
|
|
135
|
+
} else if (Array.isArray(allowlist)) {
|
|
136
|
+
ok = allowlist.indexOf(name) !== -1;
|
|
137
|
+
} else {
|
|
138
|
+
throw new SafeSqlError("allowlist must be a Set or array", "sql/bad-allowlist");
|
|
139
|
+
}
|
|
140
|
+
if (!ok) {
|
|
141
|
+
throw new SafeSqlError(
|
|
142
|
+
"identifier '" + name + "' not in allowlist",
|
|
143
|
+
"sql/not-allowed"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return name;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
validateIdentifier: validateIdentifier,
|
|
151
|
+
quoteIdentifier: quoteIdentifier,
|
|
152
|
+
assertOneOf: assertOneOf,
|
|
153
|
+
SafeSqlError: SafeSqlError,
|
|
154
|
+
// Exposed so consumers can compose their own validators
|
|
155
|
+
DEFAULT_IDENTIFIER_RE: DEFAULT_IDENTIFIER_RE,
|
|
156
|
+
MAX_IDENTIFIER_LENGTH: MAX_IDENTIFIER_LENGTH,
|
|
157
|
+
};
|
package/lib/safe-url.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* URL-safe — validate URL scheme + shape against an allowlist.
|
|
4
|
+
*
|
|
5
|
+
* Per the framework's modernity stance: outbound network calls
|
|
6
|
+
* REQUIRE TLS by default. Operators with internal cleartext
|
|
7
|
+
* endpoints (development, behind-VPN services, internal mesh) opt in
|
|
8
|
+
* explicitly via opts.allowedProtocols. The framework refuses to
|
|
9
|
+
* silently drop bytes on the wire as cleartext.
|
|
10
|
+
*
|
|
11
|
+
* Public API:
|
|
12
|
+
*
|
|
13
|
+
* safeUrl.parse(url, opts?) → URL
|
|
14
|
+
* Returns a parsed URL object. Throws if the URL is malformed
|
|
15
|
+
* or its protocol is not in the allowlist.
|
|
16
|
+
*
|
|
17
|
+
* opts:
|
|
18
|
+
* allowedProtocols — array of accepted protocol strings
|
|
19
|
+
* (e.g. ["https:"] or safeUrl.ALLOW_HTTP_TLS).
|
|
20
|
+
* Default: ALLOW_HTTP_TLS.
|
|
21
|
+
* errorClass — FrameworkError subclass for the thrown
|
|
22
|
+
* error. Lets callers (object-store,
|
|
23
|
+
* log-stream, http-client) surface their
|
|
24
|
+
* own decorated error class. Default:
|
|
25
|
+
* SafeUrlError.
|
|
26
|
+
*
|
|
27
|
+
* Constants — pre-baked allowlists for the common caller cases:
|
|
28
|
+
*
|
|
29
|
+
* ALLOW_HTTP_TLS ["https:"] (the secure HTTP default)
|
|
30
|
+
* ALLOW_HTTP_ALL ["http:", "https:"] (HTTP + cleartext opt-in)
|
|
31
|
+
* ALLOW_WS_TLS ["wss:"] (the secure WS default)
|
|
32
|
+
* ALLOW_WS_ALL ["ws:", "wss:"] (WS + cleartext opt-in)
|
|
33
|
+
* ALLOW_ANY ["http:", "https:", "ws:", "wss:"]
|
|
34
|
+
*
|
|
35
|
+
* Why per-call constants instead of one global "secure" list:
|
|
36
|
+
* The http-client only speaks HTTP, so wss:// is a category error
|
|
37
|
+
* (operator passed a WebSocket URL to a non-WebSocket client). Each
|
|
38
|
+
* caller declares its own narrow allowlist; an off-protocol URL
|
|
39
|
+
* fails with a clear "protocol not allowed here" error rather than
|
|
40
|
+
* trying and failing weirdly later.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
var { FrameworkError } = require("./framework-error");
|
|
44
|
+
var { URL } = require("url");
|
|
45
|
+
|
|
46
|
+
var ALLOW_HTTP_TLS = Object.freeze(["https:"]);
|
|
47
|
+
var ALLOW_HTTP_ALL = Object.freeze(["http:", "https:"]);
|
|
48
|
+
var ALLOW_WS_TLS = Object.freeze(["wss:"]);
|
|
49
|
+
var ALLOW_WS_ALL = Object.freeze(["ws:", "wss:"]);
|
|
50
|
+
var ALLOW_ANY = Object.freeze(["http:", "https:", "ws:", "wss:"]);
|
|
51
|
+
|
|
52
|
+
class SafeUrlError extends FrameworkError {
|
|
53
|
+
constructor(code, message) {
|
|
54
|
+
super(message, code);
|
|
55
|
+
this.name = "SafeUrlError";
|
|
56
|
+
this.isSafeUrlError = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function _makeError(errorClass, code, message) {
|
|
61
|
+
if (!errorClass || errorClass === SafeUrlError) {
|
|
62
|
+
return new SafeUrlError(code, message);
|
|
63
|
+
}
|
|
64
|
+
// Convention for operational classes (ObjectStoreError,
|
|
65
|
+
// LogStreamError, etc.): (code, message, permanent[, statusCode]).
|
|
66
|
+
// A protocol-violation URL is "permanent" — retry won't help.
|
|
67
|
+
return new errorClass(code, message, true);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parse(url, opts) {
|
|
71
|
+
opts = opts || {};
|
|
72
|
+
var allowed = Array.isArray(opts.allowedProtocols) && opts.allowedProtocols.length > 0
|
|
73
|
+
? opts.allowedProtocols
|
|
74
|
+
: ALLOW_HTTP_TLS;
|
|
75
|
+
var errClass = opts.errorClass;
|
|
76
|
+
|
|
77
|
+
if (url == null || url === "") {
|
|
78
|
+
throw _makeError(errClass, "safe-url/missing", "url is required");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var parsed;
|
|
82
|
+
if (url instanceof URL) {
|
|
83
|
+
parsed = url;
|
|
84
|
+
} else {
|
|
85
|
+
try {
|
|
86
|
+
parsed = new URL(String(url));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
throw _makeError(errClass, "safe-url/malformed", "malformed URL: " + e.message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (allowed.indexOf(parsed.protocol) === -1) {
|
|
93
|
+
throw _makeError(errClass, "safe-url/protocol-disallowed",
|
|
94
|
+
"protocol '" + parsed.protocol + "' not in allowlist [" + allowed.join(", ") +
|
|
95
|
+
"]. Pass opts.allowedProtocols to override (e.g. safeUrl.ALLOW_HTTP_ALL for cleartext endpoints).");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
parse: parse,
|
|
103
|
+
SafeUrlError: SafeUrlError,
|
|
104
|
+
ALLOW_HTTP_TLS: ALLOW_HTTP_TLS,
|
|
105
|
+
ALLOW_HTTP_ALL: ALLOW_HTTP_ALL,
|
|
106
|
+
ALLOW_WS_TLS: ALLOW_WS_TLS,
|
|
107
|
+
ALLOW_WS_ALL: ALLOW_WS_ALL,
|
|
108
|
+
ALLOW_ANY: ALLOW_ANY,
|
|
109
|
+
};
|