@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
@@ -44,6 +44,11 @@ var nodeCrypto = require("node:crypto");
44
44
  var lazyRequire = require("../lazy-require");
45
45
  var numericBounds = require("../numeric-bounds");
46
46
  var safeBuffer = require("../safe-buffer");
47
+ var safeJson = require("../safe-json");
48
+ var safeSql = require("../safe-sql");
49
+ var bCrypto = require("../crypto");
50
+ var cryptoField = require("../crypto-field");
51
+ var vault = require("../vault");
47
52
  var { defineClass } = require("../framework-error");
48
53
 
49
54
  var audit = lazyRequire(function () { return require("../audit"); });
@@ -124,6 +129,250 @@ function memoryStore(opts) {
124
129
  };
125
130
  }
126
131
 
132
+ // Operator-supplied table name is validated via b.safeSql.validateIdentifier
133
+ // — single source of truth for the framework's SQL-identifier shape
134
+ // (ASCII identifier chars only, 63-char cap, no reserved words). Direct
135
+ // interpolation is safe once the validator throws on bad input.
136
+
137
+ /**
138
+ * @primitive b.middleware.idempotencyKey.dbStore
139
+ * @signature b.middleware.idempotencyKey.dbStore(opts)
140
+ * @since 0.9.14
141
+ * @status stable
142
+ * @related b.middleware.idempotencyKey, b.middleware.idempotencyKey.memoryStore, b.db, b.cryptoField
143
+ *
144
+ * Persistent-backed store for `idempotencyKey` middleware. Implements
145
+ * the same three-method interface as `memoryStore` (`get` / `set` /
146
+ * `delete`) but stores records in a SQLite-shaped database — the
147
+ * framework's internal `b.db`, an operator-supplied better-sqlite3
148
+ * instance, or any object exposing `prepare(sql) → { run, get, all }`.
149
+ *
150
+ * Use `dbStore` instead of `memoryStore` when:
151
+ *
152
+ * - multiple processes share the request-handling fleet (forks
153
+ * behind a load balancer, multi-instance K8s deployment) and a
154
+ * retry can land on a different process than the original;
155
+ * - the daemon may restart between the original request and the
156
+ * retry (graceful rolling deploy, OOM kill, planned reboot) —
157
+ * `memoryStore` is volatile, `dbStore` survives the restart;
158
+ * - audit / compliance review needs to walk historic
159
+ * idempotency cache decisions queryable with
160
+ * `SELECT k, status_code, expires_at FROM <tableName>` —
161
+ * non-sealed columns are forensic-queryable without unsealing.
162
+ *
163
+ * **Defense-in-depth defaults (since 0.9.15) — both can be opted out:**
164
+ *
165
+ * - `hashKeys: true` — operator-supplied keys are sha3-512
166
+ * namespace-hashed via `b.crypto.namespaceHash("idempotency-key",
167
+ * key)` before insert/lookup. The `k` column carries the hash, not
168
+ * the raw key. Operator keys often carry PII (order numbers,
169
+ * emails, vendor prefixes); the DB never sees them.
170
+ * - `seal: true` — `headers` and `body` columns are sealed via
171
+ * `b.cryptoField.sealRow` (vault-managed key, AEAD envelope) so a
172
+ * DB dump leaks neither cached response bodies nor headers.
173
+ * Requires `b.vault.init(...)` to have run; falls back to plain-
174
+ * text with a one-shot audit warning when vault isn't ready, so
175
+ * test-fixture / boot-script callers still work.
176
+ *
177
+ * Lazily-expired: `get(key)` returns `null` for any row whose
178
+ * `expires_at` has passed. The cleanup is scoped by the observed
179
+ * `expires_at` so a concurrent upsert from a sibling process isn't
180
+ * clobbered.
181
+ *
182
+ * **Schema (v0.9.15, split columns):**
183
+ *
184
+ * ```
185
+ * k TEXT PRIMARY KEY -- hashed key when hashKeys=true
186
+ * fingerprint TEXT NOT NULL -- request method+path+body digest
187
+ * status_code INTEGER NOT NULL -- forensic-queryable
188
+ * headers TEXT NOT NULL -- JSON, sealed when seal=true
189
+ * body TEXT NOT NULL -- base64, sealed when seal=true
190
+ * expires_at INTEGER NOT NULL
191
+ * ```
192
+ *
193
+ * **Migration note**: v0.9.14 used a single `v` JSON envelope column.
194
+ * Operators with a v0.9.14 table must `DROP TABLE <tableName>;` (or
195
+ * pick a fresh `tableName`) before upgrading — `CREATE TABLE IF NOT
196
+ * EXISTS` won't migrate column layout. Pre-v1 the framework breaks
197
+ * across patch versions for security correctness.
198
+ *
199
+ * @opts
200
+ * db: object, // required — sqlite-shaped: { prepare(sql) → { run, get, all } }
201
+ * tableName?: string, // default "blamejs_idempotency_keys"; validated via b.safeSql.validateIdentifier
202
+ * init?: boolean, // default true — run CREATE TABLE IF NOT EXISTS at construction
203
+ * hashKeys?: boolean, // default true — store sha3-512 namespace-hash of the key, not the raw key
204
+ * seal?: boolean, // default true — seal headers + body via b.cryptoField when vault is ready
205
+ *
206
+ * @example
207
+ * // single-process daemon, framework's internal sqlite, both defaults on:
208
+ * var b = require("blamejs");
209
+ * await b.vault.init({ dataDir: "/var/lib/myapp" });
210
+ * await b.db.init({ dataDir: "/var/lib/myapp", schema: [] });
211
+ * var store = b.middleware.idempotencyKey.dbStore({ db: b.db });
212
+ * var mw = b.middleware.idempotencyKey({
213
+ * store: store,
214
+ * ttlMs: b.constants.TIME.hours(24),
215
+ * });
216
+ * app.use(mw);
217
+ */
218
+ function dbStore(opts) {
219
+ opts = opts || {};
220
+ if (!opts.db || typeof opts.db !== "object" || typeof opts.db.prepare !== "function") {
221
+ throw new IdempotencyError("idempotency/bad-db",
222
+ "dbStore: opts.db must be a sqlite-shaped database with a `prepare(sql)` method", true);
223
+ }
224
+ var tableNameRaw = opts.tableName !== undefined ? opts.tableName : "blamejs_idempotency_keys";
225
+ // Quote-and-validate via safeSql.quoteIdentifier — runs
226
+ // validateIdentifier internally + emits the dialect-correct quoted
227
+ // form. Identifier always reaches SQL through the quoted form.
228
+ var qTable;
229
+ try { qTable = safeSql.quoteIdentifier(tableNameRaw, "sqlite"); }
230
+ catch (sqlErr) {
231
+ throw new IdempotencyError("idempotency/bad-table-name",
232
+ "dbStore: opts.tableName is not a valid SQL identifier: " +
233
+ (sqlErr && sqlErr.message ? sqlErr.message : String(sqlErr)), true);
234
+ }
235
+ var qIndex = safeSql.quoteIdentifier(tableNameRaw + "_expires_idx", "sqlite");
236
+ var doInit = opts.init !== false;
237
+ var hashKeys = opts.hashKeys !== false;
238
+ var sealReq = opts.seal !== false;
239
+ var db = opts.db;
240
+
241
+ // Probe vault readiness with a sentinel seal. If vault.init() hasn't
242
+ // run (test fixture / boot-script / operator simply hasn't wired the
243
+ // posture yet) sealing falls back to plaintext for the lifetime of
244
+ // this dbStore instance and a single audit warning emits so the
245
+ // posture gap is visible in the chain.
246
+ var sealEnabled = false;
247
+ if (sealReq) {
248
+ try {
249
+ vault.seal("__idempotency_seal_probe__");
250
+ sealEnabled = true;
251
+ } catch (_vaultErr) {
252
+ _emitAudit("idempotency.seal_skipped_no_vault",
253
+ { tableName: tableNameRaw,
254
+ reason: "vault.init() has not run; sealing falls back to plaintext" },
255
+ "warning");
256
+ }
257
+ }
258
+
259
+ // Register the table with cryptoField. registerTable is idempotent
260
+ // — subsequent dbStore() calls with the same tableName re-declare
261
+ // the same sealedFields and no-op.
262
+ if (sealEnabled) {
263
+ cryptoField.registerTable(tableNameRaw, {
264
+ sealedFields: ["headers", "body"],
265
+ });
266
+ }
267
+
268
+ if (doInit) {
269
+ db.prepare("CREATE TABLE IF NOT EXISTS " + qTable + " (" +
270
+ "k TEXT PRIMARY KEY, " +
271
+ "fingerprint TEXT NOT NULL, " +
272
+ "status_code INTEGER NOT NULL, " +
273
+ "headers TEXT NOT NULL, " +
274
+ "body TEXT NOT NULL, " +
275
+ "expires_at INTEGER NOT NULL)").run();
276
+ db.prepare("CREATE INDEX IF NOT EXISTS " + qIndex + " ON " +
277
+ qTable + "(expires_at)").run();
278
+ }
279
+
280
+ // Prepared statements. status_code + expires_at stay non-sealed
281
+ // so audit/forensic SELECTs don't have to unseal-everything.
282
+ var stmtGet = db.prepare(
283
+ "SELECT fingerprint, status_code, headers, body, expires_at FROM " +
284
+ qTable + " WHERE k = ?");
285
+ var stmtUpsert = db.prepare(
286
+ "INSERT INTO " + qTable +
287
+ "(k, fingerprint, status_code, headers, body, expires_at) " +
288
+ "VALUES (?, ?, ?, ?, ?, ?) " +
289
+ "ON CONFLICT(k) DO UPDATE SET " +
290
+ " fingerprint = excluded.fingerprint, " +
291
+ " status_code = excluded.status_code, " +
292
+ " headers = excluded.headers, " +
293
+ " body = excluded.body, " +
294
+ " expires_at = excluded.expires_at");
295
+ var stmtDeleteStale = db.prepare("DELETE FROM " + qTable +
296
+ " WHERE k = ? AND expires_at <= ?");
297
+ var stmtDelete = db.prepare("DELETE FROM " + qTable + " WHERE k = ?");
298
+
299
+ function _k(rawKey) {
300
+ if (!hashKeys) return rawKey;
301
+ return bCrypto.namespaceHash("idempotency-key", rawKey);
302
+ }
303
+
304
+ return {
305
+ get: function (rawKey) {
306
+ var row = stmtGet.get(_k(rawKey));
307
+ if (!row) return null;
308
+ if (row.expires_at < Date.now()) {
309
+ stmtDeleteStale.run(_k(rawKey), row.expires_at);
310
+ return null;
311
+ }
312
+ var liveRow = row;
313
+ if (sealEnabled) {
314
+ try { liveRow = cryptoField.unsealRow(tableNameRaw, row); }
315
+ catch (_unsealErr) {
316
+ // Decryption failed (key rotation gap / corrupt envelope).
317
+ // Treat as miss + drop the row so the handler runs fresh
318
+ // and we capture a re-sealable replacement.
319
+ stmtDeleteStale.run(_k(rawKey), row.expires_at);
320
+ return null;
321
+ }
322
+ }
323
+ var headersObj;
324
+ try {
325
+ headersObj = safeJson.parse(liveRow.headers, { maxBytes: 4 * 1024 * 1024 }); // allow:raw-byte-literal — 4 MiB headers ceiling
326
+ } catch (_jsonErr) {
327
+ // Parse failure has two distinct causes:
328
+ // 1. Genuine corruption (truncated row, encoding mishap) — drop.
329
+ // 2. The row was sealed by a sibling process (vault: prefix
330
+ // present) but THIS process has sealEnabled=false (vault
331
+ // not initialized OR opts.seal=false). The row is valid
332
+ // cross-process state we just can't read locally;
333
+ // DELETING it would clobber another process's cache and
334
+ // turn a hit into a miss with potential side-effect re-
335
+ // execution. Treat as miss + LEAVE the row in place.
336
+ // Per Codex P1 on PR #45.
337
+ var lookedSealed = typeof liveRow.headers === "string" &&
338
+ liveRow.headers.indexOf("vault:") === 0;
339
+ if (!lookedSealed) {
340
+ stmtDeleteStale.run(_k(rawKey), row.expires_at);
341
+ }
342
+ return null;
343
+ }
344
+ return {
345
+ fingerprint: liveRow.fingerprint,
346
+ statusCode: liveRow.status_code,
347
+ headers: headersObj,
348
+ body: liveRow.body,
349
+ };
350
+ },
351
+ set: function (rawKey, value, ttlMs) {
352
+ var rowOut = {
353
+ k: _k(rawKey),
354
+ fingerprint: value.fingerprint,
355
+ status_code: value.statusCode,
356
+ headers: JSON.stringify(value.headers || {}),
357
+ body: value.body || "",
358
+ expires_at: Date.now() + ttlMs,
359
+ };
360
+ if (sealEnabled) {
361
+ rowOut = cryptoField.sealRow(tableNameRaw, rowOut);
362
+ }
363
+ stmtUpsert.run(
364
+ rowOut.k, rowOut.fingerprint, rowOut.status_code,
365
+ rowOut.headers, rowOut.body, rowOut.expires_at);
366
+ },
367
+ delete: function (rawKey) {
368
+ stmtDelete.run(_k(rawKey));
369
+ },
370
+ _tableName: tableNameRaw,
371
+ _hashKeys: hashKeys,
372
+ _sealEnabled: sealEnabled,
373
+ };
374
+ }
375
+
127
376
  function _validateStore(store, where) {
128
377
  if (!store || typeof store !== "object") {
129
378
  throw new IdempotencyError("idempotency/bad-store",
@@ -420,5 +669,6 @@ function _redactKey(key) {
420
669
  module.exports = create;
421
670
  module.exports.create = create;
422
671
  module.exports.memoryStore = memoryStore;
672
+ module.exports.dbStore = dbStore;
423
673
  module.exports.DEFAULT_METHODS = DEFAULT_METHODS;
424
674
  module.exports.IdempotencyError = IdempotencyError;
package/lib/migrations.js CHANGED
@@ -38,7 +38,7 @@
38
38
  * down() succeeds.
39
39
  */
40
40
 
41
- var path = require("path");
41
+ var nodePath = require("path");
42
42
  var atomicFile = require("./atomic-file");
43
43
  var dbSchema = require("./db-schema");
44
44
  var lazyRequire = require("./lazy-require");
@@ -200,7 +200,7 @@ function _acquireLock(db, opts) {
200
200
  function _releaseLock(db, holder) {
201
201
  // Only release our own lock — a process whose deploy was killed
202
202
  // shouldn't have its lock cleared by an unrelated next deploy unless
203
- // the operator explicitly used the staleAfterMs path.
203
+ // the operator explicitly used the staleAfterMs nodePath.
204
204
  try {
205
205
  db.prepare(
206
206
  "DELETE FROM " + Q_LOCK_TABLE + " WHERE scope = 'lock' AND lockedBy = ?"
@@ -234,7 +234,7 @@ function _resolveDb(opts) {
234
234
  }
235
235
 
236
236
  function _loadMigration(file, dir) {
237
- var fullPath = path.join(dir, file);
237
+ var fullPath = nodePath.join(dir, file);
238
238
  // Drop the require cache for this path before loading so a test that
239
239
  // changes a migration file between calls picks up the new content.
240
240
  // Production deployments would always restart the process, but this
package/lib/mtls-ca.js CHANGED
@@ -52,8 +52,8 @@
52
52
  * Mutual TLS Certificate Authority — internal CA cert issuance, mTLS gate setup, fingerprint pinning.
53
53
  */
54
54
 
55
- var fs = require("fs");
56
- var path = require("path");
55
+ var nodeFs = require("fs");
56
+ var nodePath = require("path");
57
57
  var nodeCrypto = require("node:crypto");
58
58
  var atomicFile = require("./atomic-file");
59
59
  var C = require("./constants");
@@ -101,10 +101,10 @@ var VALID_SEAL_MODES = { required: 1, disabled: 1 };
101
101
  // through unchanged. The pre-v0.8.58 shape always joined under
102
102
  // dataDir, which silently overrode an operator-supplied absolute
103
103
  // path (e.g. `MTLS_CA_KEY=/etc/ssl/ca.key` → `<dataDir>/etc/ssl/ca.key`).
104
- // Standard Node `path.join` semantics already preserve absolute
104
+ // Standard Node `nodePath.join` semantics already preserve absolute
105
105
  // arguments — the always-join was an oversight, not by design.
106
106
  function _absoluteOrUnderDataDir(dataDir, p) {
107
- return path.isAbsolute(p) ? p : path.join(dataDir, p);
107
+ return nodePath.isAbsolute(p) ? p : nodePath.join(dataDir, p);
108
108
  }
109
109
 
110
110
  function _resolvePaths(dataDir, paths) {
@@ -202,8 +202,8 @@ function create(opts) {
202
202
  // the first initCA() / generateClientCert() call fails with ENOENT
203
203
  // on `ca.key.tmp` because the atomic-file write expects the parent
204
204
  // dir to exist.
205
- if (!fs.existsSync(opts.dataDir)) {
206
- fs.mkdirSync(opts.dataDir, { recursive: true, mode: 0o700 });
205
+ if (!nodeFs.existsSync(opts.dataDir)) {
206
+ nodeFs.mkdirSync(opts.dataDir, { recursive: true, mode: 0o700 });
207
207
  }
208
208
  var paths = _resolvePaths(opts.dataDir, opts.paths);
209
209
  var vault = opts.vault || null;
@@ -228,10 +228,10 @@ function create(opts) {
228
228
  }
229
229
 
230
230
  function keyExists() {
231
- return fs.existsSync(paths.caKey) || fs.existsSync(paths.caKeySealed);
231
+ return nodeFs.existsSync(paths.caKey) || nodeFs.existsSync(paths.caKeySealed);
232
232
  }
233
233
  function exists() {
234
- return keyExists() && fs.existsSync(paths.caCert);
234
+ return keyExists() && nodeFs.existsSync(paths.caCert);
235
235
  }
236
236
 
237
237
  function status() {
@@ -243,7 +243,7 @@ function create(opts) {
243
243
  current: generation,
244
244
  };
245
245
  }
246
- var pem = fs.readFileSync(paths.caCert);
246
+ var pem = nodeFs.readFileSync(paths.caCert);
247
247
  var gen = parseGeneration(pem);
248
248
  return {
249
249
  exists: true,
@@ -257,8 +257,8 @@ function create(opts) {
257
257
  // caKeySealedMode dispatch. Returns Buffer of PEM bytes, or throws
258
258
  // with a precise reason when the mode rejects the on-disk form.
259
259
  function loadKey() {
260
- var hasPlain = fs.existsSync(paths.caKey);
261
- var hasSealed = fs.existsSync(paths.caKeySealed);
260
+ var hasPlain = nodeFs.existsSync(paths.caKey);
261
+ var hasSealed = nodeFs.existsSync(paths.caKeySealed);
262
262
  if (!hasPlain && !hasSealed) {
263
263
  throw new MtlsCaError("mtls-ca/missing-key",
264
264
  "no CA key on disk at " + paths.caKey + " or " + paths.caKeySealed);
@@ -269,7 +269,7 @@ function create(opts) {
269
269
  "CA_KEY_SEALED='required' but " + paths.caKeySealed + " does not exist");
270
270
  }
271
271
  _requireVault("sealed CA key load");
272
- var sealedBytes = fs.readFileSync(paths.caKeySealed, "utf8").trim();
272
+ var sealedBytes = nodeFs.readFileSync(paths.caKeySealed, "utf8").trim();
273
273
  var pem = vault.unseal(sealedBytes);
274
274
  if (!pem) {
275
275
  throw new MtlsCaError("mtls-ca/unseal-failed",
@@ -282,15 +282,15 @@ function create(opts) {
282
282
  throw new MtlsCaError("mtls-ca/plain-required",
283
283
  "caKeySealedMode='disabled' but " + paths.caKey + " does not exist");
284
284
  }
285
- return fs.readFileSync(paths.caKey);
285
+ return nodeFs.readFileSync(paths.caKey);
286
286
  }
287
287
 
288
288
  function loadCert() {
289
- if (!fs.existsSync(paths.caCert)) {
289
+ if (!nodeFs.existsSync(paths.caCert)) {
290
290
  throw new MtlsCaError("mtls-ca/missing-cert",
291
291
  "no CA cert on disk at " + paths.caCert);
292
292
  }
293
- return fs.readFileSync(paths.caCert);
293
+ return nodeFs.readFileSync(paths.caCert);
294
294
  }
295
295
 
296
296
  // Atomic commit: write .tmp + atomic rename for both key and cert.
@@ -311,22 +311,22 @@ function create(opts) {
311
311
  try {
312
312
  if (sealed) {
313
313
  _requireVault("sealed CA key commit");
314
- fs.writeFileSync(keyTmp, vault.seal(opts2.caKeyPem), { mode: 0o600 });
314
+ nodeFs.writeFileSync(keyTmp, vault.seal(opts2.caKeyPem), { mode: 0o600 });
315
315
  } else {
316
- fs.writeFileSync(keyTmp, opts2.caKeyPem, { mode: 0o600 });
316
+ nodeFs.writeFileSync(keyTmp, opts2.caKeyPem, { mode: 0o600 });
317
317
  }
318
- fs.writeFileSync(certTmp, opts2.caCertPem, { mode: 0o644 });
319
- fs.renameSync(keyTmp, keyDest);
320
- fs.renameSync(certTmp, paths.caCert);
318
+ nodeFs.writeFileSync(certTmp, opts2.caCertPem, { mode: 0o644 });
319
+ nodeFs.renameSync(keyTmp, keyDest);
320
+ nodeFs.renameSync(certTmp, paths.caCert);
321
321
  } catch (e) {
322
322
  // Best-effort cleanup of half-written tmp files; the original
323
323
  // commit error is what we re-raise. Log cleanup failures at debug
324
324
  // so a genuinely-broken filesystem state surfaces in operator logs
325
325
  // rather than getting silently swallowed.
326
- try { if (fs.existsSync(keyTmp)) fs.unlinkSync(keyTmp); }
327
- catch (cleanupErr) { caLog.debug("cleanup-failed", { op: "fs.unlinkSync", path: keyTmp, error: cleanupErr.message }); }
328
- try { if (fs.existsSync(certTmp)) fs.unlinkSync(certTmp); }
329
- catch (cleanupErr) { caLog.debug("cleanup-failed", { op: "fs.unlinkSync", path: certTmp, error: cleanupErr.message }); }
326
+ try { if (nodeFs.existsSync(keyTmp)) nodeFs.unlinkSync(keyTmp); }
327
+ catch (cleanupErr) { caLog.debug("cleanup-failed", { op: "nodeFs.unlinkSync", path: keyTmp, error: cleanupErr.message }); }
328
+ try { if (nodeFs.existsSync(certTmp)) nodeFs.unlinkSync(certTmp); }
329
+ catch (cleanupErr) { caLog.debug("cleanup-failed", { op: "nodeFs.unlinkSync", path: certTmp, error: cleanupErr.message }); }
330
330
  throw new MtlsCaError("mtls-ca/commit-failed",
331
331
  "atomic CA commit failed: " + ((e && e.message) || String(e)));
332
332
  }
@@ -381,13 +381,13 @@ function create(opts) {
381
381
  // ---- Revocation registry + CRL ----
382
382
 
383
383
  function _loadRevocations() {
384
- if (!fs.existsSync(paths.revocations)) return { revocations: [] };
384
+ if (!nodeFs.existsSync(paths.revocations)) return { revocations: [] };
385
385
  try {
386
386
  // safeJson.parse caps depth + size + protects against
387
387
  // proto-pollution; the revocation file is under the operator's
388
388
  // dataDir but a tampered or truncated file shouldn't be able to
389
389
  // corrupt the rotator process.
390
- var json = safeJson.parse(fs.readFileSync(paths.revocations, "utf8"),
390
+ var json = safeJson.parse(nodeFs.readFileSync(paths.revocations, "utf8"),
391
391
  { maxBytes: C.BYTES.mib(16) });
392
392
  if (!json || !Array.isArray(json.revocations)) return { revocations: [] };
393
393
  return json;
@@ -32,8 +32,8 @@ var nodeCrypto = require("node:crypto");
32
32
  var pki = require("./vendor/pki.cjs");
33
33
 
34
34
  var C = require("./constants");
35
- var crypto = require("./crypto");
36
- var nb = require("./numeric-bounds");
35
+ var bCrypto = require("./crypto");
36
+ var numericBounds = require("./numeric-bounds");
37
37
  var { FrameworkError } = require("./framework-error");
38
38
 
39
39
  var x509 = pki.x509;
@@ -231,7 +231,7 @@ async function generateCa(opts) {
231
231
  var keys = await webcrypto.subtle.generateKey(CA_KEY_ALG, true, CA_KEY_USAGES);
232
232
  var now = new Date();
233
233
  var ca = await x509.X509CertificateGenerator.createSelfSigned({
234
- serialNumber: crypto.generateToken(C.BYTES.bytes(16)),
234
+ serialNumber: bCrypto.generateToken(C.BYTES.bytes(16)),
235
235
  name: "CN=" + caName + ",OU=CAv" + generation,
236
236
  notBefore: now,
237
237
  notAfter: new Date(now.getTime() + C.TIME.days(CA_VALIDITY_DAYS)),
@@ -255,7 +255,7 @@ async function signClientCert(opts) {
255
255
  throw new MtlsEngineError("mtls-engine/missing-arg",
256
256
  "signClientCert requires { cn, caCertPem, caKeyPem }");
257
257
  }
258
- nb.requirePositiveFiniteIntIfPresent(opts.validityDays,
258
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.validityDays,
259
259
  "signClientCert: validityDays", MtlsEngineError, "mtls-engine/bad-validity-days");
260
260
  var validityDays = opts.validityDays !== undefined
261
261
  ? opts.validityDays : LEAF_DEFAULT_DAYS;
@@ -314,7 +314,7 @@ async function signClientCert(opts) {
314
314
  if (sanExt) extensions.push(sanExt);
315
315
 
316
316
  var clientCert = await x509.X509CertificateGenerator.create({
317
- serialNumber: crypto.generateToken(C.BYTES.bytes(16)),
317
+ serialNumber: bCrypto.generateToken(C.BYTES.bytes(16)),
318
318
  subject: "CN=" + cn,
319
319
  issuer: caCert.subject,
320
320
  notBefore: now,
@@ -4,7 +4,7 @@ var dns = require("node:dns");
4
4
  var net = require("node:net");
5
5
  var nodeCrypto = require("node:crypto");
6
6
  var https = require("node:https");
7
- var tls = require("node:tls");
7
+ var nodeTls = require("node:tls");
8
8
  var dnsPromises = dns.promises;
9
9
 
10
10
  var C = require("./constants");
@@ -561,7 +561,7 @@ function _dotConnect() {
561
561
  ecdhCurve: C.TLS_GROUP_CURVE_STR,
562
562
  };
563
563
  if (STATE.dot.ca) connectOpts.ca = STATE.dot.ca;
564
- var sock = tls.connect(connectOpts);
564
+ var sock = nodeTls.connect(connectOpts);
565
565
  // The pool entry is ref()'d while a query is in flight and unref()'d
566
566
  // when idle — _dotLookup toggles this around its query. Calling
567
567
  // unref() unconditionally here let node exit during a normal lookup
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- var tls = require("node:tls");
3
+ var nodeTls = require("node:tls");
4
4
  var dgram = require("node:dgram");
5
5
  var nodeCrypto = require("node:crypto");
6
6
 
@@ -258,7 +258,7 @@ function performKeHandshake(opts) {
258
258
  ecdhCurve: C.TLS_GROUP_CURVE_STR,
259
259
  };
260
260
  if (opts.ca) connectOpts.ca = opts.ca;
261
- var sock = tls.connect(connectOpts);
261
+ var sock = nodeTls.connect(connectOpts);
262
262
  var timer = setTimeout(function () {
263
263
  try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
264
264
  done(new NtsError("nts/ke-timeout", "NTS-KE handshake timed out after " + timeoutMs + "ms"));
@@ -3,7 +3,7 @@
3
3
  var http = require("node:http");
4
4
  var https = require("node:https");
5
5
  var net = require("node:net");
6
- var tls = require("node:tls");
6
+ var nodeTls = require("node:tls");
7
7
 
8
8
  var C = require("./constants");
9
9
  var lazyRequire = require("./lazy-require");
@@ -155,7 +155,7 @@ function _proxyAuthHeader(proxyUrl) {
155
155
  function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
156
156
  var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
157
157
  var proxySocket = proxyUrl.protocol === "https:"
158
- ? tls.connect({
158
+ ? nodeTls.connect({
159
159
  host: proxyUrl.hostname,
160
160
  port: proxyPort,
161
161
  servername: proxyUrl.hostname,
@@ -212,7 +212,7 @@ function agentFor(targetUrl) {
212
212
  agent.createConnection = function (options, cb) {
213
213
  _connectThroughTunnel(proxy, options.host, options.port, function (err, tunnel) {
214
214
  if (err) return cb(err);
215
- var secure = tls.connect({
215
+ var secure = nodeTls.connect({
216
216
  socket: tunnel,
217
217
  servername: options.servername || options.host,
218
218
  minVersion: "TLSv1.3",
@@ -58,7 +58,7 @@ var zlib = require("node:zlib");
58
58
  var asn1 = require("./asn1-der");
59
59
  var lazyRequire = require("./lazy-require");
60
60
  var validateOpts = require("./validate-opts");
61
- var crypto = require("./crypto");
61
+ var bCrypto = require("./crypto");
62
62
  var safeUrl = require("./safe-url");
63
63
  var safeJson = require("./safe-json");
64
64
  var C = require("./constants");
@@ -561,7 +561,7 @@ function tlsRptRecordShape(opts) {
561
561
 
562
562
  function _genReportId() {
563
563
  // RFC 8460 §4.4 requires uniqueness — use timestamp + random token.
564
- return Date.now() + "-" + crypto.generateToken(C.BYTES.bytes(8));
564
+ return Date.now() + "-" + bCrypto.generateToken(C.BYTES.bytes(8));
565
565
  }
566
566
 
567
567
  // ---- TLS-RPT policy fetch (RFC 8460 §3) ----
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
 
3
- var tls = require("node:tls");
4
- var fs = require("node:fs");
5
- var path = require("node:path");
3
+ var nodeTls = require("node:tls");
4
+ var nodeFs = require("node:fs");
5
+ var nodePath = require("node:path");
6
6
  var net = require("node:net");
7
7
  var nodeCrypto = require("node:crypto");
8
8
 
9
- var blamejsCrypto = require("./crypto");
9
+ var bCrypto = require("./crypto");
10
10
  var C = require("./constants");
11
11
  var safeBuffer = require("./safe-buffer");
12
12
  var validateOpts = require("./validate-opts");
@@ -85,14 +85,14 @@ function _isPathLike(s) {
85
85
  }
86
86
 
87
87
  function _readPath(p) {
88
- var stat = fs.statSync(p);
88
+ var stat = nodeFs.statSync(p);
89
89
  if (stat.isDirectory()) {
90
- var files = fs.readdirSync(p)
90
+ var files = nodeFs.readdirSync(p)
91
91
  .filter(function (f) { return /\.(pem|crt|cer)$/i.test(f); })
92
92
  .sort();
93
- return files.map(function (f) { return fs.readFileSync(path.join(p, f), "utf8"); }).join("\n");
93
+ return files.map(function (f) { return nodeFs.readFileSync(nodePath.join(p, f), "utf8"); }).join("\n");
94
94
  }
95
- return fs.readFileSync(p, "utf8");
95
+ return nodeFs.readFileSync(p, "utf8");
96
96
  }
97
97
 
98
98
  function addCa(pemOrPath, opts) {
@@ -101,7 +101,7 @@ function addCa(pemOrPath, opts) {
101
101
  var raw = pemOrPath;
102
102
  if (typeof pemOrPath === "string" && _isPathLike(pemOrPath)) {
103
103
  var stat;
104
- try { stat = fs.statSync(pemOrPath); } catch (_e) {
104
+ try { stat = nodeFs.statSync(pemOrPath); } catch (_e) {
105
105
  throw new TlsTrustError("tls/empty-pem", "tls.addCa: input has no PEM marker and is not a readable path: " +
106
106
  pemOrPath);
107
107
  }
@@ -408,7 +408,7 @@ function applyToContext(opts) {
408
408
  var base = Object.assign({}, opts.base || {});
409
409
  var caStrings = STATE.cas.map(function (e) { return e.pem; });
410
410
  if (STATE.systemTrust) {
411
- var rootCAs = tls.rootCertificates;
411
+ var rootCAs = nodeTls.rootCertificates;
412
412
  if (Array.isArray(rootCAs)) {
413
413
  caStrings = caStrings.concat(rootCAs);
414
414
  }
@@ -719,7 +719,7 @@ function _connectAndCheckOcsp(opts, requireStapled) {
719
719
  var connectOpts = Object.assign({}, opts, { requestOCSP: true });
720
720
  var sock;
721
721
  try {
722
- sock = tls.connect(connectOpts);
722
+ sock = nodeTls.connect(connectOpts);
723
723
  } catch (e) {
724
724
  reject(new TlsTrustError("tls/connect-failed",
725
725
  "tls.connect threw: " + ((e && e.message) || String(e))));
@@ -1110,7 +1110,7 @@ function evaluateOcspResponse(ocspDer, opts) {
1110
1110
  // critical here (the OCSP response is CA-signed and signature
1111
1111
  // already verified) but matches the project discipline.
1112
1112
  // (Audit 2026-05-11.)
1113
- if (!blamejsCrypto.timingSafeEqual(parsed.basic.nonce, opts.expectedNonce)) {
1113
+ if (!bCrypto.timingSafeEqual(parsed.basic.nonce, opts.expectedNonce)) {
1114
1114
  return { ok: false, status: parsed.status, signatureValid: true,
1115
1115
  errors: ["OCSP nonce mismatch — possible replay or wrong responder"] };
1116
1116
  }
@@ -2182,7 +2182,7 @@ var ct = Object.freeze({
2182
2182
  if (!Buffer.isBuffer(sthRoot) || sthRoot.length !== 32) { // allow:raw-byte-literal — RFC 9162 SHA-256 digest length
2183
2183
  return { valid: false, reason: "bad-sth-root" };
2184
2184
  }
2185
- if (!blamejsCrypto.timingSafeEqual(computedRoot, sthRoot)) {
2185
+ if (!bCrypto.timingSafeEqual(computedRoot, sthRoot)) {
2186
2186
  return { valid: false, reason: "root-mismatch",
2187
2187
  computedRoot: computedRoot.toString("hex") };
2188
2188
  }
@@ -2201,7 +2201,7 @@ var ct = Object.freeze({
2201
2201
  var computedSecond = _ctVerifyConsistencyPath(
2202
2202
  opts.consistency.firstSize, opts.sthFromLog.treeSize,
2203
2203
  opts.consistency.proof || [], firstRoot);
2204
- var ok = blamejsCrypto.timingSafeEqual(computedSecond, sthRoot);
2204
+ var ok = bCrypto.timingSafeEqual(computedSecond, sthRoot);
2205
2205
  consistencyResult = {
2206
2206
  ok: ok,
2207
2207
  computedSecondRoot: computedSecond.toString("hex"),
@@ -2256,7 +2256,7 @@ var ct = Object.freeze({
2256
2256
  return { valid: false, reason: "consistency-walk-failed",
2257
2257
  error: (e && e.message) || String(e) };
2258
2258
  }
2259
- if (!blamejsCrypto.timingSafeEqual(computed, secondRoot)) {
2259
+ if (!bCrypto.timingSafeEqual(computed, secondRoot)) {
2260
2260
  return { valid: false, reason: "root-mismatch",
2261
2261
  computedRoot: computed.toString("hex") };
2262
2262
  }
@@ -2500,7 +2500,7 @@ function _isEchSupported() {
2500
2500
  // immediately-destroyed socket. Any non-throwing path = supported.
2501
2501
  var supported = false;
2502
2502
  try {
2503
- var probe = tls.connect({
2503
+ var probe = nodeTls.connect({
2504
2504
  host: "127.0.0.1",
2505
2505
  port: 1,
2506
2506
  ech: Buffer.alloc(0),
@@ -2651,7 +2651,7 @@ function connectWithEch(opts) {
2651
2651
  }
2652
2652
 
2653
2653
  var sock;
2654
- try { sock = tls.connect(connectOpts); }
2654
+ try { sock = nodeTls.connect(connectOpts); }
2655
2655
  catch (e) {
2656
2656
  reject(new NetworkTlsError("tls/ech-connect-failed",
2657
2657
  "connectWithEch: tls.connect threw: " + ((e && e.message) || String(e))));