@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
package/lib/cli.js CHANGED
@@ -34,9 +34,9 @@
34
34
  * before encryption or temporarily switch the file to plaintext.
35
35
  */
36
36
 
37
- var fs = require("node:fs");
37
+ var nodeFs = require("node:fs");
38
38
  var os = require("node:os");
39
- var path = require("path");
39
+ var nodePath = require("path");
40
40
  var apiSnapshot = require("./api-snapshot");
41
41
  var argParser = require("./arg-parser");
42
42
  var auditChain = require("./audit-chain");
@@ -44,9 +44,8 @@ var auditTools = require("./audit-tools");
44
44
  var backup = require("./backup");
45
45
  var canonicalJson = require("./canonical-json");
46
46
  var cliHelpers = require("./cli-helpers");
47
- var constants = require("./constants");
48
- var C = constants;
49
- var crypto = require("./crypto");
47
+ var C = require("./constants");
48
+ var bCrypto = require("./crypto");
50
49
  var dev = require("./dev");
51
50
  var fileType = require("./file-type");
52
51
  var migrations = require("./migrations");
@@ -86,8 +85,8 @@ function _parseArgs(argv) {
86
85
 
87
86
  function _resolvePath(p, cwd) {
88
87
  if (!p) return p;
89
- if (path.isAbsolute(p)) return p;
90
- return path.resolve(cwd || process.cwd(), p);
88
+ if (nodePath.isAbsolute(p)) return p;
89
+ return nodePath.resolve(cwd || process.cwd(), p);
91
90
  }
92
91
 
93
92
  function _openSqlite(dbPath) {
@@ -432,12 +431,12 @@ function _resolveTargetModule(modulePath, ctx) {
432
431
  // extensibility surfaces by definition can't be statically traced by a
433
432
  // bundler — anyone bundling this CLI surface into SEA/pkg accepts that
434
433
  // runtime --module=<path> arguments won't resolve. Internal framework
435
- // code never reaches this path.
434
+ // code never reaches this nodePath.
436
435
  if (!modulePath) {
437
- var root = path.resolve(__dirname, "..");
438
- return require(path.join(root, "index.js")); // allow:dynamic-require — operator-extensibility entry point
436
+ var root = nodePath.resolve(__dirname, "..");
437
+ return require(nodePath.join(root, "index.js")); // allow:dynamic-require — operator-extensibility entry point
439
438
  }
440
- var abs = path.isAbsolute(modulePath) ? modulePath : path.resolve(ctx.cwd, modulePath);
439
+ var abs = nodePath.isAbsolute(modulePath) ? modulePath : nodePath.resolve(ctx.cwd, modulePath);
441
440
  delete require.cache[require.resolve(abs)];
442
441
  return require(abs); // allow:dynamic-require — operator-extensibility entry point
443
442
  }
@@ -453,7 +452,7 @@ function _runApiSnapshot(args, ctx) {
453
452
  }
454
453
  var sub = args.pos[0];
455
454
  var file = String(args.flags.file || "./api-snapshot.json");
456
- var filePath = path.isAbsolute(file) ? file : path.resolve(ctx.cwd, file);
455
+ var filePath = nodePath.isAbsolute(file) ? file : nodePath.resolve(ctx.cwd, file);
457
456
  var modulePathOpt = typeof args.flags.module === "string" ? args.flags.module : null;
458
457
 
459
458
  if (sub === "capture") {
@@ -559,7 +558,7 @@ function _resolvePassphrase(args, ctx) {
559
558
 
560
559
  function _resolveOutPath(p, ctx) {
561
560
  if (!p) return null;
562
- return path.isAbsolute(p) ? p : path.resolve(ctx.cwd, p);
561
+ return nodePath.isAbsolute(p) ? p : nodePath.resolve(ctx.cwd, p);
563
562
  }
564
563
 
565
564
  async function _runAudit(args, ctx) {
@@ -782,8 +781,8 @@ function _resolveRestoreBundleSelector(args, ctx, report, requireBundle) {
782
781
  if (bundleFlag && bundleFlag !== true) {
783
782
  var bundlePath = _resolvePath(String(bundleFlag), ctx.cwd);
784
783
  return {
785
- storageRoot: path.dirname(bundlePath),
786
- bundleId: path.basename(bundlePath),
784
+ storageRoot: nodePath.dirname(bundlePath),
785
+ bundleId: nodePath.basename(bundlePath),
787
786
  };
788
787
  }
789
788
  if (storageRootFlag && storageRootFlag !== true) {
@@ -862,7 +861,7 @@ async function _runRestore(args, ctx) {
862
861
  // its closure captures them; pass placeholders since inspect doesn't
863
862
  // touch them.
864
863
  var rI = restore.create({
865
- dataDir: path.join(os.tmpdir(), "blamejs-restore-inspect-noop"),
864
+ dataDir: nodePath.join(os.tmpdir(), "blamejs-restore-inspect-noop"),
866
865
  storage: storageI,
867
866
  passphrase: "inspect-only-not-used",
868
867
  audit: false,
@@ -943,7 +942,7 @@ async function _runRestore(args, ctx) {
943
942
  if (rollbackTarget && rollbackTarget !== true) {
944
943
  // operator can pass either a full path or just the basename inside rollback-root
945
944
  var rt = String(rollbackTarget);
946
- targetPath = path.isAbsolute(rt) ? rt : path.resolve(rollbackRootR, rt);
945
+ targetPath = nodePath.isAbsolute(rt) ? rt : nodePath.resolve(rollbackRootR, rt);
947
946
  } else {
948
947
  // Default to most-recent rollback point (mirrors restore.create().rollback()).
949
948
  var ptsR;
@@ -1245,8 +1244,8 @@ async function _runBackup(args, ctx) {
1245
1244
  }
1246
1245
 
1247
1246
  if (sub === "verify") {
1248
- var stagingDir = path.join(os.tmpdir(),
1249
- "blamejs-backup-verify-" + crypto.generateToken(C.BYTES.bytes(8)));
1247
+ var stagingDir = nodePath.join(os.tmpdir(),
1248
+ "blamejs-backup-verify-" + bCrypto.generateToken(C.BYTES.bytes(8)));
1250
1249
  try {
1251
1250
  var r = await restoreBundle.extract({
1252
1251
  bundleDir: bundleDir,
@@ -1260,7 +1259,7 @@ async function _runBackup(args, ctx) {
1260
1259
  } catch (e) {
1261
1260
  return report.error((e && e.message) || String(e));
1262
1261
  } finally {
1263
- try { fs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort */ }
1262
+ try { nodeFs.rmSync(stagingDir, { recursive: true, force: true }); } catch (_e) { /* best-effort */ }
1264
1263
  }
1265
1264
  }
1266
1265
 
@@ -1461,7 +1460,7 @@ async function _runMtls(args, ctx) {
1461
1460
  validityDays: daysP,
1462
1461
  });
1463
1462
  if (outPath) {
1464
- fs.writeFileSync(outPath, p12.p12, { mode: 0o600 });
1463
+ nodeFs.writeFileSync(outPath, p12.p12, { mode: 0o600 });
1465
1464
  report.write("p12 written: " + outPath);
1466
1465
  } else {
1467
1466
  // No --out: stream the bytes to stdout for piping. Operators
@@ -1838,7 +1837,7 @@ function _runFileType(args, ctx) {
1838
1837
  if (!file) return report.error("file path is required (positional arg after 'detect')", 2);
1839
1838
  var resolved = _resolvePath(String(file), ctx.cwd);
1840
1839
  var buf;
1841
- try { buf = fs.readFileSync(resolved); }
1840
+ try { buf = nodeFs.readFileSync(resolved); }
1842
1841
  catch (e) {
1843
1842
  return report.error("read failed: " + ((e && e.message) || String(e)));
1844
1843
  }
@@ -1915,7 +1914,7 @@ async function _runPassword(args, ctx) {
1915
1914
  }
1916
1915
  var plaintext;
1917
1916
  if (args.flags.stdin) {
1918
- try { plaintext = fs.readFileSync(0, "utf8").replace(/\r?\n$/, ""); }
1917
+ try { plaintext = nodeFs.readFileSync(0, "utf8").replace(/\r?\n$/, ""); }
1919
1918
  catch (e) { return report.error("stdin read failed: " + ((e && e.message) || String(e))); }
1920
1919
  } else if (args.flags.plaintext && args.flags.plaintext !== true) {
1921
1920
  plaintext = String(args.flags.plaintext);
@@ -2231,7 +2230,7 @@ async function main(argv, opts) {
2231
2230
 
2232
2231
  // Top-level flags handled before subcommand dispatch
2233
2232
  if (args.flags.version || args.flags.v) {
2234
- _writeLine(ctx.stdout, constants.version);
2233
+ _writeLine(ctx.stdout, C.version);
2235
2234
  return 0;
2236
2235
  }
2237
2236
 
@@ -2246,7 +2245,7 @@ async function main(argv, opts) {
2246
2245
  // subcommand's per-command usage
2247
2246
  // reachable via `--help` without
2248
2247
  // each handler having to special-case
2249
- // the flag-only-no-subcommand path.
2248
+ // the flag-only-no-subcommand nodePath.
2250
2249
  if (cmd === undefined) { _printTopHelp(ctx); return 0; }
2251
2250
  if (args.flags.help || args.flags.h) {
2252
2251
  args = _parseArgs(["help", cmd]);
@@ -2273,7 +2272,7 @@ async function main(argv, opts) {
2273
2272
  _printTopHelp(ctx);
2274
2273
  return 0;
2275
2274
  }
2276
- if (cmd === "version") { _writeLine(ctx.stdout, constants.version); return 0; }
2275
+ if (cmd === "version") { _writeLine(ctx.stdout, C.version); return 0; }
2277
2276
 
2278
2277
  var rest = { pos: args.pos.slice(1), flags: args.flags };
2279
2278
  if (cmd === "migrate") return await _runMigrate(rest, ctx);
package/lib/cluster.js CHANGED
@@ -45,7 +45,7 @@
45
45
  */
46
46
  var C = require("./constants");
47
47
  var clusterProviderDb = require("./cluster-provider-db");
48
- var crypto = require("./crypto");
48
+ var bCrypto = require("./crypto");
49
49
  var lazyRequire = require("./lazy-require");
50
50
  var { boot } = require("./log");
51
51
  var safeAsync = require("./safe-async");
@@ -428,7 +428,7 @@ function _vaultKeyFingerprint() {
428
428
  if (!keys || !keys.publicKey || !keys.ecPublicKey) return null;
429
429
  // Domain-separation prefix so this fingerprint can't be confused
430
430
  // with a hash of the same bytes computed elsewhere in the framework.
431
- return crypto.sha3Hash("blamejs/cluster-state/v1\n" +
431
+ return bCrypto.sha3Hash("blamejs/cluster-state/v1\n" +
432
432
  keys.publicKey + "\n" +
433
433
  keys.ecPublicKey);
434
434
  }
@@ -483,9 +483,9 @@ function create(opts) {
483
483
  // ignorable for the audit-trail use case (operators store the
484
484
  // ruleVersion + entry count alongside).
485
485
  function snapshot() {
486
- var crypto = require("crypto");
486
+ var nodeCrypto = require("crypto");
487
487
  var ids = index.map(function (e) { return e.id; }).sort();
488
- var hash = crypto.createHash("sha3-512");
488
+ var hash = nodeCrypto.createHash("sha3-512");
489
489
  for (var i = 0; i < ids.length; i++) hash.update(ids[i]);
490
490
  return {
491
491
  algorithm: algorithm,
@@ -35,11 +35,11 @@
35
35
  * @card
36
36
  * Monitor + alert when runtime config diverges from a declared baseline.
37
37
  */
38
- var fs = require("node:fs");
39
- var path = require("node:path");
38
+ var nodeFs = require("node:fs");
39
+ var nodePath = require("node:path");
40
40
  var auditSign = require("./audit-sign");
41
41
  var canonicalJson = require("./canonical-json");
42
- var crypto = require("./crypto");
42
+ var bCrypto = require("./crypto");
43
43
  var lazyRequire = require("./lazy-require");
44
44
  var safeJson = require("./safe-json");
45
45
  var validateOpts = require("./validate-opts");
@@ -57,12 +57,12 @@ var SIDECAR_VERSION = 1;
57
57
  // Deterministic key order so the same snapshot always hashes to the same
58
58
  // digest. Pre-v0.6.67 the in-line implementation silently lost Date /
59
59
  // Map / Set / Buffer / BigInt content; the shared walker handles all
60
- // of those + circular refs. Same bytes as audit-chain / audit-tools /
60
+ // of those + circular renodeFs. Same bytes as audit-chain / audit-tools /
61
61
  // pagination would produce for the same input.
62
62
  function _stableStringify(value) { return canonicalJson.stringify(value); }
63
63
 
64
64
  function _hashSnapshot(snapshot) {
65
- return crypto.sha3Hash(_stableStringify(snapshot));
65
+ return bCrypto.sha3Hash(_stableStringify(snapshot));
66
66
  }
67
67
 
68
68
  function _diffShallow(prev, next) {
@@ -155,7 +155,7 @@ function create(opts) {
155
155
  // (e.g. operator-tracked metadata that legitimately changes per
156
156
  // boot). Captured in the snapshot but never flagged.
157
157
  var ignoreKeys = Array.isArray(opts.ignoreKeys) ? opts.ignoreKeys.slice() : [];
158
- var sidecarPath = path.join(dataDir,
158
+ var sidecarPath = nodePath.join(dataDir,
159
159
  baselineName === "default" ? SIDECAR_NAME : ("config-baseline-" + baselineName + ".sig"));
160
160
 
161
161
  function _emit(action, info, outcome) {
@@ -172,9 +172,9 @@ function create(opts) {
172
172
  }
173
173
 
174
174
  function _readSidecar() {
175
- if (!fs.existsSync(sidecarPath)) return null;
175
+ if (!nodeFs.existsSync(sidecarPath)) return null;
176
176
  var raw;
177
- try { raw = fs.readFileSync(sidecarPath, "utf8"); }
177
+ try { raw = nodeFs.readFileSync(sidecarPath, "utf8"); }
178
178
  catch (_e) { return null; }
179
179
  var parsed;
180
180
  try { parsed = safeJson.parse(raw); }
@@ -200,8 +200,8 @@ function create(opts) {
200
200
  snapshot: snapshot,
201
201
  };
202
202
  var tmp = sidecarPath + ".tmp";
203
- fs.writeFileSync(tmp, JSON.stringify(payload, null, 2));
204
- fs.renameSync(tmp, sidecarPath);
203
+ nodeFs.writeFileSync(tmp, JSON.stringify(payload, null, 2));
204
+ nodeFs.renameSync(tmp, sidecarPath);
205
205
  }
206
206
 
207
207
  function _verifySidecar(parsed) {
@@ -366,10 +366,10 @@ function create(opts) {
366
366
  */
367
367
  function verifyVendorIntegrity(opts) {
368
368
  opts = opts || {};
369
- var libVendorDir = opts.libVendorDir || path.join(process.cwd(), "lib", "vendor");
370
- var manifestPath = opts.manifestPath || path.join(libVendorDir, "MANIFEST.json");
369
+ var libVendorDir = opts.libVendorDir || nodePath.join(process.cwd(), "lib", "vendor");
370
+ var manifestPath = opts.manifestPath || nodePath.join(libVendorDir, "MANIFEST.json");
371
371
  var raw;
372
- try { raw = fs.readFileSync(manifestPath, "utf8"); }
372
+ try { raw = nodeFs.readFileSync(manifestPath, "utf8"); }
373
373
  catch (_e) {
374
374
  throw _err("VENDOR_MANIFEST_MISSING",
375
375
  "vendor MANIFEST.json missing at " + manifestPath, true);
@@ -389,10 +389,10 @@ function verifyVendorIntegrity(opts) {
389
389
  var rel = files[kind];
390
390
  var expected = hashes[kind];
391
391
  if (typeof rel !== "string" || typeof expected !== "string") return;
392
- var abs = path.isAbsolute(rel) ? rel : path.join(process.cwd(), rel);
392
+ var abs = nodePath.isAbsolute(rel) ? rel : nodePath.join(process.cwd(), rel);
393
393
  var actual;
394
394
  try {
395
- var bytes = fs.readFileSync(abs);
395
+ var bytes = nodeFs.readFileSync(abs);
396
396
  actual = "sha256:" + require("node:crypto")
397
397
  .createHash("sha256").update(bytes).digest("hex");
398
398
  } catch (_e) {
@@ -28,7 +28,7 @@
28
28
  * C2PA 2.1 content provenance — sign assets with a manifest declaring origin, edits, AI involvement.
29
29
  */
30
30
 
31
- var crypto = require("./crypto");
31
+ var bCrypto = require("./crypto");
32
32
  var canonicalJson = require("./canonical-json");
33
33
  var validateOpts = require("./validate-opts");
34
34
  var audit = require("./audit");
@@ -234,7 +234,7 @@ function sign(manifest, opts) {
234
234
  validateOpts.requireNonEmptyString(opts.privateKeyPem,
235
235
  "contentCredentials.sign: privateKeyPem", ContentCredentialsError, "BAD_KEY");
236
236
  var canonical = canonicalJson.stringify(manifest);
237
- var signature = crypto.sign(Buffer.from(canonical, "utf8"), opts.privateKeyPem);
237
+ var signature = bCrypto.sign(Buffer.from(canonical, "utf8"), opts.privateKeyPem);
238
238
  var auditOn = opts.audit !== false;
239
239
  if (auditOn) {
240
240
  audit.safeEmit({
@@ -299,7 +299,7 @@ function verify(envelope, publicKeyPem, opts) {
299
299
  catch (_e) {
300
300
  return { valid: false, claims: null, reason: "signature-base64-bad" };
301
301
  }
302
- var ok = crypto.verify(Buffer.from(canonical, "utf8"), sigBuf, publicKeyPem);
302
+ var ok = bCrypto.verify(Buffer.from(canonical, "utf8"), sigBuf, publicKeyPem);
303
303
  if (!ok) {
304
304
  return { valid: false, claims: null, reason: "signature-mismatch" };
305
305
  }
@@ -519,7 +519,7 @@ function signCose(manifest, opts) {
519
519
  var toBeSigned = Buffer.concat(sigStructureBufs);
520
520
 
521
521
  // Sign with framework's b.crypto.sign — algorithm picked from the PEM.
522
- var signature = crypto.sign(toBeSigned, opts.privateKeyPem);
522
+ var signature = bCrypto.sign(toBeSigned, opts.privateKeyPem);
523
523
 
524
524
  // COSE_Sign1 = tagged-18 array [protected, unprotected, payload, signature]
525
525
  var coseSign1 = Buffer.concat([
@@ -41,7 +41,7 @@
41
41
  * Derive a deterministic, verifiable hash for credential lookup (API-key secret, shared bearer token, webhook signing key) without storing the credential itself.
42
42
  */
43
43
 
44
- var crypto = require("./crypto");
44
+ var bCrypto = require("./crypto");
45
45
  var C = require("./constants");
46
46
  var lazyRequire = require("./lazy-require");
47
47
  var { FrameworkError } = require("./framework-error");
@@ -73,7 +73,7 @@ function _shake256(secret, length) {
73
73
  // crypto.kdf wraps SHAKE256 with arbitrary output length. That's the
74
74
  // exact primitive we need — the framework's KDF and credential-hash
75
75
  // share one underlying XOF.
76
- return crypto.kdf(secret, length);
76
+ return bCrypto.kdf(secret, length);
77
77
  }
78
78
 
79
79
 
@@ -291,7 +291,7 @@ async function verify(secret, envelope) {
291
291
  return false;
292
292
  }
293
293
  var expected = _shake256(secret, decoded.payload.length);
294
- var ok = crypto.timingSafeEqual(expected, decoded.payload);
294
+ var ok = bCrypto.timingSafeEqual(expected, decoded.payload);
295
295
  _emitEvent("credentialHash.verify", 1,
296
296
  { outcome: ok ? "success" : "failure", algo: algoName });
297
297
  return ok;
package/lib/crypto.js CHANGED
@@ -147,6 +147,150 @@ function hashFile(filePath, algorithm) {
147
147
  return hashStream(nodeFs.createReadStream(filePath), algorithm);
148
148
  }
149
149
 
150
+ // _hashFileMulti — single-pass stream of a file through N hashers in
151
+ // parallel. Returns { path, byteLength, <alg>: hex } for every
152
+ // `algorithms` entry. Used by hashFilesParallel below; not exported
153
+ // directly because the common case is the parallel-many shape.
154
+ function _hashFileMulti(filePath, algorithms) {
155
+ return new Promise(function (resolve, reject) {
156
+ var hashers = new Array(algorithms.length);
157
+ for (var i = 0; i < algorithms.length; i += 1) {
158
+ try { hashers[i] = nodeCrypto.createHash(algorithms[i]); }
159
+ catch (e) {
160
+ reject(new Error("crypto.hashFilesParallel: unknown algorithm '" +
161
+ algorithms[i] + "': " + (e && e.message ? e.message : String(e))));
162
+ return;
163
+ }
164
+ }
165
+ var byteLength = 0;
166
+ var stream = nodeFs.createReadStream(filePath);
167
+ stream.on("error", reject);
168
+ stream.on("data", function (chunk) {
169
+ byteLength += chunk.length;
170
+ for (var j = 0; j < hashers.length; j += 1) hashers[j].update(chunk);
171
+ });
172
+ stream.on("end", function () {
173
+ var out = { path: filePath, byteLength: byteLength };
174
+ for (var k = 0; k < hashers.length; k += 1) {
175
+ // Field name = algorithm with `-` → `_` so "sha3-512" surfaces
176
+ // as `out.sha3_512` (matches the standalone-verifier shape).
177
+ out[algorithms[k].replace(/-/g, "_")] = hashers[k].digest("hex");
178
+ }
179
+ resolve(out);
180
+ });
181
+ });
182
+ }
183
+
184
+ /**
185
+ * @primitive b.crypto.hashFilesParallel
186
+ * @signature b.crypto.hashFilesParallel(filePaths, opts?)
187
+ * @since 0.9.14
188
+ * @status stable
189
+ * @related b.crypto.hashFile, b.crypto.hashStream
190
+ *
191
+ * Hash many files in parallel, streaming each one through one or
192
+ * more digest algorithms in a single read pass. Returns an array of
193
+ * `{ path, byteLength, sha256, sha3_512, ... }` records in the same
194
+ * order as `filePaths`. Concurrency is operator-tunable; the default
195
+ * (`min(8, filePaths.length)`) matches the framework's
196
+ * hash-while-streaming convention elsewhere without saturating the
197
+ * fs read queue on spinning-disk hosts.
198
+ *
199
+ * The common consumer-side reason to reach for this primitive is
200
+ * SBOM regeneration / vendor-data integrity sweeps / release-asset
201
+ * bundling — situations where N files each need both SHA-256 (legacy
202
+ * compat) and SHA-3-512 (PQC-first) digests and rolling a worker
203
+ * pool by hand has cost a downstream consumer (`hermitstash-sync`
204
+ * 2026-05-13) the same two-loop, capture-N-promises, settle-Q boilerplate
205
+ * every release.
206
+ *
207
+ * @opts
208
+ * algorithms?: string[], // default ["sha256", "sha3-512"]; any node:crypto-known digest
209
+ * concurrency?: number, // default min(8, filePaths.length); 1..256
210
+ * onProgress?: function (completed, total) // best-effort; thrown errors swallowed
211
+ *
212
+ * @example
213
+ * var rows = await b.crypto.hashFilesParallel(
214
+ * ["/var/lib/blamejs/asset-a.bin",
215
+ * "/var/lib/blamejs/asset-b.bin"],
216
+ * { algorithms: ["sha256", "sha3-512"], concurrency: 4 }
217
+ * );
218
+ * // rows[0] → { path: "...asset-a.bin", byteLength: 4096,
219
+ * // sha256: "...", sha3_512: "..." }
220
+ */
221
+ function hashFilesParallel(filePaths, opts) {
222
+ if (!Array.isArray(filePaths)) {
223
+ return Promise.reject(new TypeError(
224
+ "crypto.hashFilesParallel: filePaths must be an array of non-empty strings"
225
+ ));
226
+ }
227
+ for (var i = 0; i < filePaths.length; i += 1) {
228
+ if (typeof filePaths[i] !== "string" || filePaths[i].length === 0) {
229
+ return Promise.reject(new TypeError(
230
+ "crypto.hashFilesParallel: filePaths[" + i + "] must be a non-empty string"
231
+ ));
232
+ }
233
+ }
234
+ opts = opts || {};
235
+ var algorithms = opts.algorithms !== undefined
236
+ ? opts.algorithms : ["sha256", "sha3-512"];
237
+ if (!Array.isArray(algorithms) || algorithms.length === 0) {
238
+ return Promise.reject(new TypeError(
239
+ "crypto.hashFilesParallel: opts.algorithms must be a non-empty array"
240
+ ));
241
+ }
242
+ for (var ai = 0; ai < algorithms.length; ai += 1) {
243
+ if (typeof algorithms[ai] !== "string" || algorithms[ai].length === 0) {
244
+ return Promise.reject(new TypeError(
245
+ "crypto.hashFilesParallel: opts.algorithms[" + ai + "] must be a non-empty string"
246
+ ));
247
+ }
248
+ }
249
+ var concurrency = opts.concurrency !== undefined
250
+ ? opts.concurrency
251
+ : Math.min(8, Math.max(1, filePaths.length)); // allow:raw-byte-literal — worker fan-out cap, not bytes
252
+ if (typeof concurrency !== "number" || !isFinite(concurrency) ||
253
+ concurrency < 1 || concurrency > 256 || // allow:raw-byte-literal — concurrency upper cap
254
+ Math.floor(concurrency) !== concurrency) {
255
+ return Promise.reject(new TypeError(
256
+ "crypto.hashFilesParallel: opts.concurrency must be an integer in [1, 256], got " + concurrency
257
+ ));
258
+ }
259
+ var onProgress = opts.onProgress;
260
+ if (onProgress !== undefined && typeof onProgress !== "function") {
261
+ return Promise.reject(new TypeError(
262
+ "crypto.hashFilesParallel: opts.onProgress must be a function when supplied"
263
+ ));
264
+ }
265
+ if (filePaths.length === 0) return Promise.resolve([]);
266
+
267
+ var results = new Array(filePaths.length);
268
+ var nextIdx = 0;
269
+ var completed = 0;
270
+ var total = filePaths.length;
271
+ function _worker() {
272
+ function _step() {
273
+ var idx = nextIdx;
274
+ nextIdx += 1;
275
+ if (idx >= total) return Promise.resolve();
276
+ return _hashFileMulti(filePaths[idx], algorithms).then(function (rec) {
277
+ results[idx] = rec;
278
+ completed += 1;
279
+ if (onProgress) {
280
+ try { onProgress(completed, total); }
281
+ catch (_e) { /* progress callback errors are not fatal */ }
282
+ }
283
+ return _step();
284
+ });
285
+ }
286
+ return _step();
287
+ }
288
+ var workerCount = Math.min(concurrency, total);
289
+ var workers = new Array(workerCount);
290
+ for (var w = 0; w < workerCount; w += 1) workers[w] = _worker();
291
+ return Promise.all(workers).then(function () { return results; });
292
+ }
293
+
150
294
  function random(byteLength) {
151
295
  var n = byteLength || 32;
152
296
  // SHAKE256 over OS-RNG bytes. The OS RNG (nodeCrypto.randomBytes) is
@@ -1374,6 +1518,7 @@ module.exports = {
1374
1518
  sha3Hash: sha3Hash,
1375
1519
  hmacSha3: hmacSha3,
1376
1520
  hashFile: hashFile,
1521
+ hashFilesParallel: hashFilesParallel,
1377
1522
  hashStream: hashStream,
1378
1523
  namespaceHash: namespaceHash,
1379
1524
  kdf: kdf,
package/lib/daemon.js CHANGED
@@ -37,9 +37,9 @@
37
37
  * Long-running process orchestration — supervisor wiring around `b.appShutdown`, foreground signal handling, detached-fork spawn via `b.processSpawn`, PID-file health probes, and a SIGTERM-then-SIGKILL restart policy on stop.
38
38
  */
39
39
 
40
- var fs = require("fs");
41
- var path = require("path");
42
- var nb = require("./numeric-bounds");
40
+ var nodeFs = require("fs");
41
+ var nodePath = require("path");
42
+ var numericBounds = require("./numeric-bounds");
43
43
  var appShutdown = require("./app-shutdown");
44
44
  var processSpawn = require("./process-spawn");
45
45
  var lazyRequire = require("./lazy-require");
@@ -80,7 +80,7 @@ function _isLivePid(pid) {
80
80
 
81
81
  function _readPidFile(pidFile) {
82
82
  try {
83
- var raw = fs.readFileSync(pidFile, "utf8");
83
+ var raw = nodeFs.readFileSync(pidFile, "utf8");
84
84
  var pid = parseInt(String(raw).trim(), 10);
85
85
  return isFinite(pid) && pid > 0 ? pid : null;
86
86
  } catch (_e) { return null; }
@@ -118,9 +118,9 @@ function _validateStopOpts(opts) {
118
118
  "daemon.stop: opts.pidFile", DaemonError, "daemon/bad-pid-file");
119
119
  validateOpts.optionalNonEmptyString(opts.signal,
120
120
  "daemon.stop: opts.signal", DaemonError, "daemon/bad-signal");
121
- nb.requirePositiveFiniteIntIfPresent(opts.timeoutMs,
121
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.timeoutMs,
122
122
  "daemon.stop: opts.timeoutMs", DaemonError, "daemon/bad-timeout");
123
- nb.requirePositiveFiniteIntIfPresent(opts.pollMs,
123
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.pollMs,
124
124
  "daemon.stop: opts.pollMs", DaemonError, "daemon/bad-poll");
125
125
  }
126
126
 
@@ -133,7 +133,7 @@ function _maybeReapStale(pidFile) {
133
133
  }
134
134
  if (existing === process.pid) return false;
135
135
  // Stale: PID is gone (or signal-0 returned ESRCH). Reap + audit.
136
- try { fs.unlinkSync(pidFile); } catch (_e) { /* race: another reaper */ }
136
+ try { nodeFs.unlinkSync(pidFile); } catch (_e) { /* race: another reaper */ }
137
137
  _safeAuditEmit("daemon.stale_pid_cleaned", "success", {
138
138
  pidFile: pidFile,
139
139
  stalePid: existing,
@@ -146,13 +146,13 @@ function _maybeReapStale(pidFile) {
146
146
  // redirect of the current process' stdout/stderr.
147
147
  function _openLogFd(logFile) {
148
148
  if (typeof logFile !== "string" || logFile.length === 0) return null;
149
- atomicFile.ensureDir(path.dirname(logFile));
150
- var fd = fs.openSync(logFile, "a", DEFAULT_LOG_FILE_MODE);
149
+ atomicFile.ensureDir(nodePath.dirname(logFile));
150
+ var fd = nodeFs.openSync(logFile, "a", DEFAULT_LOG_FILE_MODE);
151
151
  return fd;
152
152
  }
153
153
 
154
154
  // Redirect the current process's stdout/stderr file descriptors at the
155
- // given fd. Implemented via fs.writeSync streams: Node doesn't expose a
155
+ // given fd. Implemented via nodeFs.writeSync streams: Node doesn't expose a
156
156
  // portable dup2, so we replace process.stdout.write / process.stderr.write
157
157
  // with a writer that pushes to the log fd. This is the standard
158
158
  // pattern for foreground daemons that don't want to lose output when
@@ -163,7 +163,7 @@ function _redirectStdio(fd) {
163
163
  var enc = typeof encOrCb === "string" ? encOrCb : "utf8";
164
164
  var cb = typeof encOrCb === "function" ? encOrCb : maybeCb;
165
165
  var buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), enc);
166
- try { fs.writeSync(fd, buf); }
166
+ try { nodeFs.writeSync(fd, buf); }
167
167
  catch (_e) { /* log fd closed underneath us — drop */ }
168
168
  if (typeof cb === "function") cb();
169
169
  return true;
@@ -246,21 +246,21 @@ function start(opts) {
246
246
  cwd: typeof opts.cwd === "string" ? opts.cwd : undefined,
247
247
  });
248
248
  } catch (e) {
249
- try { if (typeof logFd === "number") fs.closeSync(logFd); }
249
+ try { if (typeof logFd === "number") nodeFs.closeSync(logFd); }
250
250
  catch (_c) { /* best-effort */ }
251
251
  throw new DaemonError("daemon/spawn-failed",
252
252
  "daemon.start: spawn failed: " + ((e && e.message) || String(e)));
253
253
  }
254
254
  // Write the child's PID via atomic temp+rename so a concurrent
255
255
  // observer never sees a half-written pidFile.
256
- atomicFile.ensureDir(path.dirname(pidFile));
256
+ atomicFile.ensureDir(nodePath.dirname(pidFile));
257
257
  var pidStr = String(child.pid) + "\n";
258
258
  atomicFile.writeSync(pidFile, pidStr, { fileMode: 0o600 });
259
259
  // Detach so the child survives parent exit.
260
260
  try { child.unref(); } catch (_u) { /* best-effort */ }
261
261
  if (typeof logFd === "number") {
262
262
  // Parent doesn't need its handle to the log; child inherited it.
263
- try { fs.closeSync(logFd); } catch (_c) { /* best-effort */ }
263
+ try { nodeFs.closeSync(logFd); } catch (_c) { /* best-effort */ }
264
264
  }
265
265
  _safeAuditEmit("daemon.started", "success", {
266
266
  pidFile: pidFile,
@@ -307,7 +307,7 @@ function start(opts) {
307
307
  run: function () {
308
308
  try { lock.release(); } catch (_e) { /* best-effort */ }
309
309
  if (logFdForeground !== null) {
310
- try { fs.closeSync(logFdForeground); } catch (_c) { /* best-effort */ }
310
+ try { nodeFs.closeSync(logFdForeground); } catch (_c) { /* best-effort */ }
311
311
  }
312
312
  },
313
313
  timeoutMs: C.TIME.seconds(2),
@@ -381,7 +381,7 @@ async function stop(opts) {
381
381
  }
382
382
  if (!_isLivePid(pid)) {
383
383
  // Stale — clean up and report.
384
- try { fs.unlinkSync(pidFile); } catch (_e) { /* best-effort */ }
384
+ try { nodeFs.unlinkSync(pidFile); } catch (_e) { /* best-effort */ }
385
385
  _safeAuditEmit("daemon.stale_pid_cleaned", "success", { pidFile: pidFile, stalePid: pid });
386
386
  return { stopped: false, pid: pid, reason: "stale" };
387
387
  }
@@ -392,7 +392,7 @@ async function stop(opts) {
392
392
  catch (e) {
393
393
  if (e && e.code === "ESRCH") {
394
394
  // Died between read and kill — cleanup + report.
395
- try { fs.unlinkSync(pidFile); } catch (_u) { /* best-effort */ }
395
+ try { nodeFs.unlinkSync(pidFile); } catch (_u) { /* best-effort */ }
396
396
  _safeAuditEmit("daemon.stopped", "success", {
397
397
  pidFile: pidFile, signal: signal, waitMs: Date.now() - t0, escalated: false,
398
398
  });
@@ -405,7 +405,7 @@ async function stop(opts) {
405
405
  var deadline = t0 + timeoutMs;
406
406
  while (Date.now() < deadline) {
407
407
  if (!_isLivePid(pid)) {
408
- try { fs.unlinkSync(pidFile); } catch (_u) { /* best-effort */ }
408
+ try { nodeFs.unlinkSync(pidFile); } catch (_u) { /* best-effort */ }
409
409
  _safeAuditEmit("daemon.stopped", "success", {
410
410
  pidFile: pidFile, signal: signal, waitMs: Date.now() - t0, escalated: false,
411
411
  });
@@ -428,7 +428,7 @@ async function stop(opts) {
428
428
  if (!_isLivePid(pid)) break;
429
429
  await safeAsync.sleep(pollMs, { signal: opts.abortSignal });
430
430
  }
431
- try { fs.unlinkSync(pidFile); } catch (_u) { /* best-effort */ }
431
+ try { nodeFs.unlinkSync(pidFile); } catch (_u) { /* best-effort */ }
432
432
  _safeAuditEmit("daemon.stopped", "success", {
433
433
  pidFile: pidFile, signal: "SIGKILL", waitMs: Date.now() - t0, escalated: true,
434
434
  });