@blamejs/core 0.9.12 → 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 +3 -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 +10 -7
- 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/audit.js +29 -17
- 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 +24 -9
- 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/crypto.js +145 -0
- 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/dsr.js +22 -15
- 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/inbox.js +21 -15
- 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 +249 -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 +250 -0
- 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-agent.js +116 -26
- 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/retry.js +50 -0
- 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-standalone-verifier.js +280 -0
- package/lib/self-update.js +32 -26
- 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 +70 -66
- 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
|
@@ -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,17 +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
|
+
var safeSql = require("../safe-sql");
|
|
55
56
|
var C = require("../constants");
|
|
56
57
|
var cryptoField = require("../crypto-field");
|
|
57
|
-
var
|
|
58
|
+
var bCrypto = require("../crypto");
|
|
58
59
|
var dbSchema = require("../db-schema");
|
|
59
60
|
var lazyRequire = require("../lazy-require");
|
|
60
61
|
var { boot } = require("../log");
|
|
61
|
-
var
|
|
62
|
+
var numericBounds = require("../numeric-bounds");
|
|
62
63
|
var safeJson = require("../safe-json");
|
|
63
64
|
var validateOpts = require("../validate-opts");
|
|
64
65
|
var vaultWrap = lazyRequire(function () { return require("./wrap"); });
|
|
@@ -108,7 +109,7 @@ function _knownColumnsFor(schema, infraColumns) {
|
|
|
108
109
|
|
|
109
110
|
function validateSchemaMatch(db, opts) {
|
|
110
111
|
opts = opts || {};
|
|
111
|
-
|
|
112
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.driftSampleLimit,
|
|
112
113
|
"validateSchemaMatch: driftSampleLimit", VaultRotateError, "vault-rotate/bad-opt");
|
|
113
114
|
var sampleLimit = opts.driftSampleLimit !== undefined
|
|
114
115
|
? opts.driftSampleLimit : DEFAULT_DRIFT_SAMPLE_LIMIT;
|
|
@@ -253,7 +254,7 @@ function verify(opts) {
|
|
|
253
254
|
var keys = opts.keys;
|
|
254
255
|
var db = opts.db;
|
|
255
256
|
var oldKeys = opts.oldKeys || null;
|
|
256
|
-
|
|
257
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.sampleMin,
|
|
257
258
|
"verify: sampleMin", VaultRotateError, "vault-rotate/bad-opt");
|
|
258
259
|
var sampleMin = opts.sampleMin !== undefined
|
|
259
260
|
? opts.sampleMin : DEFAULT_VERIFY_SAMPLE_MIN;
|
|
@@ -262,7 +263,7 @@ function verify(opts) {
|
|
|
262
263
|
opts.samplePercent <= 0)) {
|
|
263
264
|
throw new VaultRotateError("vault-rotate/bad-opt",
|
|
264
265
|
"verify: samplePercent must be a positive finite fraction; got " +
|
|
265
|
-
|
|
266
|
+
numericBounds.shape(opts.samplePercent));
|
|
266
267
|
}
|
|
267
268
|
var samplePct = opts.samplePercent !== undefined
|
|
268
269
|
? opts.samplePercent : DEFAULT_VERIFY_SAMPLE_FRAC;
|
|
@@ -310,7 +311,7 @@ function verify(opts) {
|
|
|
310
311
|
if (typeof v !== "string" || v.indexOf(VAULT_PREFIX) !== 0) continue;
|
|
311
312
|
var payload = v.substring(VAULT_PREFIX.length);
|
|
312
313
|
|
|
313
|
-
try {
|
|
314
|
+
try { bCrypto.decrypt(payload, keys); }
|
|
314
315
|
catch (e) {
|
|
315
316
|
rowFailed = true;
|
|
316
317
|
failures.push({
|
|
@@ -323,7 +324,7 @@ function verify(opts) {
|
|
|
323
324
|
|
|
324
325
|
if (oldKeys && !foundOldFail) {
|
|
325
326
|
try {
|
|
326
|
-
|
|
327
|
+
bCrypto.decrypt(payload, oldKeys);
|
|
327
328
|
regressions.push({
|
|
328
329
|
table: table,
|
|
329
330
|
column: col,
|
|
@@ -390,8 +391,8 @@ function _emit(cb, ev) {
|
|
|
390
391
|
// the original write fd around.
|
|
391
392
|
function _fsyncFileByPath(p) {
|
|
392
393
|
try {
|
|
393
|
-
var fd =
|
|
394
|
-
try {
|
|
394
|
+
var fd = nodeFs.openSync(p, "r+");
|
|
395
|
+
try { nodeFs.fsyncSync(fd); } finally { nodeFs.closeSync(fd); }
|
|
395
396
|
} catch (_e) { /* best-effort across platforms */ }
|
|
396
397
|
}
|
|
397
398
|
|
|
@@ -399,8 +400,8 @@ function _reSealValue(sealedValue, oldKeys, newKeys) {
|
|
|
399
400
|
if (typeof sealedValue !== "string") return sealedValue;
|
|
400
401
|
if (sealedValue.indexOf(C.VAULT_PREFIX) !== 0) return sealedValue;
|
|
401
402
|
var payload = sealedValue.substring(VAULT_PREFIX_LEN);
|
|
402
|
-
var plain =
|
|
403
|
-
return C.VAULT_PREFIX +
|
|
403
|
+
var plain = bCrypto.decrypt(payload, oldKeys);
|
|
404
|
+
return C.VAULT_PREFIX + bCrypto.encrypt(plain, newKeys);
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
// Walk a JSON-decoded value, re-sealing every vault-prefixed string.
|
|
@@ -437,8 +438,11 @@ function _walkAndReSeal(node, oldKeys, newKeys) {
|
|
|
437
438
|
function _runStmt(db, sql) { db.prepare(sql).run(); }
|
|
438
439
|
|
|
439
440
|
function _rotateColumn(db, table, column, oldKeys, newKeys, batchSize, progress) {
|
|
440
|
-
|
|
441
|
-
|
|
441
|
+
// Identifiers reach SQL through safeSql.quoteIdentifier — runs
|
|
442
|
+
// validateIdentifier (rejects bad shape / reserved words /
|
|
443
|
+
// sqlite_-prefix) + emits the dialect-correct quoted form.
|
|
444
|
+
var qt = safeSql.quoteIdentifier(table, "sqlite");
|
|
445
|
+
var qc = safeSql.quoteIdentifier(column, "sqlite");
|
|
442
446
|
var total = db.prepare("SELECT COUNT(*) AS n FROM " + qt + " WHERE " + qc + " IS NOT NULL").get().n;
|
|
443
447
|
if (total === 0) return 0;
|
|
444
448
|
|
|
@@ -526,12 +530,12 @@ async function rotate(opts) {
|
|
|
526
530
|
throw new VaultRotateError("vault-rotate/no-keys",
|
|
527
531
|
"rotate: opts.oldKeys and opts.newKeys are required");
|
|
528
532
|
}
|
|
529
|
-
if (typeof opts.dataDir !== "string" || !
|
|
533
|
+
if (typeof opts.dataDir !== "string" || !nodeFs.existsSync(opts.dataDir)) {
|
|
530
534
|
throw new VaultRotateError("vault-rotate/no-datadir",
|
|
531
535
|
"rotate: opts.dataDir is required and must exist");
|
|
532
536
|
}
|
|
533
537
|
validateOpts.requireNonEmptyString(opts.stagingDir, "rotate: opts.stagingDir", VaultRotateError, "vault-rotate/no-staging");
|
|
534
|
-
if (
|
|
538
|
+
if (nodeFs.existsSync(opts.stagingDir)) {
|
|
535
539
|
throw new VaultRotateError("vault-rotate/staging-exists",
|
|
536
540
|
"rotate: stagingDir already exists: " + opts.stagingDir);
|
|
537
541
|
}
|
|
@@ -567,30 +571,30 @@ async function rotate(opts) {
|
|
|
567
571
|
_emit(progress, { phase: "copy_verbatim" });
|
|
568
572
|
for (var vf = 0; vf < paths.verbatimFiles.length; vf++) {
|
|
569
573
|
var entry = paths.verbatimFiles[vf];
|
|
570
|
-
var src =
|
|
571
|
-
if (!
|
|
574
|
+
var src = nodePath.join(dataDir, entry.relativePath);
|
|
575
|
+
if (!nodeFs.existsSync(src)) {
|
|
572
576
|
if (entry.required) {
|
|
573
577
|
throw new VaultRotateError("vault-rotate/missing-verbatim",
|
|
574
578
|
"rotate: required verbatim file missing: " + entry.relativePath);
|
|
575
579
|
}
|
|
576
580
|
continue;
|
|
577
581
|
}
|
|
578
|
-
var dest =
|
|
579
|
-
atomicFile.ensureDir(
|
|
580
|
-
|
|
582
|
+
var dest = nodePath.join(stagingDir, entry.relativePath);
|
|
583
|
+
atomicFile.ensureDir(nodePath.dirname(dest));
|
|
584
|
+
nodeFs.copyFileSync(src, dest);
|
|
581
585
|
}
|
|
582
586
|
for (var vd = 0; vd < paths.verbatimDirs.length; vd++) {
|
|
583
587
|
var dent = paths.verbatimDirs[vd];
|
|
584
|
-
var sdir =
|
|
585
|
-
if (!
|
|
588
|
+
var sdir = nodePath.join(dataDir, dent.relativePath);
|
|
589
|
+
if (!nodeFs.existsSync(sdir)) {
|
|
586
590
|
if (dent.required) {
|
|
587
591
|
throw new VaultRotateError("vault-rotate/missing-verbatim-dir",
|
|
588
592
|
"rotate: required verbatim dir missing: " + dent.relativePath);
|
|
589
593
|
}
|
|
590
594
|
continue;
|
|
591
595
|
}
|
|
592
|
-
if (
|
|
593
|
-
atomicFile.copyDirRecursive(sdir,
|
|
596
|
+
if (nodeFs.existsSync(sdir)) {
|
|
597
|
+
atomicFile.copyDirRecursive(sdir, nodePath.join(stagingDir, dent.relativePath));
|
|
594
598
|
}
|
|
595
599
|
}
|
|
596
600
|
|
|
@@ -599,59 +603,59 @@ async function rotate(opts) {
|
|
|
599
603
|
var keysJson = JSON.stringify(newKeys, null, 2);
|
|
600
604
|
if (mode === "wrapped") {
|
|
601
605
|
var sealed = await vaultWrap().wrap(keysJson, opts.newPassphrase);
|
|
602
|
-
|
|
606
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.vaultKeySealed), sealed, { mode: 0o600 });
|
|
603
607
|
} else {
|
|
604
|
-
|
|
608
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.vaultKeyPlain), keysJson, { mode: 0o600 });
|
|
605
609
|
}
|
|
606
610
|
|
|
607
611
|
// 3. re-seal db.key.enc + any operator-supplied additionalSealed files
|
|
608
612
|
_emit(progress, { phase: "reseal_files" });
|
|
609
|
-
var dbKeySealedPath =
|
|
613
|
+
var dbKeySealedPath = nodePath.join(dataDir, paths.dbKeySealed);
|
|
610
614
|
var dbKey = null;
|
|
611
|
-
if (
|
|
612
|
-
var sealedKey =
|
|
615
|
+
if (nodeFs.existsSync(dbKeySealedPath)) {
|
|
616
|
+
var sealedKey = nodeFs.readFileSync(dbKeySealedPath, "utf8").trim();
|
|
613
617
|
if (sealedKey.indexOf(C.VAULT_PREFIX) !== 0) {
|
|
614
618
|
throw new VaultRotateError("vault-rotate/bad-dbkey",
|
|
615
619
|
"rotate: db.key.enc does not start with the vault prefix");
|
|
616
620
|
}
|
|
617
|
-
var dbKeyB64 =
|
|
621
|
+
var dbKeyB64 = bCrypto.decrypt(sealedKey.substring(VAULT_PREFIX_LEN), oldKeys);
|
|
618
622
|
dbKey = Buffer.from(dbKeyB64, "base64");
|
|
619
|
-
var resealedKey = C.VAULT_PREFIX +
|
|
620
|
-
|
|
623
|
+
var resealedKey = C.VAULT_PREFIX + bCrypto.encrypt(dbKeyB64, newKeys);
|
|
624
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.dbKeySealed), resealedKey, { mode: 0o600 });
|
|
621
625
|
}
|
|
622
626
|
for (var as = 0; as < paths.additionalSealed.length; as++) {
|
|
623
627
|
var ase = paths.additionalSealed[as];
|
|
624
|
-
var asSrc =
|
|
625
|
-
if (!
|
|
628
|
+
var asSrc = nodePath.join(dataDir, ase.relativePath);
|
|
629
|
+
if (!nodeFs.existsSync(asSrc)) {
|
|
626
630
|
if (ase.required) {
|
|
627
631
|
throw new VaultRotateError("vault-rotate/missing-sealed",
|
|
628
632
|
"rotate: required sealed file missing: " + ase.relativePath);
|
|
629
633
|
}
|
|
630
634
|
continue;
|
|
631
635
|
}
|
|
632
|
-
var current =
|
|
636
|
+
var current = nodeFs.readFileSync(asSrc, "utf8").trim();
|
|
633
637
|
if (current.indexOf(C.VAULT_PREFIX) !== 0) {
|
|
634
638
|
throw new VaultRotateError("vault-rotate/bad-sealed",
|
|
635
639
|
"rotate: sealed file does not start with the vault prefix: " + ase.relativePath);
|
|
636
640
|
}
|
|
637
|
-
var asDestDir =
|
|
638
|
-
if (!
|
|
639
|
-
|
|
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),
|
|
640
644
|
_reSealValue(current, oldKeys, newKeys), { mode: 0o600 });
|
|
641
645
|
}
|
|
642
646
|
|
|
643
647
|
// 4. decrypt + rotate + re-encrypt db.enc
|
|
644
648
|
_emit(progress, { phase: "rotate_db" });
|
|
645
|
-
var encDbPath =
|
|
649
|
+
var encDbPath = nodePath.join(dataDir, paths.encryptedDb);
|
|
646
650
|
var tablesProcessed = 0;
|
|
647
651
|
var totalRowsProcessed = 0;
|
|
648
652
|
var verifyResult = null;
|
|
649
653
|
|
|
650
|
-
if (
|
|
651
|
-
var packed =
|
|
652
|
-
var plainBytes =
|
|
653
|
-
var tmpDbPath =
|
|
654
|
-
|
|
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 });
|
|
655
659
|
|
|
656
660
|
var db = new DatabaseSync(tmpDbPath);
|
|
657
661
|
try {
|
|
@@ -704,32 +708,32 @@ async function rotate(opts) {
|
|
|
704
708
|
// sidecar may be absent (depending on whether journal_mode produced
|
|
705
709
|
// one for this run); log at debug so the cleanup attempt isn't
|
|
706
710
|
// silently swallowed when something genuinely unexpected fails.
|
|
707
|
-
try {
|
|
708
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
709
|
-
try {
|
|
710
|
-
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 }); }
|
|
711
715
|
|
|
712
|
-
var rotatedBytes =
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
+
var rotatedBytes = nodeFs.readFileSync(tmpDbPath);
|
|
717
|
+
nodeFs.writeFileSync(nodePath.join(stagingDir, paths.encryptedDb),
|
|
718
|
+
bCrypto.encryptPacked(rotatedBytes, dbKey));
|
|
719
|
+
nodeFs.unlinkSync(tmpDbPath);
|
|
716
720
|
|
|
717
721
|
// Round-trip verify on the staged DB
|
|
718
722
|
_emit(progress, { phase: "verify" });
|
|
719
|
-
var verifyTmp =
|
|
720
|
-
|
|
721
|
-
|
|
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));
|
|
722
726
|
var vdb = new DatabaseSync(verifyTmp);
|
|
723
727
|
try {
|
|
724
728
|
verifyResult = verify({ keys: newKeys, db: vdb, oldKeys: oldKeys });
|
|
725
729
|
} finally {
|
|
726
730
|
vdb.close();
|
|
727
|
-
try {
|
|
728
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
729
|
-
try {
|
|
730
|
-
catch (e) { rotateLog.debug("cleanup-failed", { op: "
|
|
731
|
-
try {
|
|
732
|
-
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 }); }
|
|
733
737
|
}
|
|
734
738
|
if (!verifyResult.ok) {
|
|
735
739
|
throw new VaultRotateError("vault-rotate/verify-failed",
|
|
@@ -743,10 +747,10 @@ async function rotate(opts) {
|
|
|
743
747
|
// 5. fsync staging for durability before caller does the swap
|
|
744
748
|
_emit(progress, { phase: "fsync" });
|
|
745
749
|
function fsyncTree(dir) {
|
|
746
|
-
var entries =
|
|
750
|
+
var entries = nodeFs.readdirSync(dir);
|
|
747
751
|
for (var i = 0; i < entries.length; i++) {
|
|
748
|
-
var p =
|
|
749
|
-
var st =
|
|
752
|
+
var p = nodePath.join(dir, entries[i]);
|
|
753
|
+
var st = nodeFs.statSync(p);
|
|
750
754
|
if (st.isFile()) _fsyncFileByPath(p);
|
|
751
755
|
else if (st.isDirectory()) fsyncTree(p);
|
|
752
756
|
}
|