@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
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
* with the original file untouched.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
var
|
|
39
|
-
var
|
|
38
|
+
var nodeFs = require("fs");
|
|
39
|
+
var nodePath = require("path");
|
|
40
40
|
var atomicFile = require("../atomic-file");
|
|
41
41
|
var vaultWrap = require("./wrap");
|
|
42
42
|
var { defineClass } = require("../framework-error");
|
|
@@ -48,10 +48,10 @@ var SEALED_NAME = "vault.key.sealed";
|
|
|
48
48
|
|
|
49
49
|
function _paths(dataDir) {
|
|
50
50
|
return {
|
|
51
|
-
plaintext:
|
|
52
|
-
plaintextTmp:
|
|
53
|
-
sealed:
|
|
54
|
-
sealedTmp:
|
|
51
|
+
plaintext: nodePath.join(dataDir, PLAINTEXT_NAME),
|
|
52
|
+
plaintextTmp: nodePath.join(dataDir, PLAINTEXT_NAME + ".tmp"),
|
|
53
|
+
sealed: nodePath.join(dataDir, SEALED_NAME),
|
|
54
|
+
sealedTmp: nodePath.join(dataDir, SEALED_NAME + ".tmp"),
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -60,7 +60,7 @@ function _requireDataDir(opts) {
|
|
|
60
60
|
throw new VaultPassphraseError("vault-passphrase/no-datadir",
|
|
61
61
|
"opts.dataDir is required (path to the framework data directory)");
|
|
62
62
|
}
|
|
63
|
-
if (!
|
|
63
|
+
if (!nodeFs.existsSync(opts.dataDir)) {
|
|
64
64
|
throw new VaultPassphraseError("vault-passphrase/no-datadir",
|
|
65
65
|
"opts.dataDir does not exist: " + opts.dataDir);
|
|
66
66
|
}
|
|
@@ -80,8 +80,8 @@ function _requirePassphrase(opts, fieldName) {
|
|
|
80
80
|
// don't have the original write fd around.
|
|
81
81
|
function _fsyncPath(p) {
|
|
82
82
|
try {
|
|
83
|
-
var fd =
|
|
84
|
-
try { atomicFile.fsync(fd); } finally {
|
|
83
|
+
var fd = nodeFs.openSync(p, "r+");
|
|
84
|
+
try { atomicFile.fsync(fd); } finally { nodeFs.closeSync(fd); }
|
|
85
85
|
} catch (_e) { /* best-effort across filesystems */ }
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -90,13 +90,13 @@ function _fsyncPath(p) {
|
|
|
90
90
|
function preflightSealable(opts) {
|
|
91
91
|
_requireDataDir(opts);
|
|
92
92
|
var p = _paths(opts.dataDir);
|
|
93
|
-
if (!
|
|
93
|
+
if (!nodeFs.existsSync(p.plaintext)) {
|
|
94
94
|
return { ok: false, reason: "plaintext " + PLAINTEXT_NAME + " does not exist — nothing to seal" };
|
|
95
95
|
}
|
|
96
|
-
if (
|
|
96
|
+
if (nodeFs.existsSync(p.sealed)) {
|
|
97
97
|
return { ok: false, reason: SEALED_NAME + " already exists; refusing to overwrite" };
|
|
98
98
|
}
|
|
99
|
-
if (
|
|
99
|
+
if (nodeFs.existsSync(p.sealedTmp)) {
|
|
100
100
|
return { ok: false, reason: "stale " + SEALED_NAME + ".tmp from a previous crash; remove it manually after verifying the directory state" };
|
|
101
101
|
}
|
|
102
102
|
return { ok: true };
|
|
@@ -105,13 +105,13 @@ function preflightSealable(opts) {
|
|
|
105
105
|
function preflightUnsealable(opts) {
|
|
106
106
|
_requireDataDir(opts);
|
|
107
107
|
var p = _paths(opts.dataDir);
|
|
108
|
-
if (!
|
|
108
|
+
if (!nodeFs.existsSync(p.sealed)) {
|
|
109
109
|
return { ok: false, reason: SEALED_NAME + " does not exist — nothing to unseal" };
|
|
110
110
|
}
|
|
111
|
-
if (
|
|
111
|
+
if (nodeFs.existsSync(p.plaintext)) {
|
|
112
112
|
return { ok: false, reason: "plaintext " + PLAINTEXT_NAME + " already exists; refusing to overwrite" };
|
|
113
113
|
}
|
|
114
|
-
if (
|
|
114
|
+
if (nodeFs.existsSync(p.plaintextTmp)) {
|
|
115
115
|
return { ok: false, reason: "stale " + PLAINTEXT_NAME + ".tmp from a previous crash; remove it manually after verifying the directory state" };
|
|
116
116
|
}
|
|
117
117
|
return { ok: true };
|
|
@@ -120,10 +120,10 @@ function preflightUnsealable(opts) {
|
|
|
120
120
|
function preflightRotatable(opts) {
|
|
121
121
|
_requireDataDir(opts);
|
|
122
122
|
var p = _paths(opts.dataDir);
|
|
123
|
-
if (!
|
|
123
|
+
if (!nodeFs.existsSync(p.sealed)) {
|
|
124
124
|
return { ok: false, reason: SEALED_NAME + " does not exist — rotate has nothing to operate on" };
|
|
125
125
|
}
|
|
126
|
-
if (
|
|
126
|
+
if (nodeFs.existsSync(p.sealedTmp)) {
|
|
127
127
|
return { ok: false, reason: "stale " + SEALED_NAME + ".tmp from a previous crash; remove it manually after verifying the directory state" };
|
|
128
128
|
}
|
|
129
129
|
return { ok: true };
|
|
@@ -141,39 +141,39 @@ async function seal(opts) {
|
|
|
141
141
|
var p = _paths(opts.dataDir);
|
|
142
142
|
var keepPlaintext = !!opts.keepPlaintext;
|
|
143
143
|
|
|
144
|
-
var plainBytes =
|
|
144
|
+
var plainBytes = nodeFs.readFileSync(p.plaintext);
|
|
145
145
|
var sealedBytes = await vaultWrap.wrap(plainBytes, opts.passphrase);
|
|
146
146
|
|
|
147
147
|
// Step 1: write sealed.tmp + fsync
|
|
148
|
-
|
|
148
|
+
nodeFs.writeFileSync(p.sealedTmp, sealedBytes, { mode: 0o600 });
|
|
149
149
|
_fsyncPath(p.sealedTmp);
|
|
150
150
|
atomicFile.fsyncDir(opts.dataDir);
|
|
151
151
|
|
|
152
152
|
// Step 2: round-trip verify the .tmp before committing the rename
|
|
153
|
-
var verifyBytes =
|
|
153
|
+
var verifyBytes = nodeFs.readFileSync(p.sealedTmp);
|
|
154
154
|
var unwrapped;
|
|
155
155
|
try {
|
|
156
156
|
unwrapped = await vaultWrap.unwrap(verifyBytes, opts.passphrase);
|
|
157
157
|
} catch (e) {
|
|
158
|
-
try {
|
|
158
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
159
159
|
throw new VaultPassphraseError("vault-passphrase/verify-failed",
|
|
160
160
|
"round-trip verification of sealed file failed: " + ((e && e.message) || String(e)) +
|
|
161
161
|
" — original " + PLAINTEXT_NAME + " is UNCHANGED");
|
|
162
162
|
}
|
|
163
163
|
if (Buffer.compare(unwrapped, plainBytes) !== 0) {
|
|
164
|
-
try {
|
|
164
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
165
165
|
throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
|
|
166
166
|
"round-trip produced different bytes than the original — original " + PLAINTEXT_NAME +
|
|
167
167
|
" is UNCHANGED. Filesystem may be faulty.");
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// Step 3: atomic rename sealed.tmp → sealed
|
|
171
|
-
|
|
171
|
+
nodeFs.renameSync(p.sealedTmp, p.sealed);
|
|
172
172
|
atomicFile.fsyncDir(opts.dataDir);
|
|
173
173
|
|
|
174
174
|
// Step 4: delete plaintext (unless keepPlaintext)
|
|
175
175
|
if (!keepPlaintext) {
|
|
176
|
-
|
|
176
|
+
nodeFs.unlinkSync(p.plaintext);
|
|
177
177
|
atomicFile.fsyncDir(opts.dataDir);
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -194,7 +194,7 @@ async function unseal(opts) {
|
|
|
194
194
|
}
|
|
195
195
|
var p = _paths(opts.dataDir);
|
|
196
196
|
|
|
197
|
-
var sealedBytes =
|
|
197
|
+
var sealedBytes = nodeFs.readFileSync(p.sealed);
|
|
198
198
|
var plainBytes;
|
|
199
199
|
try {
|
|
200
200
|
plainBytes = await vaultWrap.unwrap(sealedBytes, opts.passphrase);
|
|
@@ -205,25 +205,25 @@ async function unseal(opts) {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
// Step 1: write plaintext.tmp + fsync
|
|
208
|
-
|
|
208
|
+
nodeFs.writeFileSync(p.plaintextTmp, plainBytes, { mode: 0o600 });
|
|
209
209
|
_fsyncPath(p.plaintextTmp);
|
|
210
210
|
atomicFile.fsyncDir(opts.dataDir);
|
|
211
211
|
|
|
212
212
|
// Step 2: round-trip sanity — re-read tmp and verify
|
|
213
|
-
var verifyBytes =
|
|
213
|
+
var verifyBytes = nodeFs.readFileSync(p.plaintextTmp);
|
|
214
214
|
if (Buffer.compare(verifyBytes, plainBytes) !== 0) {
|
|
215
|
-
try {
|
|
215
|
+
try { nodeFs.unlinkSync(p.plaintextTmp); } catch (_e) { /* cleanup */ }
|
|
216
216
|
throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
|
|
217
217
|
"plaintext.tmp re-read differs from in-memory bytes — filesystem may be faulty. " +
|
|
218
218
|
SEALED_NAME + " is UNCHANGED");
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// Step 3: atomic rename plaintext.tmp → plaintext
|
|
222
|
-
|
|
222
|
+
nodeFs.renameSync(p.plaintextTmp, p.plaintext);
|
|
223
223
|
atomicFile.fsyncDir(opts.dataDir);
|
|
224
224
|
|
|
225
225
|
// Step 4: delete sealed file
|
|
226
|
-
|
|
226
|
+
nodeFs.unlinkSync(p.sealed);
|
|
227
227
|
atomicFile.fsyncDir(opts.dataDir);
|
|
228
228
|
|
|
229
229
|
return { plaintextPath: p.plaintext };
|
|
@@ -245,7 +245,7 @@ async function rotate(opts) {
|
|
|
245
245
|
}
|
|
246
246
|
var p = _paths(opts.dataDir);
|
|
247
247
|
|
|
248
|
-
var sealedBytes =
|
|
248
|
+
var sealedBytes = nodeFs.readFileSync(p.sealed);
|
|
249
249
|
var plainBytes;
|
|
250
250
|
try {
|
|
251
251
|
plainBytes = await vaultWrap.unwrap(sealedBytes, opts.oldPassphrase);
|
|
@@ -257,23 +257,23 @@ async function rotate(opts) {
|
|
|
257
257
|
var newSealedBytes = await vaultWrap.wrap(plainBytes, opts.newPassphrase);
|
|
258
258
|
|
|
259
259
|
// Step 1: write new sealed.tmp + fsync
|
|
260
|
-
|
|
260
|
+
nodeFs.writeFileSync(p.sealedTmp, newSealedBytes, { mode: 0o600 });
|
|
261
261
|
_fsyncPath(p.sealedTmp);
|
|
262
262
|
atomicFile.fsyncDir(opts.dataDir);
|
|
263
263
|
|
|
264
264
|
// Step 2: round-trip verify with NEW passphrase, AND assert unwrap
|
|
265
265
|
// with the OLD passphrase fails — otherwise the rotation didn't take.
|
|
266
|
-
var verifyBytes =
|
|
266
|
+
var verifyBytes = nodeFs.readFileSync(p.sealedTmp);
|
|
267
267
|
var verifyPlain;
|
|
268
268
|
try { verifyPlain = await vaultWrap.unwrap(verifyBytes, opts.newPassphrase); }
|
|
269
269
|
catch (e) {
|
|
270
|
-
try {
|
|
270
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
271
271
|
throw new VaultPassphraseError("vault-passphrase/verify-failed",
|
|
272
272
|
"round-trip with new passphrase failed: " + ((e && e.message) || String(e)) +
|
|
273
273
|
" — " + SEALED_NAME + " is UNCHANGED");
|
|
274
274
|
}
|
|
275
275
|
if (Buffer.compare(verifyPlain, plainBytes) !== 0) {
|
|
276
|
-
try {
|
|
276
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
277
277
|
throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
|
|
278
278
|
"rotated sealed file decrypts under new passphrase but to different bytes — " +
|
|
279
279
|
SEALED_NAME + " is UNCHANGED. Filesystem may be faulty.");
|
|
@@ -283,7 +283,7 @@ async function rotate(opts) {
|
|
|
283
283
|
// input unchanged — refuse to commit.
|
|
284
284
|
try {
|
|
285
285
|
await vaultWrap.unwrap(verifyBytes, opts.oldPassphrase);
|
|
286
|
-
try {
|
|
286
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
287
287
|
throw new VaultPassphraseError("vault-passphrase/rotate-noop",
|
|
288
288
|
"old passphrase still unwraps the new sealed bytes — rotation did not take effect");
|
|
289
289
|
} catch (e) {
|
|
@@ -292,7 +292,7 @@ async function rotate(opts) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// Step 3: atomic rename — swap in the new sealed file
|
|
295
|
-
|
|
295
|
+
nodeFs.renameSync(p.sealedTmp, p.sealed);
|
|
296
296
|
atomicFile.fsyncDir(opts.dataDir);
|
|
297
297
|
|
|
298
298
|
return { sealedPath: p.sealed };
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* exposure to later env-dump surfaces. This doesn't zero the memory
|
|
24
24
|
* (JavaScript can't) but does remove the env-object reference.
|
|
25
25
|
*/
|
|
26
|
-
var
|
|
26
|
+
var nodeFs = require("fs");
|
|
27
27
|
var readline = require("readline");
|
|
28
28
|
var safeEnv = require("../parsers/safe-env");
|
|
29
29
|
var safeBuffer = require("../safe-buffer");
|
|
@@ -83,7 +83,7 @@ async function fromFile(filePath, opts) {
|
|
|
83
83
|
}
|
|
84
84
|
var raw;
|
|
85
85
|
try {
|
|
86
|
-
raw =
|
|
86
|
+
raw = nodeFs.readFileSync(filePath);
|
|
87
87
|
} catch (e) {
|
|
88
88
|
throw new Error("failed to read " + envVars.file + " (" + filePath + "): " + e.code);
|
|
89
89
|
}
|
package/lib/vault/rotate.js
CHANGED
|
@@ -48,18 +48,18 @@
|
|
|
48
48
|
* sampler skips them.
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
|
-
var
|
|
52
|
-
var
|
|
51
|
+
var nodeFs = require("fs");
|
|
52
|
+
var nodePath = require("path");
|
|
53
53
|
var { DatabaseSync } = require("node:sqlite");
|
|
54
54
|
var atomicFile = require("../atomic-file");
|
|
55
55
|
var safeSql = require("../safe-sql");
|
|
56
56
|
var C = require("../constants");
|
|
57
57
|
var cryptoField = require("../crypto-field");
|
|
58
|
-
var
|
|
58
|
+
var bCrypto = require("../crypto");
|
|
59
59
|
var dbSchema = require("../db-schema");
|
|
60
60
|
var lazyRequire = require("../lazy-require");
|
|
61
61
|
var { boot } = require("../log");
|
|
62
|
-
var
|
|
62
|
+
var numericBounds = require("../numeric-bounds");
|
|
63
63
|
var safeJson = require("../safe-json");
|
|
64
64
|
var validateOpts = require("../validate-opts");
|
|
65
65
|
var vaultWrap = lazyRequire(function () { return require("./wrap"); });
|
|
@@ -109,7 +109,7 @@ function _knownColumnsFor(schema, infraColumns) {
|
|
|
109
109
|
|
|
110
110
|
function validateSchemaMatch(db, opts) {
|
|
111
111
|
opts = opts || {};
|
|
112
|
-
|
|
112
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.driftSampleLimit,
|
|
113
113
|
"validateSchemaMatch: driftSampleLimit", VaultRotateError, "vault-rotate/bad-opt");
|
|
114
114
|
var sampleLimit = opts.driftSampleLimit !== undefined
|
|
115
115
|
? opts.driftSampleLimit : DEFAULT_DRIFT_SAMPLE_LIMIT;
|
|
@@ -254,7 +254,7 @@ function verify(opts) {
|
|
|
254
254
|
var keys = opts.keys;
|
|
255
255
|
var db = opts.db;
|
|
256
256
|
var oldKeys = opts.oldKeys || null;
|
|
257
|
-
|
|
257
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.sampleMin,
|
|
258
258
|
"verify: sampleMin", VaultRotateError, "vault-rotate/bad-opt");
|
|
259
259
|
var sampleMin = opts.sampleMin !== undefined
|
|
260
260
|
? opts.sampleMin : DEFAULT_VERIFY_SAMPLE_MIN;
|
|
@@ -263,7 +263,7 @@ function verify(opts) {
|
|
|
263
263
|
opts.samplePercent <= 0)) {
|
|
264
264
|
throw new VaultRotateError("vault-rotate/bad-opt",
|
|
265
265
|
"verify: samplePercent must be a positive finite fraction; got " +
|
|
266
|
-
|
|
266
|
+
numericBounds.shape(opts.samplePercent));
|
|
267
267
|
}
|
|
268
268
|
var samplePct = opts.samplePercent !== undefined
|
|
269
269
|
? opts.samplePercent : DEFAULT_VERIFY_SAMPLE_FRAC;
|
|
@@ -311,7 +311,7 @@ function verify(opts) {
|
|
|
311
311
|
if (typeof v !== "string" || v.indexOf(VAULT_PREFIX) !== 0) continue;
|
|
312
312
|
var payload = v.substring(VAULT_PREFIX.length);
|
|
313
313
|
|
|
314
|
-
try {
|
|
314
|
+
try { bCrypto.decrypt(payload, keys); }
|
|
315
315
|
catch (e) {
|
|
316
316
|
rowFailed = true;
|
|
317
317
|
failures.push({
|
|
@@ -324,7 +324,7 @@ function verify(opts) {
|
|
|
324
324
|
|
|
325
325
|
if (oldKeys && !foundOldFail) {
|
|
326
326
|
try {
|
|
327
|
-
|
|
327
|
+
bCrypto.decrypt(payload, oldKeys);
|
|
328
328
|
regressions.push({
|
|
329
329
|
table: table,
|
|
330
330
|
column: col,
|
|
@@ -391,8 +391,8 @@ function _emit(cb, ev) {
|
|
|
391
391
|
// the original write fd around.
|
|
392
392
|
function _fsyncFileByPath(p) {
|
|
393
393
|
try {
|
|
394
|
-
var fd =
|
|
395
|
-
try {
|
|
394
|
+
var fd = nodeFs.openSync(p, "r+");
|
|
395
|
+
try { nodeFs.fsyncSync(fd); } finally { nodeFs.closeSync(fd); }
|
|
396
396
|
} catch (_e) { /* best-effort across platforms */ }
|
|
397
397
|
}
|
|
398
398
|
|
|
@@ -400,8 +400,8 @@ function _reSealValue(sealedValue, oldKeys, newKeys) {
|
|
|
400
400
|
if (typeof sealedValue !== "string") return sealedValue;
|
|
401
401
|
if (sealedValue.indexOf(C.VAULT_PREFIX) !== 0) return sealedValue;
|
|
402
402
|
var payload = sealedValue.substring(VAULT_PREFIX_LEN);
|
|
403
|
-
var plain =
|
|
404
|
-
return C.VAULT_PREFIX +
|
|
403
|
+
var plain = bCrypto.decrypt(payload, oldKeys);
|
|
404
|
+
return C.VAULT_PREFIX + bCrypto.encrypt(plain, newKeys);
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
// Walk a JSON-decoded value, re-sealing every vault-prefixed string.
|
|
@@ -530,12 +530,12 @@ async function rotate(opts) {
|
|
|
530
530
|
throw new VaultRotateError("vault-rotate/no-keys",
|
|
531
531
|
"rotate: opts.oldKeys and opts.newKeys are required");
|
|
532
532
|
}
|
|
533
|
-
if (typeof opts.dataDir !== "string" || !
|
|
533
|
+
if (typeof opts.dataDir !== "string" || !nodeFs.existsSync(opts.dataDir)) {
|
|
534
534
|
throw new VaultRotateError("vault-rotate/no-datadir",
|
|
535
535
|
"rotate: opts.dataDir is required and must exist");
|
|
536
536
|
}
|
|
537
537
|
validateOpts.requireNonEmptyString(opts.stagingDir, "rotate: opts.stagingDir", VaultRotateError, "vault-rotate/no-staging");
|
|
538
|
-
if (
|
|
538
|
+
if (nodeFs.existsSync(opts.stagingDir)) {
|
|
539
539
|
throw new VaultRotateError("vault-rotate/staging-exists",
|
|
540
540
|
"rotate: stagingDir already exists: " + opts.stagingDir);
|
|
541
541
|
}
|
|
@@ -571,30 +571,30 @@ async function rotate(opts) {
|
|
|
571
571
|
_emit(progress, { phase: "copy_verbatim" });
|
|
572
572
|
for (var vf = 0; vf < paths.verbatimFiles.length; vf++) {
|
|
573
573
|
var entry = paths.verbatimFiles[vf];
|
|
574
|
-
var src =
|
|
575
|
-
if (!
|
|
574
|
+
var src = nodePath.join(dataDir, entry.relativePath);
|
|
575
|
+
if (!nodeFs.existsSync(src)) {
|
|
576
576
|
if (entry.required) {
|
|
577
577
|
throw new VaultRotateError("vault-rotate/missing-verbatim",
|
|
578
578
|
"rotate: required verbatim file missing: " + entry.relativePath);
|
|
579
579
|
}
|
|
580
580
|
continue;
|
|
581
581
|
}
|
|
582
|
-
var dest =
|
|
583
|
-
atomicFile.ensureDir(
|
|
584
|
-
|
|
582
|
+
var dest = nodePath.join(stagingDir, entry.relativePath);
|
|
583
|
+
atomicFile.ensureDir(nodePath.dirname(dest));
|
|
584
|
+
nodeFs.copyFileSync(src, dest);
|
|
585
585
|
}
|
|
586
586
|
for (var vd = 0; vd < paths.verbatimDirs.length; vd++) {
|
|
587
587
|
var dent = paths.verbatimDirs[vd];
|
|
588
|
-
var sdir =
|
|
589
|
-
if (!
|
|
588
|
+
var sdir = nodePath.join(dataDir, dent.relativePath);
|
|
589
|
+
if (!nodeFs.existsSync(sdir)) {
|
|
590
590
|
if (dent.required) {
|
|
591
591
|
throw new VaultRotateError("vault-rotate/missing-verbatim-dir",
|
|
592
592
|
"rotate: required verbatim dir missing: " + dent.relativePath);
|
|
593
593
|
}
|
|
594
594
|
continue;
|
|
595
595
|
}
|
|
596
|
-
if (
|
|
597
|
-
atomicFile.copyDirRecursive(sdir,
|
|
596
|
+
if (nodeFs.existsSync(sdir)) {
|
|
597
|
+
atomicFile.copyDirRecursive(sdir, nodePath.join(stagingDir, dent.relativePath));
|
|
598
598
|
}
|
|
599
599
|
}
|
|
600
600
|
|
|
@@ -603,59 +603,59 @@ async function rotate(opts) {
|
|
|
603
603
|
var keysJson = JSON.stringify(newKeys, null, 2);
|
|
604
604
|
if (mode === "wrapped") {
|
|
605
605
|
var sealed = await vaultWrap().wrap(keysJson, opts.newPassphrase);
|
|
606
|
-
|
|
606
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.vaultKeySealed), sealed, { mode: 0o600 });
|
|
607
607
|
} else {
|
|
608
|
-
|
|
608
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.vaultKeyPlain), keysJson, { mode: 0o600 });
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
// 3. re-seal db.key.enc + any operator-supplied additionalSealed files
|
|
612
612
|
_emit(progress, { phase: "reseal_files" });
|
|
613
|
-
var dbKeySealedPath =
|
|
613
|
+
var dbKeySealedPath = nodePath.join(dataDir, paths.dbKeySealed);
|
|
614
614
|
var dbKey = null;
|
|
615
|
-
if (
|
|
616
|
-
var sealedKey =
|
|
615
|
+
if (nodeFs.existsSync(dbKeySealedPath)) {
|
|
616
|
+
var sealedKey = nodeFs.readFileSync(dbKeySealedPath, "utf8").trim();
|
|
617
617
|
if (sealedKey.indexOf(C.VAULT_PREFIX) !== 0) {
|
|
618
618
|
throw new VaultRotateError("vault-rotate/bad-dbkey",
|
|
619
619
|
"rotate: db.key.enc does not start with the vault prefix");
|
|
620
620
|
}
|
|
621
|
-
var dbKeyB64 =
|
|
621
|
+
var dbKeyB64 = bCrypto.decrypt(sealedKey.substring(VAULT_PREFIX_LEN), oldKeys);
|
|
622
622
|
dbKey = Buffer.from(dbKeyB64, "base64");
|
|
623
|
-
var resealedKey = C.VAULT_PREFIX +
|
|
624
|
-
|
|
623
|
+
var resealedKey = C.VAULT_PREFIX + bCrypto.encrypt(dbKeyB64, newKeys);
|
|
624
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.dbKeySealed), resealedKey, { mode: 0o600 });
|
|
625
625
|
}
|
|
626
626
|
for (var as = 0; as < paths.additionalSealed.length; as++) {
|
|
627
627
|
var ase = paths.additionalSealed[as];
|
|
628
|
-
var asSrc =
|
|
629
|
-
if (!
|
|
628
|
+
var asSrc = nodePath.join(dataDir, ase.relativePath);
|
|
629
|
+
if (!nodeFs.existsSync(asSrc)) {
|
|
630
630
|
if (ase.required) {
|
|
631
631
|
throw new VaultRotateError("vault-rotate/missing-sealed",
|
|
632
632
|
"rotate: required sealed file missing: " + ase.relativePath);
|
|
633
633
|
}
|
|
634
634
|
continue;
|
|
635
635
|
}
|
|
636
|
-
var current =
|
|
636
|
+
var current = nodeFs.readFileSync(asSrc, "utf8").trim();
|
|
637
637
|
if (current.indexOf(C.VAULT_PREFIX) !== 0) {
|
|
638
638
|
throw new VaultRotateError("vault-rotate/bad-sealed",
|
|
639
639
|
"rotate: sealed file does not start with the vault prefix: " + ase.relativePath);
|
|
640
640
|
}
|
|
641
|
-
var asDestDir =
|
|
642
|
-
if (!
|
|
643
|
-
|
|
641
|
+
var asDestDir = nodePath.join(stagingDir, nodePath.dirname(ase.relativePath));
|
|
642
|
+
if (!nodeFs.existsSync(asDestDir)) atomicFile.ensureDir(asDestDir);
|
|
643
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, ase.relativePath),
|
|
644
644
|
_reSealValue(current, oldKeys, newKeys), { mode: 0o600 });
|
|
645
645
|
}
|
|
646
646
|
|
|
647
647
|
// 4. decrypt + rotate + re-encrypt db.enc
|
|
648
648
|
_emit(progress, { phase: "rotate_db" });
|
|
649
|
-
var encDbPath =
|
|
649
|
+
var encDbPath = nodePath.join(dataDir, paths.encryptedDb);
|
|
650
650
|
var tablesProcessed = 0;
|
|
651
651
|
var totalRowsProcessed = 0;
|
|
652
652
|
var verifyResult = null;
|
|
653
653
|
|
|
654
|
-
if (
|
|
655
|
-
var packed =
|
|
656
|
-
var plainBytes =
|
|
657
|
-
var tmpDbPath =
|
|
658
|
-
|
|
654
|
+
if (nodeFs.existsSync(encDbPath) && dbKey) {
|
|
655
|
+
var packed = nodeFs.readFileSync(encDbPath);
|
|
656
|
+
var plainBytes = bCrypto.decryptPacked(packed, dbKey);
|
|
657
|
+
var tmpDbPath = nodePath.join(stagingDir, "_blamejs_rotate.tmp.db");
|
|
658
|
+
nodeFs.writeFileSync(tmpDbPath, plainBytes, { mode: 0o600 });
|
|
659
659
|
|
|
660
660
|
var db = new DatabaseSync(tmpDbPath);
|
|
661
661
|
try {
|
|
@@ -708,32 +708,32 @@ async function rotate(opts) {
|
|
|
708
708
|
// sidecar may be absent (depending on whether journal_mode produced
|
|
709
709
|
// one for this run); log at debug so the cleanup attempt isn't
|
|
710
710
|
// silently swallowed when something genuinely unexpected fails.
|
|
711
|
-
try {
|
|
712
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
713
|
-
try {
|
|
714
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
711
|
+
try { nodeFs.unlinkSync(tmpDbPath + "-wal"); }
|
|
712
|
+
catch (e) { rotateLog.debug("cleanup-failed", { op: "nodeFs.unlinkSync", path: tmpDbPath + "-wal", error: e.message }); }
|
|
713
|
+
try { nodeFs.unlinkSync(tmpDbPath + "-shm"); }
|
|
714
|
+
catch (e) { rotateLog.debug("cleanup-failed", { op: "nodeFs.unlinkSync", path: tmpDbPath + "-shm", error: e.message }); }
|
|
715
715
|
|
|
716
|
-
var rotatedBytes =
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
716
|
+
var rotatedBytes = nodeFs.readFileSync(tmpDbPath);
|
|
717
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.encryptedDb),
|
|
718
|
+
bCrypto.encryptPacked(rotatedBytes, dbKey));
|
|
719
|
+
nodeFs.unlinkSync(tmpDbPath);
|
|
720
720
|
|
|
721
721
|
// Round-trip verify on the staged DB
|
|
722
722
|
_emit(progress, { phase: "verify" });
|
|
723
|
-
var verifyTmp =
|
|
724
|
-
|
|
725
|
-
|
|
723
|
+
var verifyTmp = nodePath.join(stagingDir, "_blamejs_verify.tmp.db");
|
|
724
|
+
nodeFs.writeFileSync(verifyTmp,
|
|
725
|
+
bCrypto.decryptPacked(nodeFs.readFileSync(nodePath.join(stagingDir, paths.encryptedDb)), dbKey));
|
|
726
726
|
var vdb = new DatabaseSync(verifyTmp);
|
|
727
727
|
try {
|
|
728
728
|
verifyResult = verify({ keys: newKeys, db: vdb, oldKeys: oldKeys });
|
|
729
729
|
} finally {
|
|
730
730
|
vdb.close();
|
|
731
|
-
try {
|
|
732
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
733
|
-
try {
|
|
734
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
735
|
-
try {
|
|
736
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
731
|
+
try { nodeFs.unlinkSync(verifyTmp); }
|
|
732
|
+
catch (e) { rotateLog.debug("cleanup-failed", { op: "nodeFs.unlinkSync", path: verifyTmp, error: e.message }); }
|
|
733
|
+
try { nodeFs.unlinkSync(verifyTmp + "-wal"); }
|
|
734
|
+
catch (e) { rotateLog.debug("cleanup-failed", { op: "nodeFs.unlinkSync", path: verifyTmp + "-wal", error: e.message }); }
|
|
735
|
+
try { nodeFs.unlinkSync(verifyTmp + "-shm"); }
|
|
736
|
+
catch (e) { rotateLog.debug("cleanup-failed", { op: "nodeFs.unlinkSync", path: verifyTmp + "-shm", error: e.message }); }
|
|
737
737
|
}
|
|
738
738
|
if (!verifyResult.ok) {
|
|
739
739
|
throw new VaultRotateError("vault-rotate/verify-failed",
|
|
@@ -747,10 +747,10 @@ async function rotate(opts) {
|
|
|
747
747
|
// 5. fsync staging for durability before caller does the swap
|
|
748
748
|
_emit(progress, { phase: "fsync" });
|
|
749
749
|
function fsyncTree(dir) {
|
|
750
|
-
var entries =
|
|
750
|
+
var entries = nodeFs.readdirSync(dir);
|
|
751
751
|
for (var i = 0; i < entries.length; i++) {
|
|
752
|
-
var p =
|
|
753
|
-
var st =
|
|
752
|
+
var p = nodePath.join(dir, entries[i]);
|
|
753
|
+
var st = nodeFs.statSync(p);
|
|
754
754
|
if (st.isFile()) _fsyncFileByPath(p);
|
|
755
755
|
else if (st.isDirectory()) fsyncTree(p);
|
|
756
756
|
}
|