@blamejs/core 0.9.12 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/lib/a2a.js +11 -11
- package/lib/acme.js +5 -5
- package/lib/ai-input.js +2 -2
- package/lib/api-key.js +4 -4
- package/lib/api-snapshot.js +10 -7
- package/lib/app-shutdown.js +2 -2
- package/lib/app.js +5 -5
- package/lib/archive.js +8 -8
- package/lib/argon2-builtin.js +2 -2
- package/lib/atomic-file.js +53 -53
- package/lib/audit-sign.js +8 -8
- package/lib/audit-tools.js +22 -22
- package/lib/audit.js +29 -17
- package/lib/auth/dpop.js +3 -3
- package/lib/auth/sd-jwt-vc.js +2 -2
- package/lib/backup/bundle.js +17 -17
- package/lib/backup/index.js +36 -36
- package/lib/budr.js +3 -3
- package/lib/bundler.js +20 -20
- package/lib/circuit-breaker.js +24 -9
- package/lib/cli.js +25 -26
- package/lib/cluster.js +2 -2
- package/lib/compliance-sanctions.js +2 -2
- package/lib/config-drift.js +15 -15
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +3 -3
- package/lib/crypto.js +145 -0
- package/lib/daemon.js +19 -19
- package/lib/db-file-lifecycle.js +24 -24
- package/lib/db-schema.js +2 -2
- package/lib/db.js +35 -35
- package/lib/dev.js +10 -10
- package/lib/dr-runbook.js +5 -5
- package/lib/dsr.js +22 -15
- package/lib/dual-control.js +2 -2
- package/lib/external-db-migrate.js +2 -2
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-providers.js +4 -4
- package/lib/gate-contract.js +5 -5
- package/lib/graphql-federation.js +4 -7
- package/lib/honeytoken.js +6 -6
- package/lib/http-client-cookie-jar.js +6 -6
- package/lib/http-client.js +18 -18
- package/lib/i18n.js +5 -5
- package/lib/inbox.js +21 -15
- package/lib/keychain.js +9 -9
- package/lib/legal-hold.js +2 -2
- package/lib/local-db-thin.js +9 -9
- package/lib/log-stream-local.js +17 -17
- package/lib/log-stream-syslog.js +2 -2
- package/lib/log-stream.js +3 -3
- package/lib/mail-bounce.js +2 -2
- package/lib/mail-mdn.js +2 -2
- package/lib/mail-srs.js +2 -2
- package/lib/mail.js +4 -4
- package/lib/mcp.js +2 -2
- package/lib/metrics.js +249 -2
- package/lib/middleware/api-encrypt.js +16 -16
- package/lib/middleware/body-parser.js +16 -16
- package/lib/middleware/compression.js +3 -3
- package/lib/middleware/csp-nonce.js +4 -4
- package/lib/middleware/health.js +7 -7
- package/lib/middleware/idempotency-key.js +250 -0
- package/lib/migrations.js +3 -3
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-dns.js +2 -2
- package/lib/network-nts.js +2 -2
- package/lib/network-proxy.js +3 -3
- package/lib/network-smtp-policy.js +2 -2
- package/lib/network-tls.js +17 -17
- package/lib/network.js +13 -13
- package/lib/notify.js +3 -3
- package/lib/object-store/gcs-bucket-ops.js +2 -2
- package/lib/object-store/gcs.js +5 -5
- package/lib/object-store/index.js +6 -6
- package/lib/object-store/local.js +19 -19
- package/lib/object-store/sigv4.js +3 -3
- package/lib/observability-tracer.js +4 -4
- package/lib/otel-export.js +3 -3
- package/lib/pagination.js +5 -5
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/pqc-agent.js +116 -26
- package/lib/pqc-gate.js +5 -5
- package/lib/pubsub-redis.js +2 -2
- package/lib/queue-local.js +3 -3
- package/lib/queue.js +2 -2
- package/lib/redis-client.js +4 -4
- package/lib/restore-bundle.js +18 -18
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/retry.js +50 -0
- package/lib/router.js +13 -13
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +4 -4
- package/lib/self-update-standalone-verifier.js +280 -0
- package/lib/self-update.js +32 -26
- package/lib/session-device-binding.js +2 -2
- package/lib/static.js +22 -22
- package/lib/template.js +19 -19
- package/lib/testing.js +7 -7
- package/lib/tls-exporter.js +5 -5
- package/lib/tracing.js +3 -3
- package/lib/vault/index.js +11 -11
- package/lib/vault/passphrase-ops.js +37 -37
- package/lib/vault/passphrase-source.js +2 -2
- package/lib/vault/rotate.js +70 -66
- package/lib/vault/seal-pem-file.js +26 -26
- package/lib/watcher.js +23 -23
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +4 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
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
|
|
37
|
+
var nodeFs = require("node:fs");
|
|
38
38
|
var os = require("node:os");
|
|
39
|
-
var
|
|
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
|
|
48
|
-
var
|
|
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 (
|
|
90
|
-
return
|
|
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
|
|
434
|
+
// code never reaches this nodePath.
|
|
436
435
|
if (!modulePath) {
|
|
437
|
-
var root =
|
|
438
|
-
return require(
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
786
|
-
bundleId:
|
|
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:
|
|
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 =
|
|
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 =
|
|
1249
|
-
"blamejs-backup-verify-" +
|
|
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 {
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
486
|
+
var nodeCrypto = require("crypto");
|
|
487
487
|
var ids = index.map(function (e) { return e.id; }).sort();
|
|
488
|
-
var hash =
|
|
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,
|
package/lib/config-drift.js
CHANGED
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
* @card
|
|
36
36
|
* Monitor + alert when runtime config diverges from a declared baseline.
|
|
37
37
|
*/
|
|
38
|
-
var
|
|
39
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 (!
|
|
175
|
+
if (!nodeFs.existsSync(sidecarPath)) return null;
|
|
176
176
|
var raw;
|
|
177
|
-
try { raw =
|
|
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
|
-
|
|
204
|
-
|
|
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 ||
|
|
370
|
-
var manifestPath = opts.manifestPath ||
|
|
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 =
|
|
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 =
|
|
392
|
+
var abs = nodePath.isAbsolute(rel) ? rel : nodePath.join(process.cwd(), rel);
|
|
393
393
|
var actual;
|
|
394
394
|
try {
|
|
395
|
-
var bytes =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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([
|
package/lib/credential-hash.js
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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
|
|
41
|
-
var
|
|
42
|
-
var
|
|
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 =
|
|
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
|
-
|
|
121
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.timeoutMs,
|
|
122
122
|
"daemon.stop: opts.timeoutMs", DaemonError, "daemon/bad-timeout");
|
|
123
|
-
|
|
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 {
|
|
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(
|
|
150
|
-
var fd =
|
|
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
|
|
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 {
|
|
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")
|
|
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(
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
});
|