@blamejs/core 0.9.14 → 0.9.15
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 +1 -0
- package/lib/a2a.js +11 -11
- package/lib/acme.js +5 -5
- package/lib/ai-input.js +2 -2
- package/lib/api-key.js +4 -4
- package/lib/api-snapshot.js +6 -6
- package/lib/app-shutdown.js +2 -2
- package/lib/app.js +5 -5
- package/lib/archive.js +8 -8
- package/lib/argon2-builtin.js +2 -2
- package/lib/atomic-file.js +53 -53
- package/lib/audit-sign.js +8 -8
- package/lib/audit-tools.js +22 -22
- package/lib/auth/dpop.js +3 -3
- package/lib/auth/sd-jwt-vc.js +2 -2
- package/lib/backup/bundle.js +17 -17
- package/lib/backup/index.js +36 -36
- package/lib/budr.js +3 -3
- package/lib/bundler.js +20 -20
- package/lib/circuit-breaker.js +4 -4
- package/lib/cli.js +25 -26
- package/lib/cluster.js +2 -2
- package/lib/compliance-sanctions.js +2 -2
- package/lib/config-drift.js +15 -15
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +3 -3
- package/lib/daemon.js +19 -19
- package/lib/db-file-lifecycle.js +24 -24
- package/lib/db-schema.js +2 -2
- package/lib/db.js +35 -35
- package/lib/dev.js +10 -10
- package/lib/dr-runbook.js +5 -5
- package/lib/dual-control.js +2 -2
- package/lib/external-db-migrate.js +2 -2
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-providers.js +4 -4
- package/lib/gate-contract.js +5 -5
- package/lib/graphql-federation.js +4 -7
- package/lib/honeytoken.js +6 -6
- package/lib/http-client-cookie-jar.js +6 -6
- package/lib/http-client.js +18 -18
- package/lib/i18n.js +5 -5
- package/lib/keychain.js +9 -9
- package/lib/legal-hold.js +2 -2
- package/lib/local-db-thin.js +9 -9
- package/lib/log-stream-local.js +17 -17
- package/lib/log-stream-syslog.js +2 -2
- package/lib/log-stream.js +3 -3
- package/lib/mail-bounce.js +2 -2
- package/lib/mail-mdn.js +2 -2
- package/lib/mail-srs.js +2 -2
- package/lib/mail.js +4 -4
- package/lib/mcp.js +2 -2
- package/lib/metrics.js +2 -2
- package/lib/middleware/api-encrypt.js +16 -16
- package/lib/middleware/body-parser.js +16 -16
- package/lib/middleware/compression.js +3 -3
- package/lib/middleware/csp-nonce.js +4 -4
- package/lib/middleware/health.js +7 -7
- package/lib/middleware/idempotency-key.js +163 -63
- package/lib/migrations.js +3 -3
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-dns.js +2 -2
- package/lib/network-nts.js +2 -2
- package/lib/network-proxy.js +3 -3
- package/lib/network-smtp-policy.js +2 -2
- package/lib/network-tls.js +17 -17
- package/lib/network.js +13 -13
- package/lib/notify.js +3 -3
- package/lib/object-store/gcs-bucket-ops.js +2 -2
- package/lib/object-store/gcs.js +5 -5
- package/lib/object-store/index.js +6 -6
- package/lib/object-store/local.js +19 -19
- package/lib/object-store/sigv4.js +3 -3
- package/lib/observability-tracer.js +4 -4
- package/lib/otel-export.js +3 -3
- package/lib/pagination.js +5 -5
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/pqc-gate.js +5 -5
- package/lib/pubsub-redis.js +2 -2
- package/lib/queue-local.js +3 -3
- package/lib/queue.js +2 -2
- package/lib/redis-client.js +4 -4
- package/lib/restore-bundle.js +18 -18
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/router.js +13 -13
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +4 -4
- package/lib/self-update.js +18 -18
- package/lib/session-device-binding.js +2 -2
- package/lib/static.js +22 -22
- package/lib/template.js +19 -19
- package/lib/testing.js +7 -7
- package/lib/tls-exporter.js +5 -5
- package/lib/tracing.js +3 -3
- package/lib/vault/index.js +11 -11
- package/lib/vault/passphrase-ops.js +37 -37
- package/lib/vault/passphrase-source.js +2 -2
- package/lib/vault/rotate.js +64 -64
- package/lib/vault/seal-pem-file.js +26 -26
- package/lib/watcher.js +23 -23
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +4 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Implements the uniform protocol surface (put / get / getStream / delete /
|
|
6
6
|
* head / list) against a directory tree. Streaming is via Node's native
|
|
7
|
-
*
|
|
7
|
+
* nodeFs.createReadStream / createWriteStream — no in-memory buffering of
|
|
8
8
|
* full files.
|
|
9
9
|
*
|
|
10
10
|
* Path safety: every key resolves under the configured rootDir, with an
|
|
11
11
|
* alphanumeric + `_-./` charset whitelist and explicit rejection of any
|
|
12
12
|
* path that escapes rootDir after resolution.
|
|
13
13
|
*/
|
|
14
|
-
var
|
|
15
|
-
var
|
|
14
|
+
var nodeFs = require("fs");
|
|
15
|
+
var nodePath = require("path");
|
|
16
16
|
var atomicFile = require("../atomic-file");
|
|
17
17
|
var cluster = require("../cluster");
|
|
18
18
|
var { ObjectStoreError } = require("../framework-error");
|
|
@@ -24,10 +24,10 @@ function _resolveSafe(rootDir, key) {
|
|
|
24
24
|
throw _err("INVALID_KEY", "key must be a non-empty string", true);
|
|
25
25
|
}
|
|
26
26
|
if (key.includes("\0")) throw _err("INVALID_KEY", "null byte in key", true);
|
|
27
|
-
if (
|
|
27
|
+
if (nodePath.isAbsolute(key)) throw _err("INVALID_KEY", "absolute key not allowed", true);
|
|
28
28
|
if (!SAFE_KEY.test(key)) throw _err("INVALID_KEY", "invalid characters in key", true);
|
|
29
|
-
var full =
|
|
30
|
-
var withSep = rootDir.endsWith(
|
|
29
|
+
var full = nodePath.resolve(rootDir, key);
|
|
30
|
+
var withSep = rootDir.endsWith(nodePath.sep) ? rootDir : rootDir + nodePath.sep;
|
|
31
31
|
if (full !== rootDir && !full.startsWith(withSep)) {
|
|
32
32
|
throw _err("INVALID_KEY", "key escapes rootDir", true);
|
|
33
33
|
}
|
|
@@ -40,14 +40,14 @@ function create(config) {
|
|
|
40
40
|
if (!config || !config.rootDir) {
|
|
41
41
|
throw new Error("local protocol requires { rootDir }");
|
|
42
42
|
}
|
|
43
|
-
var rootDir =
|
|
44
|
-
if (!
|
|
43
|
+
var rootDir = nodePath.resolve(config.rootDir);
|
|
44
|
+
if (!nodeFs.existsSync(rootDir)) nodeFs.mkdirSync(rootDir, { recursive: true });
|
|
45
45
|
|
|
46
46
|
function put(key, body, _opts) {
|
|
47
47
|
cluster.requireLeader();
|
|
48
48
|
var full = _resolveSafe(rootDir, key);
|
|
49
|
-
var dir =
|
|
50
|
-
if (!
|
|
49
|
+
var dir = nodePath.dirname(full);
|
|
50
|
+
if (!nodeFs.existsSync(dir)) nodeFs.mkdirSync(dir, { recursive: true });
|
|
51
51
|
|
|
52
52
|
if (Buffer.isBuffer(body)) {
|
|
53
53
|
atomicFile.writeSync(full, body);
|
|
@@ -56,7 +56,7 @@ function create(config) {
|
|
|
56
56
|
if (body && typeof body.pipe === "function") {
|
|
57
57
|
// Streaming put — pipe directly to disk
|
|
58
58
|
return new Promise(function (resolve, reject) {
|
|
59
|
-
var ws =
|
|
59
|
+
var ws = nodeFs.createWriteStream(full);
|
|
60
60
|
var bytes = 0;
|
|
61
61
|
body.on("data", function (chunk) { bytes += chunk.length; });
|
|
62
62
|
body.pipe(ws);
|
|
@@ -75,26 +75,26 @@ function create(config) {
|
|
|
75
75
|
|
|
76
76
|
function get(key) {
|
|
77
77
|
var full = _resolveSafe(rootDir, key);
|
|
78
|
-
if (!
|
|
78
|
+
if (!nodeFs.existsSync(full)) {
|
|
79
79
|
return Promise.reject(_err("NOT_FOUND", "key not found: " + key, true));
|
|
80
80
|
}
|
|
81
|
-
return Promise.resolve(
|
|
81
|
+
return Promise.resolve(nodeFs.readFileSync(full));
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function getStream(key) {
|
|
85
85
|
var full = _resolveSafe(rootDir, key);
|
|
86
|
-
if (!
|
|
86
|
+
if (!nodeFs.existsSync(full)) {
|
|
87
87
|
throw _err("NOT_FOUND", "key not found: " + key, true);
|
|
88
88
|
}
|
|
89
|
-
return
|
|
89
|
+
return nodeFs.createReadStream(full);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function head(key) {
|
|
93
93
|
var full = _resolveSafe(rootDir, key);
|
|
94
|
-
if (!
|
|
94
|
+
if (!nodeFs.existsSync(full)) {
|
|
95
95
|
return Promise.reject(_err("NOT_FOUND", "key not found: " + key, true));
|
|
96
96
|
}
|
|
97
|
-
var stat =
|
|
97
|
+
var stat = nodeFs.statSync(full);
|
|
98
98
|
return Promise.resolve({
|
|
99
99
|
size: stat.size,
|
|
100
100
|
lastModified: stat.mtimeMs,
|
|
@@ -104,8 +104,8 @@ function create(config) {
|
|
|
104
104
|
function deleteKey(key) {
|
|
105
105
|
cluster.requireLeader();
|
|
106
106
|
var full = _resolveSafe(rootDir, key);
|
|
107
|
-
if (!
|
|
108
|
-
|
|
107
|
+
if (!nodeFs.existsSync(full)) return Promise.resolve(false);
|
|
108
|
+
nodeFs.unlinkSync(full);
|
|
109
109
|
return Promise.resolve(true);
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -29,7 +29,7 @@ var { Readable } = require("stream");
|
|
|
29
29
|
var safeXml = require("../parsers/safe-xml");
|
|
30
30
|
var sharedRequest = require("./http-request");
|
|
31
31
|
var C = require("../constants");
|
|
32
|
-
var
|
|
32
|
+
var numericBounds = require("../numeric-bounds");
|
|
33
33
|
var requestHelpers = require("../request-helpers");
|
|
34
34
|
var { ObjectStoreError } = require("../framework-error");
|
|
35
35
|
var safeUrl = require("../safe-url");
|
|
@@ -821,10 +821,10 @@ function create(config) {
|
|
|
821
821
|
"POST-form policy enforces body size via the content-length-range condition; " +
|
|
822
822
|
"use presignedUploadUrl if size enforcement is not needed", true);
|
|
823
823
|
}
|
|
824
|
-
if (opts.minBytes !== undefined && !
|
|
824
|
+
if (opts.minBytes !== undefined && !numericBounds.isNonNegativeFiniteInt(opts.minBytes)) {
|
|
825
825
|
throw _err("INVALID_MIN_BYTES",
|
|
826
826
|
"presignedUploadPolicy: minBytes must be a non-negative finite integer; got " +
|
|
827
|
-
|
|
827
|
+
numericBounds.shape(opts.minBytes), true);
|
|
828
828
|
}
|
|
829
829
|
var minBytes = opts.minBytes !== undefined ? opts.minBytes : 0;
|
|
830
830
|
var expiresIn = opts.expiresIn != null ? opts.expiresIn : PRESIGN_DEFAULT_EXPIRES_SECONDS;
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
*/
|
|
74
74
|
|
|
75
75
|
var bCrypto = require("./crypto");
|
|
76
|
-
var
|
|
76
|
+
var C = require("./constants");
|
|
77
77
|
var lazyRequire = require("./lazy-require");
|
|
78
78
|
var safeBuffer = require("./safe-buffer");
|
|
79
79
|
var validateOpts = require("./validate-opts");
|
|
80
80
|
var { defineClass } = require("./framework-error");
|
|
81
81
|
|
|
82
|
-
var TRACE_ID_BYTES =
|
|
83
|
-
var SPAN_ID_BYTES =
|
|
82
|
+
var TRACE_ID_BYTES = C.BYTES.bytes(16); // W3C §3.2.2.3 — 128-bit trace-id
|
|
83
|
+
var SPAN_ID_BYTES = C.BYTES.bytes(8); // W3C §3.2.2.4 — 64-bit span-id
|
|
84
84
|
|
|
85
85
|
var TracerError = defineClass("TracerError", { alwaysPermanent: true });
|
|
86
86
|
|
|
@@ -168,7 +168,7 @@ function create(opts) {
|
|
|
168
168
|
var resource = Object.assign({
|
|
169
169
|
"service.name": opts.service,
|
|
170
170
|
}, opts.resource || {});
|
|
171
|
-
var scope = opts.scope || { name: "blamejs", version:
|
|
171
|
+
var scope = opts.scope || { name: "blamejs", version: C.version || null };
|
|
172
172
|
var maxAttributes = opts.maxAttributes || DEFAULT_MAX_ATTRIBUTES;
|
|
173
173
|
var maxEvents = opts.maxEvents || DEFAULT_MAX_EVENTS;
|
|
174
174
|
var maxAttrValLen = opts.maxAttributeValueLength || DEFAULT_MAX_ATTR_VALUE_LEN;
|
package/lib/otel-export.js
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
*/
|
|
42
42
|
var C = require("./constants");
|
|
43
43
|
var canonicalJson = require("./canonical-json");
|
|
44
|
-
var
|
|
44
|
+
var httpClient = require("./http-client");
|
|
45
45
|
var safeAsync = require("./safe-async");
|
|
46
46
|
var validateOpts = require("./validate-opts");
|
|
47
47
|
var { defineClass } = require("./framework-error");
|
|
@@ -110,7 +110,7 @@ function create(opts) {
|
|
|
110
110
|
throw new OtelExportError("otel-export/bad-interval",
|
|
111
111
|
"create: intervalMs must be a non-negative finite number");
|
|
112
112
|
}
|
|
113
|
-
var
|
|
113
|
+
var effectiveHttpClient = opts.httpClient || httpClient;
|
|
114
114
|
var scopeName = (opts.scope && opts.scope.name) || "blamejs";
|
|
115
115
|
var scopeVersion = (opts.scope && opts.scope.version) || "0.5.x";
|
|
116
116
|
var resourceAttrs = Object.assign({ "service.name": serviceName },
|
|
@@ -220,7 +220,7 @@ function create(opts) {
|
|
|
220
220
|
if (!payload) return { sent: false, reason: "no-data" };
|
|
221
221
|
var body = JSON.stringify(payload);
|
|
222
222
|
try {
|
|
223
|
-
var res = await
|
|
223
|
+
var res = await effectiveHttpClient.request({
|
|
224
224
|
method: "POST",
|
|
225
225
|
url: endpoint,
|
|
226
226
|
headers: Object.assign({ "Content-Type": "application/json" }, headers),
|
package/lib/pagination.js
CHANGED
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
var nodeCrypto = require("node:crypto");
|
|
50
50
|
var C = require("./constants");
|
|
51
51
|
var canonicalJson = require("./canonical-json");
|
|
52
|
-
var
|
|
53
|
-
var
|
|
52
|
+
var bCrypto = require("./crypto");
|
|
53
|
+
var numericBounds = require("./numeric-bounds");
|
|
54
54
|
var safeJson = require("./safe-json");
|
|
55
55
|
var safeSql = require("./safe-sql");
|
|
56
56
|
var { defineClass } = require("./framework-error");
|
|
@@ -185,7 +185,7 @@ function decodeCursor(token, secret) {
|
|
|
185
185
|
throw new PaginationError("pagination/bad-cursor", "cursor base64 decode failed");
|
|
186
186
|
}
|
|
187
187
|
var expected = _tag(sb, json);
|
|
188
|
-
if (!
|
|
188
|
+
if (!bCrypto.timingSafeEqual(tag, expected)) {
|
|
189
189
|
throw new PaginationError("pagination/cursor-tag-mismatch",
|
|
190
190
|
"cursor HMAC verification failed (tampered or wrong secret)");
|
|
191
191
|
}
|
|
@@ -205,9 +205,9 @@ function decodeCursor(token, secret) {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
function _resolveLimit(opts) {
|
|
208
|
-
|
|
208
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.max,
|
|
209
209
|
"max", PaginationError, "pagination/bad-opt");
|
|
210
|
-
|
|
210
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.default,
|
|
211
211
|
"default", PaginationError, "pagination/bad-opt");
|
|
212
212
|
var max = opts.max || DEFAULT_MAX_LIMIT;
|
|
213
213
|
var def = opts.default || DEFAULT_LIMIT;
|
package/lib/parsers/safe-xml.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
var C = require("../constants");
|
|
43
|
-
var
|
|
43
|
+
var numericBounds = require("../numeric-bounds");
|
|
44
44
|
var safeBuffer = require("../safe-buffer");
|
|
45
45
|
var { FrameworkError } = require("../framework-error");
|
|
46
46
|
|
|
@@ -77,10 +77,10 @@ var BUILT_IN_ENTITIES = { lt: "<", gt: ">", amp: "&", quot: "\"", apos: "'" };
|
|
|
77
77
|
|
|
78
78
|
function _validateAndCap(name, value, defaultValue, ceiling) {
|
|
79
79
|
if (value === undefined) return defaultValue;
|
|
80
|
-
if (!
|
|
80
|
+
if (!numericBounds.isPositiveFiniteInt(value)) {
|
|
81
81
|
throw new SafeXmlError("xml/bad-opt",
|
|
82
82
|
"xml.parse: " + name + " must be a positive finite integer; got " +
|
|
83
|
-
|
|
83
|
+
numericBounds.shape(value));
|
|
84
84
|
}
|
|
85
85
|
return Math.min(value, ceiling);
|
|
86
86
|
}
|
package/lib/pqc-gate.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
var net = require("node:net");
|
|
40
40
|
var C = require("./constants");
|
|
41
41
|
var { PQC_GROUPS } = require("./constants");
|
|
42
|
-
var
|
|
42
|
+
var numericBounds = require("./numeric-bounds");
|
|
43
43
|
var validateOpts = require("./validate-opts");
|
|
44
44
|
var { boot } = require("./log");
|
|
45
45
|
|
|
@@ -155,14 +155,14 @@ function create(opts) {
|
|
|
155
155
|
}
|
|
156
156
|
var internalHost = typeof opts.internalHost === "string" ? opts.internalHost : "127.0.0.1";
|
|
157
157
|
var bypass = Array.isArray(opts.bypass) ? opts.bypass.slice() : DEFAULT_BYPASS.slice();
|
|
158
|
-
if (opts.clientHelloTimeoutMs !== undefined && !
|
|
158
|
+
if (opts.clientHelloTimeoutMs !== undefined && !numericBounds.isPositiveFiniteInt(opts.clientHelloTimeoutMs)) {
|
|
159
159
|
throw new Error("pqc-gate: clientHelloTimeoutMs must be a positive finite integer; got " +
|
|
160
|
-
|
|
160
|
+
numericBounds.shape(opts.clientHelloTimeoutMs));
|
|
161
161
|
}
|
|
162
162
|
var clientHelloTimeoutMs = opts.clientHelloTimeoutMs || DEFAULT_CLIENTHELLO_TIMEOUT_MS;
|
|
163
|
-
if (opts.maxClientHelloBytes !== undefined && !
|
|
163
|
+
if (opts.maxClientHelloBytes !== undefined && !numericBounds.isPositiveFiniteInt(opts.maxClientHelloBytes)) {
|
|
164
164
|
throw new Error("pqc-gate: maxClientHelloBytes must be a positive finite integer; got " +
|
|
165
|
-
|
|
165
|
+
numericBounds.shape(opts.maxClientHelloBytes));
|
|
166
166
|
}
|
|
167
167
|
var maxClientHelloBytes = opts.maxClientHelloBytes || DEFAULT_MAX_CLIENTHELLO_BYTES;
|
|
168
168
|
var log = opts.log || null;
|
package/lib/pubsub-redis.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* conventions of its own.
|
|
23
23
|
*/
|
|
24
24
|
var C = require("./constants");
|
|
25
|
-
var
|
|
25
|
+
var bCrypto = require("./crypto");
|
|
26
26
|
var lazyRequire = require("./lazy-require");
|
|
27
27
|
var redisClient = require("./redis-client");
|
|
28
28
|
var safeJson = require("./safe-json");
|
|
@@ -39,7 +39,7 @@ function create(opts) {
|
|
|
39
39
|
// the local dispatch synchronously before awaiting the remote
|
|
40
40
|
// write — without this filter every same-instance publish would
|
|
41
41
|
// double-fire local handlers).
|
|
42
|
-
var instanceNonce =
|
|
42
|
+
var instanceNonce = bCrypto.generateToken(C.BYTES.bytes(8));
|
|
43
43
|
|
|
44
44
|
var clientOpts = redisClient.pickClientOpts(opts, "redis");
|
|
45
45
|
|
package/lib/queue-local.js
CHANGED
|
@@ -35,7 +35,7 @@ var C = require("./constants");
|
|
|
35
35
|
var { generateToken } = require("./crypto");
|
|
36
36
|
var cryptoField = require("./crypto-field");
|
|
37
37
|
var lazyRequire = require("./lazy-require");
|
|
38
|
-
var
|
|
38
|
+
var numericBounds = require("./numeric-bounds");
|
|
39
39
|
var safeJson = require("./safe-json");
|
|
40
40
|
var scheduler = require("./scheduler");
|
|
41
41
|
var { QueueError } = require("./framework-error");
|
|
@@ -388,10 +388,10 @@ function create(_config) {
|
|
|
388
388
|
opts = opts || {};
|
|
389
389
|
var limit = 100;
|
|
390
390
|
if (opts.limit !== undefined) {
|
|
391
|
-
if (!
|
|
391
|
+
if (!numericBounds.isPositiveFiniteInt(opts.limit)) {
|
|
392
392
|
throw new QueueError("queue/bad-opt",
|
|
393
393
|
"queue.dlqList: limit must be a positive finite integer; got " +
|
|
394
|
-
|
|
394
|
+
numericBounds.shape(opts.limit), true);
|
|
395
395
|
}
|
|
396
396
|
limit = opts.limit;
|
|
397
397
|
}
|
package/lib/queue.js
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
*/
|
|
44
44
|
var C = require("./constants");
|
|
45
45
|
var clusterStorage = require("./cluster-storage");
|
|
46
|
-
var
|
|
46
|
+
var bCrypto = require("./crypto");
|
|
47
47
|
var lazyRequire = require("./lazy-require");
|
|
48
48
|
var { boot } = require("./log");
|
|
49
49
|
var numericChecks = require("./numeric-checks");
|
|
@@ -877,7 +877,7 @@ function enqueueFlow(spec) {
|
|
|
877
877
|
return Promise.reject(e);
|
|
878
878
|
}
|
|
879
879
|
|
|
880
|
-
var flowId = "flow-" +
|
|
880
|
+
var flowId = "flow-" + bCrypto.generateToken(C.BYTES.bytes(8));
|
|
881
881
|
|
|
882
882
|
return observability.tap("queue.enqueueFlow",
|
|
883
883
|
{ queueName: spec.queueName, flowId: flowId, childCount: spec.children.length },
|
package/lib/redis-client.js
CHANGED
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* callers can branch on transport vs server-side errors.
|
|
26
26
|
*/
|
|
27
27
|
var net = require("node:net");
|
|
28
|
-
var
|
|
29
|
-
var
|
|
28
|
+
var nodeTls = require("node:tls");
|
|
29
|
+
var nodeUrl = require("node:url");
|
|
30
30
|
var C = require("./constants");
|
|
31
31
|
var safeAsync = require("./safe-async");
|
|
32
32
|
var validateOpts = require("./validate-opts");
|
|
@@ -319,7 +319,7 @@ function create(opts) {
|
|
|
319
319
|
var tlsConnectOpts = { host: host, port: port };
|
|
320
320
|
if (servername) tlsConnectOpts.servername = servername;
|
|
321
321
|
if (caBundle) tlsConnectOpts.ca = caBundle;
|
|
322
|
-
sock =
|
|
322
|
+
sock = nodeTls.connect(tlsConnectOpts, onOk);
|
|
323
323
|
} else {
|
|
324
324
|
sock = net.connect({ host: host, port: port }, onOk);
|
|
325
325
|
}
|
|
@@ -457,7 +457,7 @@ function create(opts) {
|
|
|
457
457
|
// Empty-username + non-empty password is the legacy single-arg AUTH form.
|
|
458
458
|
function _parseRedisUrl(s) {
|
|
459
459
|
var u;
|
|
460
|
-
try { u = new
|
|
460
|
+
try { u = new nodeUrl.URL(s); }
|
|
461
461
|
catch (e) {
|
|
462
462
|
throw _err("BAD_URL", "redis url parse failed: " + ((e && e.message) || String(e)));
|
|
463
463
|
}
|
package/lib/restore-bundle.js
CHANGED
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
* Backup-bundle reader — verify the manifest signature, list bundle contents without decrypting, and cherry-pick a restore subset to a staging directory the caller atomically swaps into place.
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
-
var
|
|
49
|
-
var
|
|
48
|
+
var nodeFs = require("fs");
|
|
49
|
+
var nodePath = require("path");
|
|
50
50
|
var atomicFile = require("./atomic-file");
|
|
51
51
|
var backupCrypto = require("./backup/crypto");
|
|
52
52
|
var backupManifest = require("./backup/manifest");
|
|
@@ -65,7 +65,7 @@ function _cleanupStaging(stagingDir) {
|
|
|
65
65
|
// Best-effort recursive remove — if cleanup fails, surface that to
|
|
66
66
|
// the caller via stderr but never override the original error
|
|
67
67
|
// we're already throwing.
|
|
68
|
-
try {
|
|
68
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); }
|
|
69
69
|
catch (_e) { /* best-effort */ }
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -123,15 +123,15 @@ function _cleanupStaging(stagingDir) {
|
|
|
123
123
|
async function extract(opts) {
|
|
124
124
|
var t0 = Date.now();
|
|
125
125
|
opts = opts || {};
|
|
126
|
-
if (typeof opts.bundleDir !== "string" || !
|
|
126
|
+
if (typeof opts.bundleDir !== "string" || !nodeFs.existsSync(opts.bundleDir)) {
|
|
127
127
|
throw new RestoreBundleError("restore-bundle/no-bundle",
|
|
128
128
|
"extract: opts.bundleDir is required and must exist");
|
|
129
129
|
}
|
|
130
130
|
validateOpts.requireNonEmptyString(opts.stagingDir, "extract: opts.stagingDir", RestoreBundleError, "restore-bundle/no-staging");
|
|
131
|
-
if (
|
|
131
|
+
if (nodeFs.existsSync(opts.stagingDir)) {
|
|
132
132
|
throw new RestoreBundleError("restore-bundle/staging-exists",
|
|
133
133
|
"extract: stagingDir already exists: " + opts.stagingDir +
|
|
134
|
-
" (refusing to merge into existing directory — pick a fresh
|
|
134
|
+
" (refusing to merge into existing directory — pick a fresh nodePath)");
|
|
135
135
|
}
|
|
136
136
|
if (!Buffer.isBuffer(opts.passphrase) && typeof opts.passphrase !== "string") {
|
|
137
137
|
throw new RestoreBundleError("restore-bundle/no-passphrase",
|
|
@@ -145,14 +145,14 @@ async function extract(opts) {
|
|
|
145
145
|
|
|
146
146
|
// 1. Read + parse + validate manifest
|
|
147
147
|
_emit(progress, { phase: "read_manifest" });
|
|
148
|
-
var manifestPath =
|
|
149
|
-
if (!
|
|
148
|
+
var manifestPath = nodePath.join(bundleDir, "manifest.json");
|
|
149
|
+
if (!nodeFs.existsSync(manifestPath)) {
|
|
150
150
|
throw new RestoreBundleError("restore-bundle/missing-manifest",
|
|
151
151
|
"extract: bundleDir has no manifest.json — bundle is incomplete or not a blamejs backup");
|
|
152
152
|
}
|
|
153
153
|
var manifest;
|
|
154
154
|
try {
|
|
155
|
-
manifest = backupManifest.parse(
|
|
155
|
+
manifest = backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8"));
|
|
156
156
|
} catch (e) {
|
|
157
157
|
if (e && e.isBackupManifestError) throw e;
|
|
158
158
|
throw new RestoreBundleError("restore-bundle/bad-manifest",
|
|
@@ -215,13 +215,13 @@ async function extract(opts) {
|
|
|
215
215
|
continue;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
var blobPath =
|
|
219
|
-
if (!
|
|
218
|
+
var blobPath = nodePath.join(bundleDir, entry.encryptedPath);
|
|
219
|
+
if (!nodeFs.existsSync(blobPath)) {
|
|
220
220
|
throw new RestoreBundleError("restore-bundle/missing-blob",
|
|
221
221
|
"extract: manifest references '" + entry.encryptedPath +
|
|
222
222
|
"' but the bundle has no such file");
|
|
223
223
|
}
|
|
224
|
-
var blob =
|
|
224
|
+
var blob = nodeFs.readFileSync(blobPath);
|
|
225
225
|
if (blob.length !== entry.encryptedSize) {
|
|
226
226
|
throw new RestoreBundleError("restore-bundle/size-mismatch",
|
|
227
227
|
"extract: blob '" + entry.encryptedPath + "' has size " + blob.length +
|
|
@@ -257,8 +257,8 @@ async function extract(opts) {
|
|
|
257
257
|
" — bundle is corrupted or manifest tampered");
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
var destPath =
|
|
261
|
-
atomicFile.ensureDir(
|
|
260
|
+
var destPath = nodePath.join(stagingDir, entry.relativePath);
|
|
261
|
+
atomicFile.ensureDir(nodePath.dirname(destPath));
|
|
262
262
|
atomicFile.writeSync(destPath, plaintext, { fileMode: 0o600 });
|
|
263
263
|
|
|
264
264
|
fileCount++;
|
|
@@ -321,16 +321,16 @@ async function extract(opts) {
|
|
|
321
321
|
*/
|
|
322
322
|
function inspect(opts) {
|
|
323
323
|
opts = opts || {};
|
|
324
|
-
if (typeof opts.bundleDir !== "string" || !
|
|
324
|
+
if (typeof opts.bundleDir !== "string" || !nodeFs.existsSync(opts.bundleDir)) {
|
|
325
325
|
throw new RestoreBundleError("restore-bundle/no-bundle",
|
|
326
326
|
"inspect: opts.bundleDir is required and must exist");
|
|
327
327
|
}
|
|
328
|
-
var manifestPath =
|
|
329
|
-
if (!
|
|
328
|
+
var manifestPath = nodePath.join(opts.bundleDir, "manifest.json");
|
|
329
|
+
if (!nodeFs.existsSync(manifestPath)) {
|
|
330
330
|
throw new RestoreBundleError("restore-bundle/missing-manifest",
|
|
331
331
|
"inspect: bundleDir has no manifest.json");
|
|
332
332
|
}
|
|
333
|
-
return backupManifest.parse(
|
|
333
|
+
return backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8"));
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
module.exports = {
|
package/lib/restore-rollback.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @intro
|
|
8
8
|
* Backup-restore safety net — atomic dataDir swap with a versioned
|
|
9
|
-
* rollback
|
|
9
|
+
* rollback nodePath. The primitive `b.restore` calls to put a
|
|
10
10
|
* freshly-decrypted bundle into place: filesystem rename is atomic
|
|
11
11
|
* on POSIX (and on Windows when nothing has the dir open), so the
|
|
12
12
|
* swap either fully completes or the previous `dataDir` is
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
* corrupting state.
|
|
40
40
|
*
|
|
41
41
|
* @card
|
|
42
|
-
* Backup-restore safety net — atomic dataDir swap with a versioned rollback
|
|
42
|
+
* Backup-restore safety net — atomic dataDir swap with a versioned rollback nodePath.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
var
|
|
46
|
-
var
|
|
45
|
+
var nodeFs = require("fs");
|
|
46
|
+
var nodePath = require("path");
|
|
47
47
|
var atomicFile = require("./atomic-file");
|
|
48
48
|
var C = require("./constants");
|
|
49
|
-
var
|
|
49
|
+
var numericBounds = require("./numeric-bounds");
|
|
50
50
|
var safeJson = require("./safe-json");
|
|
51
51
|
var { defineClass } = require("./framework-error");
|
|
52
52
|
|
|
@@ -94,7 +94,7 @@ function _resolveRollbackRoot(opts) {
|
|
|
94
94
|
*/
|
|
95
95
|
function swap(opts) {
|
|
96
96
|
opts = opts || {};
|
|
97
|
-
if (typeof opts.stagingDir !== "string" || !
|
|
97
|
+
if (typeof opts.stagingDir !== "string" || !nodeFs.existsSync(opts.stagingDir)) {
|
|
98
98
|
throw new RestoreRollbackError("restore-rollback/no-staging",
|
|
99
99
|
"swap: opts.stagingDir is required and must exist");
|
|
100
100
|
}
|
|
@@ -106,20 +106,20 @@ function swap(opts) {
|
|
|
106
106
|
atomicFile.ensureDir(rollbackRoot);
|
|
107
107
|
|
|
108
108
|
var swappedAt = atomicFile.pathTimestamp();
|
|
109
|
-
var rollbackPath =
|
|
110
|
-
var markerPath =
|
|
109
|
+
var rollbackPath = nodePath.join(rollbackRoot, swappedAt);
|
|
110
|
+
var markerPath = nodePath.join(rollbackRoot, swappedAt + ".marker.json");
|
|
111
111
|
|
|
112
|
-
if (
|
|
112
|
+
if (nodeFs.existsSync(rollbackPath) || nodeFs.existsSync(markerPath)) {
|
|
113
113
|
throw new RestoreRollbackError("restore-rollback/collision",
|
|
114
114
|
"swap: a rollback at " + rollbackPath + " already exists — refusing to overwrite");
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
var hadDataDir =
|
|
117
|
+
var hadDataDir = nodeFs.existsSync(opts.dataDir);
|
|
118
118
|
|
|
119
|
-
// Step 1: rename current dataDir → rollback
|
|
119
|
+
// Step 1: rename current dataDir → rollback nodePath. Skipped on first
|
|
120
120
|
// restore (no existing dataDir).
|
|
121
121
|
if (hadDataDir) {
|
|
122
|
-
try {
|
|
122
|
+
try { nodeFs.renameSync(opts.dataDir, rollbackPath); }
|
|
123
123
|
catch (e) {
|
|
124
124
|
throw new RestoreRollbackError("restore-rollback/rename-existing-failed",
|
|
125
125
|
"swap: could not move existing dataDir to rollback: " + ((e && e.message) || String(e)));
|
|
@@ -127,11 +127,11 @@ function swap(opts) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Step 2: rename staging → dataDir
|
|
130
|
-
try {
|
|
130
|
+
try { nodeFs.renameSync(opts.stagingDir, opts.dataDir); }
|
|
131
131
|
catch (e) {
|
|
132
132
|
// Step 2 failed — try to undo step 1 so the operator's dataDir is back
|
|
133
133
|
if (hadDataDir) {
|
|
134
|
-
try {
|
|
134
|
+
try { nodeFs.renameSync(rollbackPath, opts.dataDir); }
|
|
135
135
|
catch (_e) { /* dataDir is now in rollbackPath; operator must recover manually */ }
|
|
136
136
|
}
|
|
137
137
|
throw new RestoreRollbackError("restore-rollback/rename-staging-failed",
|
|
@@ -148,7 +148,7 @@ function swap(opts) {
|
|
|
148
148
|
operator: opts.marker || null,
|
|
149
149
|
};
|
|
150
150
|
try {
|
|
151
|
-
|
|
151
|
+
nodeFs.writeFileSync(markerPath, JSON.stringify(marker, null, 2) + "\n", { mode: 0o600 });
|
|
152
152
|
} catch (_e) { /* marker write is best-effort */ }
|
|
153
153
|
|
|
154
154
|
return {
|
|
@@ -193,19 +193,19 @@ async function rollback(opts) {
|
|
|
193
193
|
throw new RestoreRollbackError("restore-rollback/no-datadir",
|
|
194
194
|
"rollback: opts.dataDir is required");
|
|
195
195
|
}
|
|
196
|
-
if (typeof opts.rollbackPath !== "string" || !
|
|
196
|
+
if (typeof opts.rollbackPath !== "string" || !nodeFs.existsSync(opts.rollbackPath)) {
|
|
197
197
|
throw new RestoreRollbackError("restore-rollback/no-rollback",
|
|
198
198
|
"rollback: opts.rollbackPath is required and must exist");
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// Move the current dataDir aside (so the rollback's rename target is empty)
|
|
202
202
|
var discardedAt = null;
|
|
203
|
-
if (
|
|
203
|
+
if (nodeFs.existsSync(opts.dataDir)) {
|
|
204
204
|
var rollbackRoot = _resolveRollbackRoot(opts);
|
|
205
205
|
atomicFile.ensureDir(rollbackRoot);
|
|
206
206
|
discardedAt = atomicFile.pathTimestamp();
|
|
207
|
-
var discardedPath =
|
|
208
|
-
try {
|
|
207
|
+
var discardedPath = nodePath.join(rollbackRoot, "discarded-" + discardedAt);
|
|
208
|
+
try { nodeFs.renameSync(opts.dataDir, discardedPath); }
|
|
209
209
|
catch (e) {
|
|
210
210
|
throw new RestoreRollbackError("restore-rollback/rename-existing-failed",
|
|
211
211
|
"rollback: could not move current dataDir aside: " + ((e && e.message) || String(e)));
|
|
@@ -214,7 +214,7 @@ async function rollback(opts) {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
// Rename the rollback dir back into dataDir's place
|
|
217
|
-
try {
|
|
217
|
+
try { nodeFs.renameSync(opts.rollbackPath, opts.dataDir); }
|
|
218
218
|
catch (e) {
|
|
219
219
|
throw new RestoreRollbackError("restore-rollback/rollback-rename-failed",
|
|
220
220
|
"rollback: could not move rollback into dataDir: " + ((e && e.message) || String(e)) +
|
|
@@ -223,7 +223,7 @@ async function rollback(opts) {
|
|
|
223
223
|
|
|
224
224
|
// Best-effort: clean up the marker file alongside the rollback path
|
|
225
225
|
var markerPath = opts.rollbackPath + ".marker.json";
|
|
226
|
-
try { if (
|
|
226
|
+
try { if (nodeFs.existsSync(markerPath)) nodeFs.unlinkSync(markerPath); }
|
|
227
227
|
catch (_e) { /* marker cleanup is best-effort */ }
|
|
228
228
|
|
|
229
229
|
return {
|
|
@@ -258,22 +258,22 @@ async function rollback(opts) {
|
|
|
258
258
|
function list(opts) {
|
|
259
259
|
opts = opts || {};
|
|
260
260
|
var rollbackRoot = _resolveRollbackRoot(opts);
|
|
261
|
-
if (!
|
|
262
|
-
var entries =
|
|
261
|
+
if (!nodeFs.existsSync(rollbackRoot)) return [];
|
|
262
|
+
var entries = nodeFs.readdirSync(rollbackRoot, { withFileTypes: true });
|
|
263
263
|
var out = [];
|
|
264
264
|
for (var i = 0; i < entries.length; i++) {
|
|
265
265
|
if (!entries[i].isDirectory()) continue;
|
|
266
266
|
var name = entries[i].name;
|
|
267
267
|
if (name.indexOf("discarded-") === 0) continue; // discarded dirs aren't restore points
|
|
268
|
-
var p =
|
|
268
|
+
var p = nodePath.join(rollbackRoot, name);
|
|
269
269
|
var markerPath = p + ".marker.json";
|
|
270
270
|
var marker = null;
|
|
271
|
-
if (
|
|
272
|
-
try { marker = safeJson.parse(
|
|
271
|
+
if (nodeFs.existsSync(markerPath)) {
|
|
272
|
+
try { marker = safeJson.parse(nodeFs.readFileSync(markerPath, "utf8"), { maxBytes: C.BYTES.kib(64) }); }
|
|
273
273
|
catch (_e) { marker = null; }
|
|
274
274
|
}
|
|
275
275
|
var stat;
|
|
276
|
-
try { stat =
|
|
276
|
+
try { stat = nodeFs.statSync(p); } catch (_e) { continue; }
|
|
277
277
|
out.push({
|
|
278
278
|
rollbackPath: p,
|
|
279
279
|
swappedAt: (marker && marker.swappedAt) || stat.mtime.toISOString(),
|
|
@@ -311,18 +311,18 @@ function list(opts) {
|
|
|
311
311
|
*/
|
|
312
312
|
function purge(opts) {
|
|
313
313
|
opts = opts || {};
|
|
314
|
-
|
|
314
|
+
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.keep,
|
|
315
315
|
"restore-rollback.purge: opts.keep", RestoreRollbackError, "restore-rollback/bad-keep");
|
|
316
316
|
var keep = opts.keep !== undefined ? opts.keep : 0;
|
|
317
317
|
var rollbackRoot = _resolveRollbackRoot(opts);
|
|
318
|
-
if (!
|
|
318
|
+
if (!nodeFs.existsSync(rollbackRoot)) return { kept: keep, deleted: [] };
|
|
319
319
|
// Always sweep "discarded-*" dirs — they're never restore points
|
|
320
|
-
var entries =
|
|
320
|
+
var entries = nodeFs.readdirSync(rollbackRoot, { withFileTypes: true });
|
|
321
321
|
var deleted = [];
|
|
322
322
|
for (var i = 0; i < entries.length; i++) {
|
|
323
323
|
if (entries[i].isDirectory() && entries[i].name.indexOf("discarded-") === 0) {
|
|
324
|
-
var p =
|
|
325
|
-
try {
|
|
324
|
+
var p = nodePath.join(rollbackRoot, entries[i].name);
|
|
325
|
+
try { nodeFs.rmSync(p, { recursive: true, force: true }); deleted.push(p); }
|
|
326
326
|
catch (_e) { /* best-effort */ }
|
|
327
327
|
}
|
|
328
328
|
}
|
|
@@ -333,9 +333,9 @@ function purge(opts) {
|
|
|
333
333
|
for (var j = 0; j < toDelete.length; j++) {
|
|
334
334
|
var rbPath = toDelete[j].rollbackPath;
|
|
335
335
|
var mkPath = rbPath + ".marker.json";
|
|
336
|
-
try {
|
|
336
|
+
try { nodeFs.rmSync(rbPath, { recursive: true, force: true }); deleted.push(rbPath); }
|
|
337
337
|
catch (_e) { /* best-effort */ }
|
|
338
|
-
try { if (
|
|
338
|
+
try { if (nodeFs.existsSync(mkPath)) nodeFs.unlinkSync(mkPath); }
|
|
339
339
|
catch (_e) { /* best-effort */ }
|
|
340
340
|
}
|
|
341
341
|
return { kept: keep, deleted: deleted };
|