@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.
Files changed (119) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/lib/a2a.js +11 -11
  3. package/lib/acme.js +5 -5
  4. package/lib/ai-input.js +2 -2
  5. package/lib/api-key.js +4 -4
  6. package/lib/api-snapshot.js +10 -7
  7. package/lib/app-shutdown.js +2 -2
  8. package/lib/app.js +5 -5
  9. package/lib/archive.js +8 -8
  10. package/lib/argon2-builtin.js +2 -2
  11. package/lib/atomic-file.js +53 -53
  12. package/lib/audit-sign.js +8 -8
  13. package/lib/audit-tools.js +22 -22
  14. package/lib/audit.js +29 -17
  15. package/lib/auth/dpop.js +3 -3
  16. package/lib/auth/sd-jwt-vc.js +2 -2
  17. package/lib/backup/bundle.js +17 -17
  18. package/lib/backup/index.js +36 -36
  19. package/lib/budr.js +3 -3
  20. package/lib/bundler.js +20 -20
  21. package/lib/circuit-breaker.js +24 -9
  22. package/lib/cli.js +25 -26
  23. package/lib/cluster.js +2 -2
  24. package/lib/compliance-sanctions.js +2 -2
  25. package/lib/config-drift.js +15 -15
  26. package/lib/content-credentials.js +4 -4
  27. package/lib/credential-hash.js +3 -3
  28. package/lib/crypto.js +145 -0
  29. package/lib/daemon.js +19 -19
  30. package/lib/db-file-lifecycle.js +24 -24
  31. package/lib/db-schema.js +2 -2
  32. package/lib/db.js +35 -35
  33. package/lib/dev.js +10 -10
  34. package/lib/dr-runbook.js +5 -5
  35. package/lib/dsr.js +22 -15
  36. package/lib/dual-control.js +2 -2
  37. package/lib/external-db-migrate.js +2 -2
  38. package/lib/external-db.js +2 -2
  39. package/lib/fdx.js +2 -2
  40. package/lib/file-upload.js +30 -30
  41. package/lib/flag-providers.js +4 -4
  42. package/lib/gate-contract.js +5 -5
  43. package/lib/graphql-federation.js +4 -7
  44. package/lib/honeytoken.js +6 -6
  45. package/lib/http-client-cookie-jar.js +6 -6
  46. package/lib/http-client.js +18 -18
  47. package/lib/i18n.js +5 -5
  48. package/lib/inbox.js +21 -15
  49. package/lib/keychain.js +9 -9
  50. package/lib/legal-hold.js +2 -2
  51. package/lib/local-db-thin.js +9 -9
  52. package/lib/log-stream-local.js +17 -17
  53. package/lib/log-stream-syslog.js +2 -2
  54. package/lib/log-stream.js +3 -3
  55. package/lib/mail-bounce.js +2 -2
  56. package/lib/mail-mdn.js +2 -2
  57. package/lib/mail-srs.js +2 -2
  58. package/lib/mail.js +4 -4
  59. package/lib/mcp.js +2 -2
  60. package/lib/metrics.js +249 -2
  61. package/lib/middleware/api-encrypt.js +16 -16
  62. package/lib/middleware/body-parser.js +16 -16
  63. package/lib/middleware/compression.js +3 -3
  64. package/lib/middleware/csp-nonce.js +4 -4
  65. package/lib/middleware/health.js +7 -7
  66. package/lib/middleware/idempotency-key.js +250 -0
  67. package/lib/migrations.js +3 -3
  68. package/lib/mtls-ca.js +26 -26
  69. package/lib/mtls-engine-default.js +5 -5
  70. package/lib/network-dns.js +2 -2
  71. package/lib/network-nts.js +2 -2
  72. package/lib/network-proxy.js +3 -3
  73. package/lib/network-smtp-policy.js +2 -2
  74. package/lib/network-tls.js +17 -17
  75. package/lib/network.js +13 -13
  76. package/lib/notify.js +3 -3
  77. package/lib/object-store/gcs-bucket-ops.js +2 -2
  78. package/lib/object-store/gcs.js +5 -5
  79. package/lib/object-store/index.js +6 -6
  80. package/lib/object-store/local.js +19 -19
  81. package/lib/object-store/sigv4.js +3 -3
  82. package/lib/observability-tracer.js +4 -4
  83. package/lib/otel-export.js +3 -3
  84. package/lib/pagination.js +5 -5
  85. package/lib/parsers/safe-xml.js +3 -3
  86. package/lib/pqc-agent.js +116 -26
  87. package/lib/pqc-gate.js +5 -5
  88. package/lib/pubsub-redis.js +2 -2
  89. package/lib/queue-local.js +3 -3
  90. package/lib/queue.js +2 -2
  91. package/lib/redis-client.js +4 -4
  92. package/lib/restore-bundle.js +18 -18
  93. package/lib/restore-rollback.js +34 -34
  94. package/lib/restore.js +16 -16
  95. package/lib/retry.js +50 -0
  96. package/lib/router.js +13 -13
  97. package/lib/sandbox.js +8 -8
  98. package/lib/sec-cyber.js +3 -3
  99. package/lib/security-assert.js +2 -2
  100. package/lib/seeders.js +4 -4
  101. package/lib/self-update-standalone-verifier.js +280 -0
  102. package/lib/self-update.js +32 -26
  103. package/lib/session-device-binding.js +2 -2
  104. package/lib/static.js +22 -22
  105. package/lib/template.js +19 -19
  106. package/lib/testing.js +7 -7
  107. package/lib/tls-exporter.js +5 -5
  108. package/lib/tracing.js +3 -3
  109. package/lib/vault/index.js +11 -11
  110. package/lib/vault/passphrase-ops.js +37 -37
  111. package/lib/vault/passphrase-source.js +2 -2
  112. package/lib/vault/rotate.js +70 -66
  113. package/lib/vault/seal-pem-file.js +26 -26
  114. package/lib/watcher.js +23 -23
  115. package/lib/webhook.js +10 -10
  116. package/lib/worker-pool.js +6 -6
  117. package/lib/ws-client.js +4 -4
  118. package/package.json +1 -1
  119. package/sbom.cdx.json +6 -6
@@ -35,8 +35,8 @@
35
35
  * with the original file untouched.
36
36
  */
37
37
 
38
- var fs = require("fs");
39
- var path = require("path");
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: path.join(dataDir, PLAINTEXT_NAME),
52
- plaintextTmp: path.join(dataDir, PLAINTEXT_NAME + ".tmp"),
53
- sealed: path.join(dataDir, SEALED_NAME),
54
- sealedTmp: path.join(dataDir, SEALED_NAME + ".tmp"),
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 (!fs.existsSync(opts.dataDir)) {
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 = fs.openSync(p, "r+");
84
- try { atomicFile.fsync(fd); } finally { fs.closeSync(fd); }
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 (!fs.existsSync(p.plaintext)) {
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 (fs.existsSync(p.sealed)) {
96
+ if (nodeFs.existsSync(p.sealed)) {
97
97
  return { ok: false, reason: SEALED_NAME + " already exists; refusing to overwrite" };
98
98
  }
99
- if (fs.existsSync(p.sealedTmp)) {
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 (!fs.existsSync(p.sealed)) {
108
+ if (!nodeFs.existsSync(p.sealed)) {
109
109
  return { ok: false, reason: SEALED_NAME + " does not exist — nothing to unseal" };
110
110
  }
111
- if (fs.existsSync(p.plaintext)) {
111
+ if (nodeFs.existsSync(p.plaintext)) {
112
112
  return { ok: false, reason: "plaintext " + PLAINTEXT_NAME + " already exists; refusing to overwrite" };
113
113
  }
114
- if (fs.existsSync(p.plaintextTmp)) {
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 (!fs.existsSync(p.sealed)) {
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 (fs.existsSync(p.sealedTmp)) {
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 = fs.readFileSync(p.plaintext);
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
- fs.writeFileSync(p.sealedTmp, sealedBytes, { mode: 0o600 });
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 = fs.readFileSync(p.sealedTmp);
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 { fs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
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 { fs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
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
- fs.renameSync(p.sealedTmp, p.sealed);
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
- fs.unlinkSync(p.plaintext);
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 = fs.readFileSync(p.sealed);
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
- fs.writeFileSync(p.plaintextTmp, plainBytes, { mode: 0o600 });
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 = fs.readFileSync(p.plaintextTmp);
213
+ var verifyBytes = nodeFs.readFileSync(p.plaintextTmp);
214
214
  if (Buffer.compare(verifyBytes, plainBytes) !== 0) {
215
- try { fs.unlinkSync(p.plaintextTmp); } catch (_e) { /* cleanup */ }
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
- fs.renameSync(p.plaintextTmp, p.plaintext);
222
+ nodeFs.renameSync(p.plaintextTmp, p.plaintext);
223
223
  atomicFile.fsyncDir(opts.dataDir);
224
224
 
225
225
  // Step 4: delete sealed file
226
- fs.unlinkSync(p.sealed);
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 = fs.readFileSync(p.sealed);
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
- fs.writeFileSync(p.sealedTmp, newSealedBytes, { mode: 0o600 });
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 = fs.readFileSync(p.sealedTmp);
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 { fs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
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 { fs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
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 { fs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
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
- fs.renameSync(p.sealedTmp, p.sealed);
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 fs = require("fs");
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 = fs.readFileSync(filePath);
86
+ raw = nodeFs.readFileSync(filePath);
87
87
  } catch (e) {
88
88
  throw new Error("failed to read " + envVars.file + " (" + filePath + "): " + e.code);
89
89
  }
@@ -48,17 +48,18 @@
48
48
  * sampler skips them.
49
49
  */
50
50
 
51
- var fs = require("fs");
52
- var path = require("path");
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 cryptoLib = require("../crypto");
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 nb = require("../numeric-bounds");
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
- nb.requirePositiveFiniteIntIfPresent(opts.driftSampleLimit,
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
- nb.requirePositiveFiniteIntIfPresent(opts.sampleMin,
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
- nb.shape(opts.samplePercent));
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 { cryptoLib.decrypt(payload, keys); }
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
- cryptoLib.decrypt(payload, oldKeys);
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 = fs.openSync(p, "r+");
394
- try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
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 = cryptoLib.decrypt(payload, oldKeys);
403
- return C.VAULT_PREFIX + cryptoLib.encrypt(plain, newKeys);
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
- var qt = '"' + table.replace(/"/g, '""') + '"';
441
- var qc = '"' + column.replace(/"/g, '""') + '"';
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" || !fs.existsSync(opts.dataDir)) {
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 (fs.existsSync(opts.stagingDir)) {
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 = path.join(dataDir, entry.relativePath);
571
- if (!fs.existsSync(src)) {
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 = path.join(stagingDir, entry.relativePath);
579
- atomicFile.ensureDir(path.dirname(dest));
580
- fs.copyFileSync(src, dest);
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 = path.join(dataDir, dent.relativePath);
585
- if (!fs.existsSync(sdir)) {
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 (fs.existsSync(sdir)) {
593
- atomicFile.copyDirRecursive(sdir, path.join(stagingDir, dent.relativePath));
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
- fs.writeFileSync(path.join(stagingDir, paths.vaultKeySealed), sealed, { mode: 0o600 });
606
+ nodeFs.writeFileSync(nodePath.join(stagingDir, paths.vaultKeySealed), sealed, { mode: 0o600 });
603
607
  } else {
604
- fs.writeFileSync(path.join(stagingDir, paths.vaultKeyPlain), keysJson, { mode: 0o600 });
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 = path.join(dataDir, paths.dbKeySealed);
613
+ var dbKeySealedPath = nodePath.join(dataDir, paths.dbKeySealed);
610
614
  var dbKey = null;
611
- if (fs.existsSync(dbKeySealedPath)) {
612
- var sealedKey = fs.readFileSync(dbKeySealedPath, "utf8").trim();
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 = cryptoLib.decrypt(sealedKey.substring(VAULT_PREFIX_LEN), oldKeys);
621
+ var dbKeyB64 = bCrypto.decrypt(sealedKey.substring(VAULT_PREFIX_LEN), oldKeys);
618
622
  dbKey = Buffer.from(dbKeyB64, "base64");
619
- var resealedKey = C.VAULT_PREFIX + cryptoLib.encrypt(dbKeyB64, newKeys);
620
- fs.writeFileSync(path.join(stagingDir, paths.dbKeySealed), resealedKey, { mode: 0o600 });
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 = path.join(dataDir, ase.relativePath);
625
- if (!fs.existsSync(asSrc)) {
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 = fs.readFileSync(asSrc, "utf8").trim();
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 = path.join(stagingDir, path.dirname(ase.relativePath));
638
- if (!fs.existsSync(asDestDir)) atomicFile.ensureDir(asDestDir);
639
- fs.writeFileSync(path.join(stagingDir, ase.relativePath),
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 = path.join(dataDir, paths.encryptedDb);
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 (fs.existsSync(encDbPath) && dbKey) {
651
- var packed = fs.readFileSync(encDbPath);
652
- var plainBytes = cryptoLib.decryptPacked(packed, dbKey);
653
- var tmpDbPath = path.join(stagingDir, "_blamejs_rotate.tmp.db");
654
- fs.writeFileSync(tmpDbPath, plainBytes, { mode: 0o600 });
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 { fs.unlinkSync(tmpDbPath + "-wal"); }
708
- catch (e) { rotateLog.debug("cleanup-failed", { op: "fs.unlinkSync", path: tmpDbPath + "-wal", error: e.message }); }
709
- try { fs.unlinkSync(tmpDbPath + "-shm"); }
710
- catch (e) { rotateLog.debug("cleanup-failed", { op: "fs.unlinkSync", path: tmpDbPath + "-shm", error: e.message }); }
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 = fs.readFileSync(tmpDbPath);
713
- fs.writeFileSync(path.join(stagingDir, paths.encryptedDb),
714
- cryptoLib.encryptPacked(rotatedBytes, dbKey));
715
- fs.unlinkSync(tmpDbPath);
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 = path.join(stagingDir, "_blamejs_verify.tmp.db");
720
- fs.writeFileSync(verifyTmp,
721
- cryptoLib.decryptPacked(fs.readFileSync(path.join(stagingDir, paths.encryptedDb)), dbKey));
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 { fs.unlinkSync(verifyTmp); }
728
- catch (e) { rotateLog.debug("cleanup-failed", { op: "fs.unlinkSync", path: verifyTmp, error: e.message }); }
729
- try { fs.unlinkSync(verifyTmp + "-wal"); }
730
- catch (e) { rotateLog.debug("cleanup-failed", { op: "fs.unlinkSync", path: verifyTmp + "-wal", error: e.message }); }
731
- try { fs.unlinkSync(verifyTmp + "-shm"); }
732
- catch (e) { rotateLog.debug("cleanup-failed", { op: "fs.unlinkSync", path: verifyTmp + "-shm", error: e.message }); }
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 = fs.readdirSync(dir);
750
+ var entries = nodeFs.readdirSync(dir);
747
751
  for (var i = 0; i < entries.length; i++) {
748
- var p = path.join(dir, entries[i]);
749
- var st = fs.statSync(p);
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
  }