@blamejs/core 0.9.14 → 0.9.16
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 +2 -0
- package/lib/a2a-tasks.js +2 -2
- 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/elevation-grant.js +3 -3
- package/lib/auth/fido-mds3.js +6 -6
- package/lib/auth/jwt-external.js +2 -2
- 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/compliance.js +6 -7
- package/lib/config-drift.js +15 -15
- package/lib/config.js +6 -6
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +7 -7
- package/lib/crypto-field.js +9 -9
- 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 +34 -34
- 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 +17 -17
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-evaluation-context.js +2 -2
- 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 +5 -5
- 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/log.js +2 -2
- 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 +7 -7
- package/lib/mcp-tool-registry.js +6 -6
- 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 +18 -18
- 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/middleware/require-bound-key.js +4 -4
- package/lib/middleware/require-mtls.js +4 -4
- package/lib/migrations.js +5 -5
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-byte-quota.js +2 -2
- 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 +25 -25
- package/lib/notify.js +11 -11
- 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-env.js +3 -3
- 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 +17 -17
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/router.js +25 -25
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +6 -6
- 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 +9 -9
- 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/vault-aad.js +5 -5
- package/lib/watcher.js +22 -22
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +6 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/backup/bundle.js
CHANGED
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
* compressor (gzip, zstd) downstream of the framework primitive.
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
|
-
var
|
|
50
|
-
var
|
|
49
|
+
var nodeFs = require("fs");
|
|
50
|
+
var nodePath = require("path");
|
|
51
51
|
var atomicFile = require("../atomic-file");
|
|
52
|
-
var
|
|
52
|
+
var bCrypto = require("./crypto");
|
|
53
53
|
var backupManifest = require("./manifest");
|
|
54
54
|
var validateOpts = require("../validate-opts");
|
|
55
55
|
var { defineClass } = require("../framework-error");
|
|
@@ -68,19 +68,19 @@ function _emit(cb, ev) {
|
|
|
68
68
|
function _encryptedPathFor(relativePath) {
|
|
69
69
|
// POSIX-normalize separators in the bundle so manifests written on
|
|
70
70
|
// Windows and Linux look the same on disk.
|
|
71
|
-
var posix = relativePath.split(
|
|
71
|
+
var posix = relativePath.split(nodePath.sep).join("/");
|
|
72
72
|
return "files/" + posix + ".enc";
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
async function create(opts) {
|
|
76
76
|
var t0 = Date.now();
|
|
77
77
|
opts = opts || {};
|
|
78
|
-
if (typeof opts.dataDir !== "string" || !
|
|
78
|
+
if (typeof opts.dataDir !== "string" || !nodeFs.existsSync(opts.dataDir)) {
|
|
79
79
|
throw new BackupBundleError("backup-bundle/no-datadir",
|
|
80
80
|
"create: opts.dataDir is required and must exist");
|
|
81
81
|
}
|
|
82
82
|
validateOpts.requireNonEmptyString(opts.outDir, "create: opts.outDir", BackupBundleError, "backup-bundle/no-outdir");
|
|
83
|
-
if (
|
|
83
|
+
if (nodeFs.existsSync(opts.outDir)) {
|
|
84
84
|
throw new BackupBundleError("backup-bundle/outdir-exists",
|
|
85
85
|
"create: outDir already exists: " + opts.outDir +
|
|
86
86
|
" (refusing to overwrite — pick a fresh path)");
|
|
@@ -104,11 +104,11 @@ async function create(opts) {
|
|
|
104
104
|
var progress = opts.progressCallback;
|
|
105
105
|
|
|
106
106
|
atomicFile.ensureDir(outDir);
|
|
107
|
-
atomicFile.ensureDir(
|
|
107
|
+
atomicFile.ensureDir(nodePath.join(outDir, "files"));
|
|
108
108
|
|
|
109
109
|
// 1. Encrypt the vault key JSON
|
|
110
110
|
_emit(progress, { phase: "wrap_vault_key" });
|
|
111
|
-
var wrappedVk = await
|
|
111
|
+
var wrappedVk = await bCrypto.encryptWithFreshSalt(opts.vaultKeyJson, passphrase);
|
|
112
112
|
|
|
113
113
|
// 2. Walk each include entry, encrypt the bytes, emit a blob
|
|
114
114
|
var fileEntries = [];
|
|
@@ -124,8 +124,8 @@ async function create(opts) {
|
|
|
124
124
|
throw new BackupBundleError("backup-bundle/bad-include",
|
|
125
125
|
"create: files[" + i + "].relativePath must be a relative path (got '" + entry.relativePath + "')");
|
|
126
126
|
}
|
|
127
|
-
var srcPath =
|
|
128
|
-
if (!
|
|
127
|
+
var srcPath = nodePath.join(dataDir, entry.relativePath);
|
|
128
|
+
if (!nodeFs.existsSync(srcPath)) {
|
|
129
129
|
if (entry.required) {
|
|
130
130
|
throw new BackupBundleError("backup-bundle/missing-required",
|
|
131
131
|
"create: required file missing: " + entry.relativePath);
|
|
@@ -133,7 +133,7 @@ async function create(opts) {
|
|
|
133
133
|
_emit(progress, { phase: "skip_missing", relativePath: entry.relativePath });
|
|
134
134
|
continue;
|
|
135
135
|
}
|
|
136
|
-
var stat =
|
|
136
|
+
var stat = nodeFs.statSync(srcPath);
|
|
137
137
|
if (!stat.isFile()) {
|
|
138
138
|
// Directories aren't supported in this slice — the bundler
|
|
139
139
|
// operates on a flat list of files. Operator wanting a recursive
|
|
@@ -143,12 +143,12 @@ async function create(opts) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
_emit(progress, { phase: "read", relativePath: entry.relativePath, size: stat.size });
|
|
146
|
-
var plain =
|
|
147
|
-
var checksum =
|
|
148
|
-
var encResult = await
|
|
146
|
+
var plain = nodeFs.readFileSync(srcPath);
|
|
147
|
+
var checksum = bCrypto.checksum(plain);
|
|
148
|
+
var encResult = await bCrypto.encryptWithFreshSalt(plain, passphrase);
|
|
149
149
|
var encPath = _encryptedPathFor(entry.relativePath);
|
|
150
|
-
var destFull =
|
|
151
|
-
atomicFile.ensureDir(
|
|
150
|
+
var destFull = nodePath.join(outDir, encPath);
|
|
151
|
+
atomicFile.ensureDir(nodePath.dirname(destFull));
|
|
152
152
|
atomicFile.writeSync(destFull, encResult.encrypted, { fileMode: 0o600 });
|
|
153
153
|
|
|
154
154
|
var kind = entry.kind || "raw";
|
|
@@ -217,7 +217,7 @@ async function create(opts) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
-
var manifestPath =
|
|
220
|
+
var manifestPath = nodePath.join(outDir, "manifest.json");
|
|
221
221
|
atomicFile.writeSync(manifestPath, backupManifest.serialize(manifest), { fileMode: 0o600 });
|
|
222
222
|
|
|
223
223
|
var durationMs = Date.now() - t0;
|
package/lib/backup/index.js
CHANGED
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
* PQC-encrypted backup bundles — sealed columns + audit chain + keyring.
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
|
-
var
|
|
51
|
+
var nodeFs = require("fs");
|
|
52
52
|
var os = require("os");
|
|
53
|
-
var
|
|
54
|
-
var
|
|
53
|
+
var nodePath = require("path");
|
|
54
|
+
var bCrypto = require("../crypto");
|
|
55
55
|
var atomicFile = require("../atomic-file");
|
|
56
56
|
var backupBundle = require("./bundle");
|
|
57
57
|
var backupManifest = require("./manifest");
|
|
@@ -93,16 +93,16 @@ function _isValidBundleId(s) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function _generateBundleId() {
|
|
96
|
-
return atomicFile.pathTimestamp() + "-" +
|
|
96
|
+
return atomicFile.pathTimestamp() + "-" + bCrypto.generateToken(4);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
function _dirSize(p) {
|
|
100
100
|
var total = 0;
|
|
101
|
-
var entries =
|
|
101
|
+
var entries = nodeFs.readdirSync(p, { withFileTypes: true });
|
|
102
102
|
for (var i = 0; i < entries.length; i++) {
|
|
103
|
-
var f =
|
|
103
|
+
var f = nodePath.join(p, entries[i].name);
|
|
104
104
|
if (entries[i].isDirectory()) total += _dirSize(f);
|
|
105
|
-
else if (entries[i].isFile()) total +=
|
|
105
|
+
else if (entries[i].isFile()) total += nodeFs.statSync(f).size;
|
|
106
106
|
}
|
|
107
107
|
return total;
|
|
108
108
|
}
|
|
@@ -150,7 +150,7 @@ function localStorage(opts) {
|
|
|
150
150
|
throw new BackupError("backup/bad-bundle-id",
|
|
151
151
|
"bundleId must match the framework's timestamp+suffix format");
|
|
152
152
|
}
|
|
153
|
-
return
|
|
153
|
+
return nodePath.join(root, bundleId);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
return {
|
|
@@ -158,7 +158,7 @@ function localStorage(opts) {
|
|
|
158
158
|
async writeBundle(bundleId, sourceDir) {
|
|
159
159
|
atomicFile.ensureDir(root);
|
|
160
160
|
var dest = _bundlePath(bundleId);
|
|
161
|
-
if (
|
|
161
|
+
if (nodeFs.existsSync(dest)) {
|
|
162
162
|
throw new BackupError("backup/bundle-exists",
|
|
163
163
|
"writeBundle: bundle '" + bundleId + "' already exists in storage");
|
|
164
164
|
}
|
|
@@ -166,26 +166,26 @@ function localStorage(opts) {
|
|
|
166
166
|
},
|
|
167
167
|
async readBundle(bundleId, destDir) {
|
|
168
168
|
var src = _bundlePath(bundleId);
|
|
169
|
-
if (!
|
|
169
|
+
if (!nodeFs.existsSync(src)) {
|
|
170
170
|
throw new BackupError("backup/bundle-not-found",
|
|
171
171
|
"readBundle: '" + bundleId + "' not in storage at " + root);
|
|
172
172
|
}
|
|
173
|
-
if (
|
|
173
|
+
if (nodeFs.existsSync(destDir)) {
|
|
174
174
|
throw new BackupError("backup/dest-exists",
|
|
175
175
|
"readBundle: destDir already exists: " + destDir);
|
|
176
176
|
}
|
|
177
177
|
atomicFile.copyDirRecursive(src, destDir);
|
|
178
178
|
},
|
|
179
179
|
async listBundles() {
|
|
180
|
-
if (!
|
|
181
|
-
var entries =
|
|
180
|
+
if (!nodeFs.existsSync(root)) return [];
|
|
181
|
+
var entries = nodeFs.readdirSync(root, { withFileTypes: true });
|
|
182
182
|
var out = [];
|
|
183
183
|
for (var i = 0; i < entries.length; i++) {
|
|
184
184
|
if (!entries[i].isDirectory()) continue;
|
|
185
185
|
if (!_isValidBundleId(entries[i].name)) continue;
|
|
186
|
-
var p =
|
|
186
|
+
var p = nodePath.join(root, entries[i].name);
|
|
187
187
|
var stat;
|
|
188
|
-
try { stat =
|
|
188
|
+
try { stat = nodeFs.statSync(p); } catch (_e) { continue; }
|
|
189
189
|
var size;
|
|
190
190
|
try { size = _dirSize(p); } catch (_e) { size = 0; }
|
|
191
191
|
out.push({
|
|
@@ -200,11 +200,11 @@ function localStorage(opts) {
|
|
|
200
200
|
},
|
|
201
201
|
async deleteBundle(bundleId) {
|
|
202
202
|
var p = _bundlePath(bundleId);
|
|
203
|
-
if (!
|
|
204
|
-
|
|
203
|
+
if (!nodeFs.existsSync(p)) return;
|
|
204
|
+
nodeFs.rmSync(p, { recursive: true, force: true });
|
|
205
205
|
},
|
|
206
206
|
async hasBundle(bundleId) {
|
|
207
|
-
try { return
|
|
207
|
+
try { return nodeFs.existsSync(_bundlePath(bundleId)); }
|
|
208
208
|
catch (_e) { return false; }
|
|
209
209
|
},
|
|
210
210
|
};
|
|
@@ -285,10 +285,10 @@ async function _resolveVaultKeyJson(vaultKeyJsonOpt) {
|
|
|
285
285
|
* var path = require("node:path");
|
|
286
286
|
* var os = require("node:os");
|
|
287
287
|
*
|
|
288
|
-
* var dataDir =
|
|
289
|
-
* var root =
|
|
290
|
-
*
|
|
291
|
-
*
|
|
288
|
+
* var dataDir = nodeFs.mkdtempSync(nodePath.join(os.tmpdir(), "backup-data-"));
|
|
289
|
+
* var root = nodeFs.mkdtempSync(nodePath.join(os.tmpdir(), "backup-root-"));
|
|
290
|
+
* nodeFs.writeFileSync(nodePath.join(dataDir, "db.enc"), Buffer.from([1, 2, 3]));
|
|
291
|
+
* nodeFs.writeFileSync(nodePath.join(dataDir, "db.key.enc"), Buffer.from([4, 5, 6]));
|
|
292
292
|
*
|
|
293
293
|
* var engine = b.backup.create({
|
|
294
294
|
* dataDir: dataDir,
|
|
@@ -308,7 +308,7 @@ async function _resolveVaultKeyJson(vaultKeyJsonOpt) {
|
|
|
308
308
|
*/
|
|
309
309
|
function create(opts) {
|
|
310
310
|
opts = opts || {};
|
|
311
|
-
if (typeof opts.dataDir !== "string" || !
|
|
311
|
+
if (typeof opts.dataDir !== "string" || !nodeFs.existsSync(opts.dataDir)) {
|
|
312
312
|
throw new BackupError("backup/no-datadir",
|
|
313
313
|
"create: opts.dataDir is required and must exist");
|
|
314
314
|
}
|
|
@@ -456,7 +456,7 @@ function create(opts) {
|
|
|
456
456
|
runOpts = runOpts || {};
|
|
457
457
|
var t0 = Date.now();
|
|
458
458
|
var bundleId = _generateBundleId();
|
|
459
|
-
var stagingDir =
|
|
459
|
+
var stagingDir = nodePath.join(os.tmpdir(),
|
|
460
460
|
"blamejs-backup-staging-" + bundleId.replace(/[:.]/g, "-"));
|
|
461
461
|
|
|
462
462
|
// Flush the live DB to disk so the snapshot is current. Default
|
|
@@ -500,7 +500,7 @@ function create(opts) {
|
|
|
500
500
|
progressCallback: runOpts.progressCallback,
|
|
501
501
|
});
|
|
502
502
|
} catch (e) {
|
|
503
|
-
try {
|
|
503
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
504
504
|
_emitAudit("backup.failure",
|
|
505
505
|
{ bundleId: bundleId, reason: (e && e.message) || String(e) }, "failure");
|
|
506
506
|
throw e;
|
|
@@ -509,7 +509,7 @@ function create(opts) {
|
|
|
509
509
|
try {
|
|
510
510
|
await storage.writeBundle(bundleId, stagingDir);
|
|
511
511
|
} catch (e) {
|
|
512
|
-
try {
|
|
512
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort tmpdir cleanup */ }
|
|
513
513
|
_emitAudit("backup.failure",
|
|
514
514
|
{ bundleId: bundleId, reason: "storage.writeBundle: " + ((e && e.message) || String(e)) },
|
|
515
515
|
"failure");
|
|
@@ -517,7 +517,7 @@ function create(opts) {
|
|
|
517
517
|
"writing bundle to storage failed: " + ((e && e.message) || String(e)));
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
try {
|
|
520
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort */ }
|
|
521
521
|
|
|
522
522
|
var summary = {
|
|
523
523
|
bundleId: bundleId,
|
|
@@ -675,11 +675,11 @@ function create(opts) {
|
|
|
675
675
|
}
|
|
676
676
|
// Newest bundle (storage.listBundles returns newest first).
|
|
677
677
|
var bundleId = bundles[0].bundleId;
|
|
678
|
-
var stagingDir =
|
|
678
|
+
var stagingDir = nodePath.join(testOpts.restoreTo,
|
|
679
679
|
"test-" + bundleId.replace(/[:.]/g, "-"));
|
|
680
680
|
// Refuse to overwrite an existing dir — operators get a fresh
|
|
681
681
|
// restore every drill.
|
|
682
|
-
if (
|
|
682
|
+
if (nodeFs.existsSync(stagingDir)) {
|
|
683
683
|
_emitAudit("backup.test.failed",
|
|
684
684
|
{ bundleId: bundleId, reason: "stagingDir already exists: " + stagingDir },
|
|
685
685
|
"failure");
|
|
@@ -688,12 +688,12 @@ function create(opts) {
|
|
|
688
688
|
var manifestPath, manifest, sigVerification;
|
|
689
689
|
try {
|
|
690
690
|
await storage.readBundle(bundleId, stagingDir);
|
|
691
|
-
manifestPath =
|
|
692
|
-
if (!
|
|
691
|
+
manifestPath = nodePath.join(stagingDir, "manifest.json");
|
|
692
|
+
if (!nodeFs.existsSync(manifestPath)) {
|
|
693
693
|
throw new BackupError("backup/test-no-manifest",
|
|
694
694
|
"manifest.json missing under restored bundle " + bundleId);
|
|
695
695
|
}
|
|
696
|
-
manifest = backupManifest.parse(
|
|
696
|
+
manifest = backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8"));
|
|
697
697
|
// Verify the manifest signature so a tampered backup test
|
|
698
698
|
// surfaces here, not as a regulator finding later.
|
|
699
699
|
sigVerification = backupManifest.verifySignature(manifest, {
|
|
@@ -740,7 +740,7 @@ function create(opts) {
|
|
|
740
740
|
// Best-effort cleanup so the staging dir doesn't accumulate
|
|
741
741
|
// across drills.
|
|
742
742
|
if (testOpts.cleanup !== false) {
|
|
743
|
-
try {
|
|
743
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); }
|
|
744
744
|
catch (_e) { /* tmpdir cleanup best-effort */ }
|
|
745
745
|
}
|
|
746
746
|
}
|
|
@@ -804,12 +804,12 @@ function verifyManifestSignature(target, opts) {
|
|
|
804
804
|
opts = opts || {};
|
|
805
805
|
var manifest;
|
|
806
806
|
if (typeof target === "string") {
|
|
807
|
-
var manifestPath =
|
|
808
|
-
if (!
|
|
807
|
+
var manifestPath = nodePath.join(target, "manifest.json");
|
|
808
|
+
if (!nodeFs.existsSync(manifestPath)) {
|
|
809
809
|
throw new BackupError("backup/no-manifest",
|
|
810
810
|
"verifyManifestSignature: manifest.json missing at " + manifestPath);
|
|
811
811
|
}
|
|
812
|
-
try { manifest = backupManifest.parse(
|
|
812
|
+
try { manifest = backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8")); }
|
|
813
813
|
catch (e) {
|
|
814
814
|
throw new BackupError("backup/bad-manifest",
|
|
815
815
|
"verifyManifestSignature: parse failed: " + ((e && e.message) || String(e)));
|
package/lib/budr.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Backup / Disaster-Recovery RTO/RPO declaration primitive for regulated workloads (HIPAA / DORA / ISO 22301:2019 / NIST SP 800-34).
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
var
|
|
27
|
+
var numericBounds = require("./numeric-bounds");
|
|
28
28
|
var validateOpts = require("./validate-opts");
|
|
29
29
|
var audit = require("./audit");
|
|
30
30
|
var { defineClass } = require("./framework-error");
|
|
@@ -85,8 +85,8 @@ function declare(opts) {
|
|
|
85
85
|
throw BudrError.factory("BAD_SERVICE",
|
|
86
86
|
"budr.declare: service must match " + SERVICE_RE);
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.rtoMs, "budr.declare: rtoMs", BudrError, "BAD_RTO");
|
|
89
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.rpoMs, "budr.declare: rpoMs", BudrError, "BAD_RPO");
|
|
90
90
|
if (typeof opts.rtoMs !== "number" || typeof opts.rpoMs !== "number") {
|
|
91
91
|
throw BudrError.factory("BAD_TARGETS",
|
|
92
92
|
"budr.declare: rtoMs and rpoMs are required positive integer milliseconds");
|
package/lib/bundler.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* override via `opts.hashLen` between 4 and 64). Source maps written
|
|
27
27
|
* by an engine land as `<hashed>.<ext>.map` siblings.
|
|
28
28
|
*
|
|
29
|
-
* Watch mode: `bundler.watch(callback)` arms `
|
|
29
|
+
* Watch mode: `bundler.watch(callback)` arms `nodeFs.watch` on each
|
|
30
30
|
* entry's directory, debounces bursts via `opts.graceMs` (default
|
|
31
31
|
* 100 ms), and rebuilds the entire entry set on change.
|
|
32
32
|
*
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
* Client-side asset bundler — produces content-hashed `dist/<name>.<hash>.<ext>` files plus a `manifest.json` mapping logical name to hashed filename.
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
|
-
var
|
|
47
|
-
var
|
|
48
|
-
var
|
|
46
|
+
var nodePath = require("path");
|
|
47
|
+
var nodeFs = require("fs");
|
|
48
|
+
var bCrypto = require("./crypto");
|
|
49
49
|
var atomicFile = require("./atomic-file");
|
|
50
50
|
var logModule = require("./log");
|
|
51
|
-
var
|
|
51
|
+
var numericBounds = require("./numeric-bounds");
|
|
52
52
|
var safeJson = require("./safe-json");
|
|
53
53
|
var validateOpts = require("./validate-opts");
|
|
54
54
|
var { defineClass } = require("./framework-error");
|
|
@@ -67,7 +67,7 @@ var DEFAULT_GRACE_MS = 100;
|
|
|
67
67
|
function _hashContent(buf, hexLen) {
|
|
68
68
|
// SHA3-512 → take the first hexLen hex chars. Same family as the
|
|
69
69
|
// framework's other content fingerprints (no SHA-256 for new code).
|
|
70
|
-
return
|
|
70
|
+
return bCrypto.sha3Hash(buf).slice(0, hexLen);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function _hashedName(baseName, hash, ext) {
|
|
@@ -200,7 +200,7 @@ function _validateEngine(eng) {
|
|
|
200
200
|
* Build a content-hashed asset pipeline for a fixed set of named
|
|
201
201
|
* entries. The returned object exposes `build()` (one-shot rebuild,
|
|
202
202
|
* resolves to `{ outputs, manifestPath, manifest, durationMs }`),
|
|
203
|
-
* `watch(callback)` (arm `
|
|
203
|
+
* `watch(callback)` (arm `nodeFs.watch` and debounce-rebuild on change),
|
|
204
204
|
* and `close()` (drop watchers and pending timers).
|
|
205
205
|
*
|
|
206
206
|
* Throws `BundlerError` at config time on missing / malformed entries,
|
|
@@ -257,7 +257,7 @@ function create(opts) {
|
|
|
257
257
|
|
|
258
258
|
var entries = Object.assign({}, opts.entries);
|
|
259
259
|
var cwd = opts.cwd || process.cwd();
|
|
260
|
-
var outdir =
|
|
260
|
+
var outdir = nodePath.isAbsolute(opts.outdir) ? opts.outdir : nodePath.resolve(cwd, opts.outdir);
|
|
261
261
|
var manifestName = (opts.manifest === false || opts.manifest === null)
|
|
262
262
|
? null
|
|
263
263
|
: (typeof opts.manifest === "string" && opts.manifest.length > 0
|
|
@@ -266,24 +266,24 @@ function create(opts) {
|
|
|
266
266
|
var hashOn = opts.hash !== false;
|
|
267
267
|
var hashLen = DEFAULT_HASH_LEN;
|
|
268
268
|
if (opts.hashLen !== undefined) {
|
|
269
|
-
if (!
|
|
269
|
+
if (!numericBounds.isPositiveFiniteInt(opts.hashLen) ||
|
|
270
270
|
opts.hashLen < MIN_HASH_LEN || opts.hashLen > MAX_HASH_LEN) {
|
|
271
271
|
throw new BundlerError("bundler/bad-hash-len",
|
|
272
272
|
"bundler.create: opts.hashLen must be a positive finite integer " +
|
|
273
273
|
"between " + MIN_HASH_LEN + " and " + MAX_HASH_LEN +
|
|
274
|
-
"; got " +
|
|
274
|
+
"; got " + numericBounds.shape(opts.hashLen));
|
|
275
275
|
}
|
|
276
276
|
hashLen = opts.hashLen;
|
|
277
277
|
}
|
|
278
278
|
var log = opts.log || null;
|
|
279
279
|
|
|
280
|
-
// Test seam: tests pass a fake watcher so we don't actually
|
|
280
|
+
// Test seam: tests pass a fake watcher so we don't actually nodeFs.watch
|
|
281
281
|
var watchFn = opts._watch || function (dirOrFile, wopts, listener) {
|
|
282
|
-
return
|
|
282
|
+
return nodeFs.watch(dirOrFile, wopts, listener);
|
|
283
283
|
};
|
|
284
284
|
var setTimeoutFn = opts._setTimeout || setTimeout;
|
|
285
285
|
var clearTimeoutFn = opts._clearTimeout || clearTimeout;
|
|
286
|
-
|
|
286
|
+
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.graceMs,
|
|
287
287
|
"bundler.create: opts.graceMs", BundlerError, "bundler/bad-grace-ms");
|
|
288
288
|
var graceMs = opts.graceMs !== undefined ? opts.graceMs : DEFAULT_GRACE_MS;
|
|
289
289
|
|
|
@@ -292,7 +292,7 @@ function create(opts) {
|
|
|
292
292
|
var watching = false;
|
|
293
293
|
|
|
294
294
|
function _resolveEntry(p) {
|
|
295
|
-
return
|
|
295
|
+
return nodePath.isAbsolute(p) ? p : nodePath.resolve(cwd, p);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
var _logVia = logModule.makeViaOrFallback(log, bootLog);
|
|
@@ -308,9 +308,9 @@ function create(opts) {
|
|
|
308
308
|
for (var i = 0; i < names.length; i++) {
|
|
309
309
|
var name = names[i];
|
|
310
310
|
var entryPath = _resolveEntry(entries[name]);
|
|
311
|
-
var ext =
|
|
311
|
+
var ext = nodePath.extname(entryPath);
|
|
312
312
|
var raw;
|
|
313
|
-
try { raw =
|
|
313
|
+
try { raw = nodeFs.readFileSync(entryPath); }
|
|
314
314
|
catch (e) {
|
|
315
315
|
throw new BundlerError("bundler/read-failed",
|
|
316
316
|
"could not read entry '" + name + "' at " + entryPath +
|
|
@@ -333,7 +333,7 @@ function create(opts) {
|
|
|
333
333
|
var sourceMap = transformed && transformed.sourceMap;
|
|
334
334
|
var hash = hashOn ? _hashContent(content, hashLen) : null;
|
|
335
335
|
var outName = hashOn ? _hashedName(name, hash, ext) : (name + ext);
|
|
336
|
-
var outPath =
|
|
336
|
+
var outPath = nodePath.join(outdir, outName);
|
|
337
337
|
// atomic-file write so a concurrent reader (the http server
|
|
338
338
|
// serving outdir) never sees a partial file
|
|
339
339
|
atomicFile.writeSync(outPath, content, { mode: 0o644 });
|
|
@@ -360,7 +360,7 @@ function create(opts) {
|
|
|
360
360
|
|
|
361
361
|
var manifestPath = null;
|
|
362
362
|
if (manifestName) {
|
|
363
|
-
manifestPath =
|
|
363
|
+
manifestPath = nodePath.join(outdir, manifestName);
|
|
364
364
|
atomicFile.writeSync(
|
|
365
365
|
manifestPath,
|
|
366
366
|
safeJson.stringify(manifest, null, 2) + "\n",
|
|
@@ -407,8 +407,8 @@ function create(opts) {
|
|
|
407
407
|
// Watch the entry's directory (single-file watches are flaky
|
|
408
408
|
// across editors that write-then-rename). Filter events to the
|
|
409
409
|
// entry's basename only.
|
|
410
|
-
var dir =
|
|
411
|
-
var base =
|
|
410
|
+
var dir = nodePath.dirname(entryPath);
|
|
411
|
+
var base = nodePath.basename(entryPath);
|
|
412
412
|
var w;
|
|
413
413
|
try {
|
|
414
414
|
w = watchFn(dir, { persistent: false }, function (eventType, filename) {
|
package/lib/circuit-breaker.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Top-level circuit-breaker primitive.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
var
|
|
27
|
+
var retryHelper = require("./retry");
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* @primitive b.circuitBreaker.create
|
|
@@ -80,14 +80,14 @@ function create(opts) {
|
|
|
80
80
|
// Caught by hermitstash-sync operator review against v0.9.12.
|
|
81
81
|
opts = opts || {};
|
|
82
82
|
var name = (opts && typeof opts.name === "string") ? opts.name : "";
|
|
83
|
-
return new
|
|
83
|
+
return new retryHelper.CircuitBreaker(name, opts);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
module.exports = {
|
|
87
87
|
create: create,
|
|
88
|
-
CircuitBreaker:
|
|
88
|
+
CircuitBreaker: retryHelper.CircuitBreaker,
|
|
89
89
|
// Forward the error class so operators catching breaker rejections
|
|
90
90
|
// can `instanceof` against the framework's RetryError without
|
|
91
91
|
// requiring a separate b.retry import.
|
|
92
|
-
RetryError:
|
|
92
|
+
RetryError: retryHelper.RetryError,
|
|
93
93
|
};
|
package/lib/cli.js
CHANGED
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
* before encryption or temporarily switch the file to plaintext.
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
-
var
|
|
37
|
+
var nodeFs = require("node:fs");
|
|
38
38
|
var os = require("node:os");
|
|
39
|
-
var
|
|
39
|
+
var nodePath = require("path");
|
|
40
40
|
var apiSnapshot = require("./api-snapshot");
|
|
41
41
|
var argParser = require("./arg-parser");
|
|
42
42
|
var auditChain = require("./audit-chain");
|
|
@@ -44,9 +44,8 @@ var auditTools = require("./audit-tools");
|
|
|
44
44
|
var backup = require("./backup");
|
|
45
45
|
var canonicalJson = require("./canonical-json");
|
|
46
46
|
var cliHelpers = require("./cli-helpers");
|
|
47
|
-
var
|
|
48
|
-
var
|
|
49
|
-
var crypto = require("./crypto");
|
|
47
|
+
var C = require("./constants");
|
|
48
|
+
var bCrypto = require("./crypto");
|
|
50
49
|
var dev = require("./dev");
|
|
51
50
|
var fileType = require("./file-type");
|
|
52
51
|
var migrations = require("./migrations");
|
|
@@ -86,8 +85,8 @@ function _parseArgs(argv) {
|
|
|
86
85
|
|
|
87
86
|
function _resolvePath(p, cwd) {
|
|
88
87
|
if (!p) return p;
|
|
89
|
-
if (
|
|
90
|
-
return
|
|
88
|
+
if (nodePath.isAbsolute(p)) return p;
|
|
89
|
+
return nodePath.resolve(cwd || process.cwd(), p);
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
function _openSqlite(dbPath) {
|
|
@@ -432,12 +431,12 @@ function _resolveTargetModule(modulePath, ctx) {
|
|
|
432
431
|
// extensibility surfaces by definition can't be statically traced by a
|
|
433
432
|
// bundler — anyone bundling this CLI surface into SEA/pkg accepts that
|
|
434
433
|
// runtime --module=<path> arguments won't resolve. Internal framework
|
|
435
|
-
// code never reaches this
|
|
434
|
+
// code never reaches this nodePath.
|
|
436
435
|
if (!modulePath) {
|
|
437
|
-
var root =
|
|
438
|
-
return require(
|
|
436
|
+
var root = nodePath.resolve(__dirname, "..");
|
|
437
|
+
return require(nodePath.join(root, "index.js")); // allow:dynamic-require — operator-extensibility entry point
|
|
439
438
|
}
|
|
440
|
-
var abs =
|
|
439
|
+
var abs = nodePath.isAbsolute(modulePath) ? modulePath : nodePath.resolve(ctx.cwd, modulePath);
|
|
441
440
|
delete require.cache[require.resolve(abs)];
|
|
442
441
|
return require(abs); // allow:dynamic-require — operator-extensibility entry point
|
|
443
442
|
}
|
|
@@ -453,7 +452,7 @@ function _runApiSnapshot(args, ctx) {
|
|
|
453
452
|
}
|
|
454
453
|
var sub = args.pos[0];
|
|
455
454
|
var file = String(args.flags.file || "./api-snapshot.json");
|
|
456
|
-
var filePath =
|
|
455
|
+
var filePath = nodePath.isAbsolute(file) ? file : nodePath.resolve(ctx.cwd, file);
|
|
457
456
|
var modulePathOpt = typeof args.flags.module === "string" ? args.flags.module : null;
|
|
458
457
|
|
|
459
458
|
if (sub === "capture") {
|
|
@@ -559,7 +558,7 @@ function _resolvePassphrase(args, ctx) {
|
|
|
559
558
|
|
|
560
559
|
function _resolveOutPath(p, ctx) {
|
|
561
560
|
if (!p) return null;
|
|
562
|
-
return
|
|
561
|
+
return nodePath.isAbsolute(p) ? p : nodePath.resolve(ctx.cwd, p);
|
|
563
562
|
}
|
|
564
563
|
|
|
565
564
|
async function _runAudit(args, ctx) {
|
|
@@ -782,8 +781,8 @@ function _resolveRestoreBundleSelector(args, ctx, report, requireBundle) {
|
|
|
782
781
|
if (bundleFlag && bundleFlag !== true) {
|
|
783
782
|
var bundlePath = _resolvePath(String(bundleFlag), ctx.cwd);
|
|
784
783
|
return {
|
|
785
|
-
storageRoot:
|
|
786
|
-
bundleId:
|
|
784
|
+
storageRoot: nodePath.dirname(bundlePath),
|
|
785
|
+
bundleId: nodePath.basename(bundlePath),
|
|
787
786
|
};
|
|
788
787
|
}
|
|
789
788
|
if (storageRootFlag && storageRootFlag !== true) {
|
|
@@ -862,7 +861,7 @@ async function _runRestore(args, ctx) {
|
|
|
862
861
|
// its closure captures them; pass placeholders since inspect doesn't
|
|
863
862
|
// touch them.
|
|
864
863
|
var rI = restore.create({
|
|
865
|
-
dataDir:
|
|
864
|
+
dataDir: nodePath.join(os.tmpdir(), "blamejs-restore-inspect-noop"),
|
|
866
865
|
storage: storageI,
|
|
867
866
|
passphrase: "inspect-only-not-used",
|
|
868
867
|
audit: false,
|
|
@@ -943,7 +942,7 @@ async function _runRestore(args, ctx) {
|
|
|
943
942
|
if (rollbackTarget && rollbackTarget !== true) {
|
|
944
943
|
// operator can pass either a full path or just the basename inside rollback-root
|
|
945
944
|
var rt = String(rollbackTarget);
|
|
946
|
-
targetPath =
|
|
945
|
+
targetPath = nodePath.isAbsolute(rt) ? rt : nodePath.resolve(rollbackRootR, rt);
|
|
947
946
|
} else {
|
|
948
947
|
// Default to most-recent rollback point (mirrors restore.create().rollback()).
|
|
949
948
|
var ptsR;
|
|
@@ -1245,8 +1244,8 @@ async function _runBackup(args, ctx) {
|
|
|
1245
1244
|
}
|
|
1246
1245
|
|
|
1247
1246
|
if (sub === "verify") {
|
|
1248
|
-
var stagingDir =
|
|
1249
|
-
"blamejs-backup-verify-" +
|
|
1247
|
+
var stagingDir = nodePath.join(os.tmpdir(),
|
|
1248
|
+
"blamejs-backup-verify-" + bCrypto.generateToken(C.BYTES.bytes(8)));
|
|
1250
1249
|
try {
|
|
1251
1250
|
var r = await restoreBundle.extract({
|
|
1252
1251
|
bundleDir: bundleDir,
|
|
@@ -1260,7 +1259,7 @@ async function _runBackup(args, ctx) {
|
|
|
1260
1259
|
} catch (e) {
|
|
1261
1260
|
return report.error((e && e.message) || String(e));
|
|
1262
1261
|
} finally {
|
|
1263
|
-
try {
|
|
1262
|
+
try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort */ }
|
|
1264
1263
|
}
|
|
1265
1264
|
}
|
|
1266
1265
|
|
|
@@ -1461,7 +1460,7 @@ async function _runMtls(args, ctx) {
|
|
|
1461
1460
|
validityDays: daysP,
|
|
1462
1461
|
});
|
|
1463
1462
|
if (outPath) {
|
|
1464
|
-
|
|
1463
|
+
nodeFs.writeFileSync(outPath, p12.p12, { mode: 0o600 });
|
|
1465
1464
|
report.write("p12 written: " + outPath);
|
|
1466
1465
|
} else {
|
|
1467
1466
|
// No --out: stream the bytes to stdout for piping. Operators
|
|
@@ -1838,7 +1837,7 @@ function _runFileType(args, ctx) {
|
|
|
1838
1837
|
if (!file) return report.error("file path is required (positional arg after 'detect')", 2);
|
|
1839
1838
|
var resolved = _resolvePath(String(file), ctx.cwd);
|
|
1840
1839
|
var buf;
|
|
1841
|
-
try { buf =
|
|
1840
|
+
try { buf = nodeFs.readFileSync(resolved); }
|
|
1842
1841
|
catch (e) {
|
|
1843
1842
|
return report.error("read failed: " + ((e && e.message) || String(e)));
|
|
1844
1843
|
}
|
|
@@ -1915,7 +1914,7 @@ async function _runPassword(args, ctx) {
|
|
|
1915
1914
|
}
|
|
1916
1915
|
var plaintext;
|
|
1917
1916
|
if (args.flags.stdin) {
|
|
1918
|
-
try { plaintext =
|
|
1917
|
+
try { plaintext = nodeFs.readFileSync(0, "utf8").replace(/\r?\n$/, ""); }
|
|
1919
1918
|
catch (e) { return report.error("stdin read failed: " + ((e && e.message) || String(e))); }
|
|
1920
1919
|
} else if (args.flags.plaintext && args.flags.plaintext !== true) {
|
|
1921
1920
|
plaintext = String(args.flags.plaintext);
|
|
@@ -2231,7 +2230,7 @@ async function main(argv, opts) {
|
|
|
2231
2230
|
|
|
2232
2231
|
// Top-level flags handled before subcommand dispatch
|
|
2233
2232
|
if (args.flags.version || args.flags.v) {
|
|
2234
|
-
_writeLine(ctx.stdout,
|
|
2233
|
+
_writeLine(ctx.stdout, C.version);
|
|
2235
2234
|
return 0;
|
|
2236
2235
|
}
|
|
2237
2236
|
|
|
@@ -2246,7 +2245,7 @@ async function main(argv, opts) {
|
|
|
2246
2245
|
// subcommand's per-command usage
|
|
2247
2246
|
// reachable via `--help` without
|
|
2248
2247
|
// each handler having to special-case
|
|
2249
|
-
// the flag-only-no-subcommand
|
|
2248
|
+
// the flag-only-no-subcommand nodePath.
|
|
2250
2249
|
if (cmd === undefined) { _printTopHelp(ctx); return 0; }
|
|
2251
2250
|
if (args.flags.help || args.flags.h) {
|
|
2252
2251
|
args = _parseArgs(["help", cmd]);
|
|
@@ -2273,7 +2272,7 @@ async function main(argv, opts) {
|
|
|
2273
2272
|
_printTopHelp(ctx);
|
|
2274
2273
|
return 0;
|
|
2275
2274
|
}
|
|
2276
|
-
if (cmd === "version") { _writeLine(ctx.stdout,
|
|
2275
|
+
if (cmd === "version") { _writeLine(ctx.stdout, C.version); return 0; }
|
|
2277
2276
|
|
|
2278
2277
|
var rest = { pos: args.pos.slice(1), flags: args.flags };
|
|
2279
2278
|
if (cmd === "migrate") return await _runMigrate(rest, ctx);
|