@blamejs/core 0.9.14 → 0.9.16
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 +2 -0
- package/lib/a2a-tasks.js +2 -2
- 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 +6 -6
- 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/auth/dpop.js +3 -3
- package/lib/auth/elevation-grant.js +3 -3
- package/lib/auth/fido-mds3.js +6 -6
- package/lib/auth/jwt-external.js +2 -2
- 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 +4 -4
- package/lib/cli.js +25 -26
- package/lib/cluster.js +2 -2
- package/lib/compliance-sanctions.js +2 -2
- package/lib/compliance.js +6 -7
- package/lib/config-drift.js +15 -15
- package/lib/config.js +6 -6
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +7 -7
- package/lib/crypto-field.js +9 -9
- 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 +34 -34
- package/lib/dev.js +10 -10
- package/lib/dr-runbook.js +5 -5
- package/lib/dual-control.js +2 -2
- package/lib/external-db-migrate.js +17 -17
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-evaluation-context.js +2 -2
- 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/keychain.js +5 -5
- 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/log.js +2 -2
- 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 +7 -7
- package/lib/mcp-tool-registry.js +6 -6
- package/lib/mcp.js +2 -2
- package/lib/metrics.js +2 -2
- package/lib/middleware/api-encrypt.js +16 -16
- package/lib/middleware/body-parser.js +18 -18
- 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 +163 -63
- package/lib/middleware/require-bound-key.js +4 -4
- package/lib/middleware/require-mtls.js +4 -4
- package/lib/migrations.js +5 -5
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-byte-quota.js +2 -2
- 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 +25 -25
- package/lib/notify.js +11 -11
- 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-env.js +3 -3
- package/lib/parsers/safe-xml.js +3 -3
- 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 +17 -17
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/router.js +25 -25
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +6 -6
- package/lib/self-update.js +18 -18
- package/lib/session-device-binding.js +2 -2
- package/lib/static.js +22 -22
- package/lib/template.js +19 -19
- package/lib/testing.js +9 -9
- 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 +64 -64
- package/lib/vault/seal-pem-file.js +26 -26
- package/lib/vault-aad.js +5 -5
- package/lib/watcher.js +22 -22
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +6 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/atomic-file.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* Every write goes through the same crash-safe sequence:
|
|
12
12
|
* 1. write payload to a sibling temp file (`<filepath>.tmp-<token>`)
|
|
13
13
|
* 2. fsync the file descriptor before close
|
|
14
|
-
* 3.
|
|
15
|
-
* is atomic on the same filesystem; on Windows,
|
|
14
|
+
* 3. nodeFs.rename() the temp file over the destination — POSIX rename
|
|
15
|
+
* is atomic on the same filesystem; on Windows, nodeFs.rename uses
|
|
16
16
|
* MoveFileEx with REPLACE_EXISTING for the same guarantee
|
|
17
17
|
* 4. fsync the parent directory so the rename itself is durable
|
|
18
18
|
*
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
* @card
|
|
39
39
|
* Atomic file I/O with integrity verification, retry on transient errors, and cross-process locking.
|
|
40
40
|
*/
|
|
41
|
-
var
|
|
42
|
-
var
|
|
41
|
+
var nodeFs = require("fs");
|
|
42
|
+
var nodePath = require("path");
|
|
43
43
|
var { generateToken, sha3Hash } = require("./crypto");
|
|
44
44
|
var safeJson = require("./safe-json");
|
|
45
45
|
var C = require("./constants");
|
|
@@ -47,7 +47,7 @@ var { boot } = require("./log");
|
|
|
47
47
|
var safeBuffer = require("./safe-buffer");
|
|
48
48
|
var numericBounds = require("./numeric-bounds");
|
|
49
49
|
var safeAsync = require("./safe-async");
|
|
50
|
-
var
|
|
50
|
+
var retryHelper = require("./retry");
|
|
51
51
|
var { FrameworkError } = require("./framework-error");
|
|
52
52
|
|
|
53
53
|
var log = boot("atomic-file");
|
|
@@ -89,7 +89,7 @@ function _isFsRetryable(e) {
|
|
|
89
89
|
|
|
90
90
|
async function _withRetry(fn, opts) {
|
|
91
91
|
opts = Object.assign({}, DEFAULTS, opts || {});
|
|
92
|
-
return
|
|
92
|
+
return retryHelper.withRetry(function () { return fn(); }, {
|
|
93
93
|
maxAttempts: opts.retryAttempts,
|
|
94
94
|
baseDelayMs: opts.retryBaseMs,
|
|
95
95
|
maxDelayMs: opts.retryMaxMs,
|
|
@@ -114,7 +114,7 @@ async function _withRetry(fn, opts) {
|
|
|
114
114
|
* @status stable
|
|
115
115
|
* @related b.atomicFile.fsyncDir, b.atomicFile.write
|
|
116
116
|
*
|
|
117
|
-
* Best-effort
|
|
117
|
+
* Best-effort nodeFs.fsyncSync wrapper. Silently swallows errors because
|
|
118
118
|
* not every platform / fd type supports fsync (some FUSE mounts, some
|
|
119
119
|
* device fds). Use this when you want the durability hint but don't
|
|
120
120
|
* want a non-fsyncable target to crash the caller.
|
|
@@ -124,10 +124,10 @@ async function _withRetry(fn, opts) {
|
|
|
124
124
|
* var fd = fs.openSync("/tmp/note.txt", "w");
|
|
125
125
|
* fs.writeSync(fd, "hello\n");
|
|
126
126
|
* b.atomicFile.fsync(fd);
|
|
127
|
-
*
|
|
127
|
+
* nodeFs.closeSync(fd);
|
|
128
128
|
*/
|
|
129
129
|
function fsync(fd) {
|
|
130
|
-
try {
|
|
130
|
+
try { nodeFs.fsyncSync(fd); } catch (_e) { /* not all platforms support fsync on every fd type */ }
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
@@ -147,9 +147,9 @@ function fsync(fd) {
|
|
|
147
147
|
*/
|
|
148
148
|
function fsyncDir(dirPath) {
|
|
149
149
|
try {
|
|
150
|
-
var fd =
|
|
151
|
-
try {
|
|
152
|
-
finally {
|
|
150
|
+
var fd = nodeFs.openSync(dirPath, "r");
|
|
151
|
+
try { nodeFs.fsyncSync(fd); } catch (_e) { /* Windows rejects directory fsync */ }
|
|
152
|
+
finally { nodeFs.closeSync(fd); }
|
|
153
153
|
} catch (_e) { /* dir fsync is best-effort across filesystems */ }
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -180,7 +180,7 @@ function ensureDir(dirPath, mode) {
|
|
|
180
180
|
if (typeof dirPath !== "string" || dirPath.length === 0) {
|
|
181
181
|
throw new AtomicFileError("ensureDir: path must be a non-empty string", "atomic-file/bad-path");
|
|
182
182
|
}
|
|
183
|
-
|
|
183
|
+
nodeFs.mkdirSync(dirPath, { recursive: true, mode: typeof mode === "number" ? mode : 0o700 });
|
|
184
184
|
return dirPath;
|
|
185
185
|
}
|
|
186
186
|
|
|
@@ -217,29 +217,29 @@ function copyDirRecursive(src, dest, opts) {
|
|
|
217
217
|
if (typeof dest !== "string" || dest.length === 0) {
|
|
218
218
|
throw new AtomicFileError("copyDirRecursive: dest must be a non-empty string", "atomic-file/bad-path");
|
|
219
219
|
}
|
|
220
|
-
if (!
|
|
220
|
+
if (!nodeFs.existsSync(src)) {
|
|
221
221
|
throw new AtomicFileError("copyDirRecursive: src does not exist: " + src, "atomic-file/missing-src");
|
|
222
222
|
}
|
|
223
223
|
opts = opts || {};
|
|
224
224
|
var dirMode = typeof opts.dirMode === "number" ? opts.dirMode : 0o700;
|
|
225
225
|
var overwrite = !!opts.overwrite;
|
|
226
|
-
var copyFlags = overwrite ? 0 :
|
|
226
|
+
var copyFlags = overwrite ? 0 : nodeFs.constants.COPYFILE_EXCL;
|
|
227
227
|
|
|
228
228
|
ensureDir(dest, dirMode);
|
|
229
|
-
var entries =
|
|
229
|
+
var entries = nodeFs.readdirSync(src, { withFileTypes: true });
|
|
230
230
|
var fileCount = 0;
|
|
231
231
|
var byteCount = 0;
|
|
232
232
|
for (var i = 0; i < entries.length; i++) {
|
|
233
233
|
var name = entries[i].name;
|
|
234
|
-
var s =
|
|
235
|
-
var d =
|
|
234
|
+
var s = nodePath.join(src, name);
|
|
235
|
+
var d = nodePath.join(dest, name);
|
|
236
236
|
if (entries[i].isDirectory()) {
|
|
237
237
|
var sub = copyDirRecursive(s, d, opts);
|
|
238
238
|
fileCount += sub.fileCount;
|
|
239
239
|
byteCount += sub.byteCount;
|
|
240
240
|
} else if (entries[i].isFile()) {
|
|
241
|
-
|
|
242
|
-
try { byteCount +=
|
|
241
|
+
nodeFs.copyFileSync(s, d, copyFlags);
|
|
242
|
+
try { byteCount += nodeFs.statSync(d).size; } catch (_e) { /* size best-effort */ }
|
|
243
243
|
fileCount++;
|
|
244
244
|
}
|
|
245
245
|
// Symlinks, sockets, devices: deliberately skipped
|
|
@@ -308,30 +308,30 @@ function writeSync(filepath, data, opts) {
|
|
|
308
308
|
typeMessage: "data must be Buffer, Uint8Array, or string",
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
-
var dir =
|
|
312
|
-
if (!
|
|
311
|
+
var dir = nodePath.dirname(filepath);
|
|
312
|
+
if (!nodeFs.existsSync(dir)) nodeFs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
313
313
|
|
|
314
314
|
var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
|
|
315
315
|
var renamed = false;
|
|
316
316
|
try {
|
|
317
|
-
var fd =
|
|
317
|
+
var fd = nodeFs.openSync(tmpPath, "w", opts.fileMode);
|
|
318
318
|
try {
|
|
319
319
|
var pos = 0;
|
|
320
320
|
while (pos < buf.length) {
|
|
321
|
-
pos +=
|
|
321
|
+
pos += nodeFs.writeSync(fd, buf, pos, buf.length - pos, null);
|
|
322
322
|
}
|
|
323
323
|
_fsync(fd);
|
|
324
324
|
} finally {
|
|
325
|
-
try {
|
|
325
|
+
try { nodeFs.closeSync(fd); } catch (_e) { /* already closed? */ }
|
|
326
326
|
}
|
|
327
|
-
|
|
327
|
+
nodeFs.renameSync(tmpPath, filepath);
|
|
328
328
|
renamed = true;
|
|
329
329
|
_fsyncDir(dir);
|
|
330
330
|
} finally {
|
|
331
331
|
if (!renamed) {
|
|
332
332
|
// Either the write or the rename failed — remove the tmp so the next
|
|
333
333
|
// boot doesn't see a leaked partial file.
|
|
334
|
-
try {
|
|
334
|
+
try { nodeFs.unlinkSync(tmpPath); } catch (_e) { /* may not exist */ }
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
@@ -354,7 +354,7 @@ function writeSync(filepath, data, opts) {
|
|
|
354
354
|
* predict — only glob-by-prefix and prune by age. Operators should
|
|
355
355
|
* call this at boot for every "important" filepath (vault.key.sealed,
|
|
356
356
|
* audit-sign.key.sealed, db.enc, ...) BEFORE the first atomic write
|
|
357
|
-
* to that
|
|
357
|
+
* to that nodePath. Returns the number of orphans removed.
|
|
358
358
|
*
|
|
359
359
|
* @opts
|
|
360
360
|
* olderThanMs: 300000, // only prune temp files older than this many ms (default 5 minutes)
|
|
@@ -369,8 +369,8 @@ function writeSync(filepath, data, opts) {
|
|
|
369
369
|
function cleanOrphans(filepath, opts) {
|
|
370
370
|
opts = opts || {};
|
|
371
371
|
var olderThanMs = opts.olderThanMs != null ? opts.olderThanMs : C.TIME.minutes(5);
|
|
372
|
-
var dir =
|
|
373
|
-
var basename =
|
|
372
|
+
var dir = nodePath.dirname(filepath);
|
|
373
|
+
var basename = nodePath.basename(filepath);
|
|
374
374
|
var prefix = basename + ".tmp-";
|
|
375
375
|
var nowMs = Date.now();
|
|
376
376
|
var removed = 0;
|
|
@@ -382,7 +382,7 @@ function cleanOrphans(filepath, opts) {
|
|
|
382
382
|
var entry = entries[i];
|
|
383
383
|
try {
|
|
384
384
|
if (nowMs - entry.mtimeMs >= olderThanMs) {
|
|
385
|
-
|
|
385
|
+
nodeFs.unlinkSync(entry.fullPath);
|
|
386
386
|
removed += 1;
|
|
387
387
|
}
|
|
388
388
|
} catch (_e) { /* concurrent cleanup or permission — best effort */ }
|
|
@@ -433,24 +433,24 @@ async function write(filepath, data, opts) {
|
|
|
433
433
|
|
|
434
434
|
return await _withRetry(function () {
|
|
435
435
|
return new Promise(function (resolve, reject) {
|
|
436
|
-
var dir =
|
|
437
|
-
if (!
|
|
436
|
+
var dir = nodePath.dirname(filepath);
|
|
437
|
+
if (!nodeFs.existsSync(dir)) nodeFs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
438
438
|
var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
|
|
439
439
|
var renamed = false;
|
|
440
440
|
try {
|
|
441
|
-
var fd =
|
|
441
|
+
var fd = nodeFs.openSync(tmpPath, "w", opts.fileMode);
|
|
442
442
|
try {
|
|
443
443
|
var pos = 0;
|
|
444
444
|
while (pos < buf.length) {
|
|
445
|
-
pos +=
|
|
445
|
+
pos += nodeFs.writeSync(fd, buf, pos, buf.length - pos, null);
|
|
446
446
|
}
|
|
447
447
|
_fsync(fd);
|
|
448
448
|
} finally {
|
|
449
|
-
try {
|
|
449
|
+
try { nodeFs.closeSync(fd); } catch (_e) { /* already closed? */ }
|
|
450
450
|
}
|
|
451
451
|
// Atomic rename — POSIX rename is atomic on the same FS; on Windows,
|
|
452
|
-
//
|
|
453
|
-
|
|
452
|
+
// nodeFs.renameSync uses MoveFileEx with REPLACE_EXISTING.
|
|
453
|
+
nodeFs.renameSync(tmpPath, filepath);
|
|
454
454
|
renamed = true;
|
|
455
455
|
_fsyncDir(dir);
|
|
456
456
|
var hash = opts.computeHash ? sha3Hash(buf) : null;
|
|
@@ -459,7 +459,7 @@ async function write(filepath, data, opts) {
|
|
|
459
459
|
reject(e);
|
|
460
460
|
} finally {
|
|
461
461
|
if (!renamed) {
|
|
462
|
-
try {
|
|
462
|
+
try { nodeFs.unlinkSync(tmpPath); } catch (_e) { /* may not exist */ }
|
|
463
463
|
}
|
|
464
464
|
}
|
|
465
465
|
});
|
|
@@ -560,20 +560,20 @@ function _validateMaxBytes(maxBytes) {
|
|
|
560
560
|
}
|
|
561
561
|
|
|
562
562
|
function _readSyncCore(filepath, opts) {
|
|
563
|
-
if (!
|
|
563
|
+
if (!nodeFs.existsSync(filepath)) {
|
|
564
564
|
var e = new AtomicFileError("file not found: " + filepath, "atomic-file/not-found");
|
|
565
565
|
e.code = "ENOENT";
|
|
566
566
|
throw e;
|
|
567
567
|
}
|
|
568
568
|
_validateMaxBytes(opts.maxBytes);
|
|
569
|
-
var stat =
|
|
569
|
+
var stat = nodeFs.statSync(filepath);
|
|
570
570
|
if (stat.size > opts.maxBytes) {
|
|
571
571
|
throw new AtomicFileError(
|
|
572
572
|
"file size " + stat.size + " > maxBytes " + opts.maxBytes,
|
|
573
573
|
"atomic-file/too-large"
|
|
574
574
|
);
|
|
575
575
|
}
|
|
576
|
-
var buf =
|
|
576
|
+
var buf = nodeFs.readFileSync(filepath);
|
|
577
577
|
if (opts.expectedHash) {
|
|
578
578
|
var actual = sha3Hash(buf);
|
|
579
579
|
if (actual !== opts.expectedHash) {
|
|
@@ -706,7 +706,7 @@ async function copy(src, dst, opts) {
|
|
|
706
706
|
* @status stable
|
|
707
707
|
* @related b.atomicFile.read, b.atomicFile.readSync
|
|
708
708
|
*
|
|
709
|
-
* Synchronous existence check. Thin wrapper over `
|
|
709
|
+
* Synchronous existence check. Thin wrapper over `nodeFs.existsSync` that
|
|
710
710
|
* normalises the answer for callers that already require this module
|
|
711
711
|
* — saves an additional `require("fs")` in modules that otherwise
|
|
712
712
|
* only need atomicFile.
|
|
@@ -717,7 +717,7 @@ async function copy(src, dst, opts) {
|
|
|
717
717
|
* }
|
|
718
718
|
*/
|
|
719
719
|
function exists(filepath) {
|
|
720
|
-
return
|
|
720
|
+
return nodeFs.existsSync(filepath);
|
|
721
721
|
}
|
|
722
722
|
|
|
723
723
|
/**
|
|
@@ -767,16 +767,16 @@ async function lock(filepath, fn, opts) {
|
|
|
767
767
|
while (Date.now() < deadline) {
|
|
768
768
|
try {
|
|
769
769
|
// O_CREAT | O_EXCL — fails if file exists
|
|
770
|
-
fd =
|
|
770
|
+
fd = nodeFs.openSync(lockPath, "wx", opts.fileMode);
|
|
771
771
|
break;
|
|
772
772
|
} catch (e) {
|
|
773
773
|
if (e.code !== "EEXIST") throw e;
|
|
774
774
|
// Stale lock detection: if the .lock file is older than 5 minutes,
|
|
775
775
|
// assume the holding process crashed and remove it.
|
|
776
776
|
try {
|
|
777
|
-
var stat =
|
|
777
|
+
var stat = nodeFs.statSync(lockPath);
|
|
778
778
|
if (Date.now() - stat.mtimeMs > C.TIME.minutes(5)) {
|
|
779
|
-
try {
|
|
779
|
+
try { nodeFs.unlinkSync(lockPath); }
|
|
780
780
|
catch (uerr) { log.debug("stale-lock unlink failed", { path: lockPath, error: uerr.message }); }
|
|
781
781
|
continue;
|
|
782
782
|
}
|
|
@@ -791,7 +791,7 @@ async function lock(filepath, fn, opts) {
|
|
|
791
791
|
);
|
|
792
792
|
}
|
|
793
793
|
try {
|
|
794
|
-
|
|
794
|
+
nodeFs.writeSync(fd, Buffer.from(JSON.stringify({
|
|
795
795
|
pid: process.pid,
|
|
796
796
|
acquiredAt: Date.now(),
|
|
797
797
|
}), "utf8"));
|
|
@@ -801,9 +801,9 @@ async function lock(filepath, fn, opts) {
|
|
|
801
801
|
try {
|
|
802
802
|
return await fn();
|
|
803
803
|
} finally {
|
|
804
|
-
try {
|
|
804
|
+
try { nodeFs.closeSync(fd); }
|
|
805
805
|
catch (cerr) { log.debug("lock fd close failed", { error: cerr.message }); }
|
|
806
|
-
try {
|
|
806
|
+
try { nodeFs.unlinkSync(lockPath); }
|
|
807
807
|
catch (uerr) { log.debug("lock release unlink failed", { path: lockPath, error: uerr.message }); }
|
|
808
808
|
}
|
|
809
809
|
}
|
|
@@ -848,7 +848,7 @@ function listDir(dir, opts) {
|
|
|
848
848
|
|
|
849
849
|
var entries;
|
|
850
850
|
try {
|
|
851
|
-
entries =
|
|
851
|
+
entries = nodeFs.readdirSync(dir);
|
|
852
852
|
} catch (e) {
|
|
853
853
|
if (missingOk && e.code === "ENOENT") return [];
|
|
854
854
|
throw new AtomicFileError(
|
|
@@ -861,11 +861,11 @@ function listDir(dir, opts) {
|
|
|
861
861
|
for (var i = 0; i < entries.length; i++) {
|
|
862
862
|
var name = entries[i];
|
|
863
863
|
if (filter && !filter(name)) continue;
|
|
864
|
-
var fullPath =
|
|
864
|
+
var fullPath = nodePath.join(dir, name);
|
|
865
865
|
var entry = { name: name, fullPath: fullPath };
|
|
866
866
|
if (includeStat) {
|
|
867
867
|
try {
|
|
868
|
-
var stat =
|
|
868
|
+
var stat = nodeFs.statSync(fullPath);
|
|
869
869
|
entry.mtimeMs = stat.mtimeMs;
|
|
870
870
|
entry.sizeBytes = stat.size;
|
|
871
871
|
entry.isDirectory = stat.isDirectory();
|
package/lib/audit-sign.js
CHANGED
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
* @card
|
|
59
59
|
* SLH-DSA-SHAKE-256f post-quantum signature for audit-chain checkpoints.
|
|
60
60
|
*/
|
|
61
|
-
var
|
|
62
|
-
var
|
|
61
|
+
var nodeFs = require("fs");
|
|
62
|
+
var nodePath = require("path");
|
|
63
63
|
var nodeCrypto = require("crypto");
|
|
64
64
|
var atomicFile = require("./atomic-file");
|
|
65
65
|
var { sha3Hash } = require("./crypto");
|
|
@@ -118,8 +118,8 @@ var log = boot("audit-sign");
|
|
|
118
118
|
function resolvePaths(dataDir) {
|
|
119
119
|
return {
|
|
120
120
|
dataDir: dataDir,
|
|
121
|
-
plaintext:
|
|
122
|
-
sealed:
|
|
121
|
+
plaintext: nodePath.join(dataDir, "audit-sign.key"),
|
|
122
|
+
sealed: nodePath.join(dataDir, "audit-sign.key.sealed"),
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -200,13 +200,13 @@ async function init(opts) {
|
|
|
200
200
|
currentMode = mode;
|
|
201
201
|
paths = resolvePaths(opts.dataDir);
|
|
202
202
|
|
|
203
|
-
if (!
|
|
203
|
+
if (!nodeFs.existsSync(paths.dataDir)) nodeFs.mkdirSync(paths.dataDir, { recursive: true });
|
|
204
204
|
// Sweep tmp files from any prior crashed write
|
|
205
205
|
atomicFile.cleanOrphans(paths.sealed);
|
|
206
206
|
atomicFile.cleanOrphans(paths.plaintext);
|
|
207
207
|
|
|
208
|
-
var hasPlaintext =
|
|
209
|
-
var hasSealed =
|
|
208
|
+
var hasPlaintext = nodeFs.existsSync(paths.plaintext);
|
|
209
|
+
var hasSealed = nodeFs.existsSync(paths.sealed);
|
|
210
210
|
if (hasPlaintext && hasSealed) {
|
|
211
211
|
throw _err("KEY_FILE_CONFLICT",
|
|
212
212
|
"both audit-sign.key and audit-sign.key.sealed exist; resolve manually");
|
|
@@ -233,7 +233,7 @@ async function init(opts) {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
function _initPlaintext() {
|
|
236
|
-
if (
|
|
236
|
+
if (nodeFs.existsSync(paths.plaintext)) {
|
|
237
237
|
var loaded;
|
|
238
238
|
try { loaded = safeJson.parse(atomicFile.readSync(paths.plaintext), { schema: SIGNING_KEY_SCHEMA }); }
|
|
239
239
|
catch (e) {
|
package/lib/audit-tools.js
CHANGED
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
* Operator-side audit-chain inspection / export — verify chain integrity end-to-end, export RFC 8785 canonical-JSON slices, format rows for downstream SIEM (CADF / ISO 19395), and generate tamper-evident compliance-evidence bundles auditors can verify off-line.
|
|
55
55
|
*/
|
|
56
56
|
|
|
57
|
-
var
|
|
58
|
-
var
|
|
57
|
+
var nodeFs = require("fs");
|
|
58
|
+
var nodePath = require("path");
|
|
59
59
|
var pkg = require("../package.json");
|
|
60
60
|
var atomicFile = require("./atomic-file");
|
|
61
61
|
var auditChain = require("./audit-chain");
|
|
@@ -65,7 +65,7 @@ var backupCrypto = require("./backup/crypto");
|
|
|
65
65
|
var clusterStorage = require("./cluster-storage");
|
|
66
66
|
var lazyRequire = require("./lazy-require");
|
|
67
67
|
var validateOpts = require("./validate-opts");
|
|
68
|
-
var
|
|
68
|
+
var safeJson = require("./safe-json");
|
|
69
69
|
var { defineClass } = require("./framework-error");
|
|
70
70
|
|
|
71
71
|
var FRAMEWORK_VERSION = (pkg && pkg.version) || "unknown";
|
|
@@ -117,7 +117,7 @@ function _requireOutDir(outDir, kind) {
|
|
|
117
117
|
throw new AuditToolsError("audit-tools/no-outdir",
|
|
118
118
|
kind + ": opts.out is required");
|
|
119
119
|
}
|
|
120
|
-
if (
|
|
120
|
+
if (nodeFs.existsSync(outDir)) {
|
|
121
121
|
throw new AuditToolsError("audit-tools/outdir-exists",
|
|
122
122
|
kind + ": out already exists: " + outDir +
|
|
123
123
|
" (refusing to overwrite — pick a fresh path)");
|
|
@@ -128,7 +128,7 @@ function _requireOutDir(outDir, kind) {
|
|
|
128
128
|
// as audit-chain.canonicalize, config-drift._stableStringify, and
|
|
129
129
|
// pagination._canonicalize for the same input. Pre-v0.6.67 each site
|
|
130
130
|
// had its own copy of the walk, all carrying the same silent-loss bug
|
|
131
|
-
// for Date / Buffer / Map / Set / BigInt / circular
|
|
131
|
+
// for Date / Buffer / Map / Set / BigInt / circular renodeFs.
|
|
132
132
|
function _canonicalize(value) { return canonicalJson.stringify(value); }
|
|
133
133
|
|
|
134
134
|
// Convert a single audit_log row to its on-disk-canonical JSON shape.
|
|
@@ -309,14 +309,14 @@ async function _writeBundle(args) {
|
|
|
309
309
|
return JSON.stringify(_rowToWireForm(r));
|
|
310
310
|
}).join("\n") + "\n";
|
|
311
311
|
var rowsEnc = await backupCrypto.encryptWithFreshSalt(jsonl, passphrase);
|
|
312
|
-
atomicFile.writeSync(
|
|
312
|
+
atomicFile.writeSync(nodePath.join(outDir, "rows.enc"), rowsEnc.encrypted, { fileMode: 0o600 });
|
|
313
313
|
|
|
314
314
|
// 2. (archive) Encrypt the checkpoint JSON
|
|
315
315
|
var checkpointSalt = null;
|
|
316
316
|
if (checkpoint) {
|
|
317
317
|
var ckptJson = _canonicalize(_rowToWireForm(checkpoint));
|
|
318
318
|
var ckptEnc = await backupCrypto.encryptWithFreshSalt(ckptJson, passphrase);
|
|
319
|
-
atomicFile.writeSync(
|
|
319
|
+
atomicFile.writeSync(nodePath.join(outDir, "checkpoint.enc"), ckptEnc.encrypted, { fileMode: 0o600 });
|
|
320
320
|
checkpointSalt = ckptEnc.salt;
|
|
321
321
|
}
|
|
322
322
|
|
|
@@ -343,7 +343,7 @@ async function _writeBundle(args) {
|
|
|
343
343
|
checksum: {
|
|
344
344
|
rowsSha3_512: backupCrypto.checksum(rowsEnc.encrypted),
|
|
345
345
|
checkpointSha3_512: checkpointSalt
|
|
346
|
-
? backupCrypto.checksum(
|
|
346
|
+
? backupCrypto.checksum(nodeFs.readFileSync(nodePath.join(outDir, "checkpoint.enc")))
|
|
347
347
|
: null,
|
|
348
348
|
},
|
|
349
349
|
};
|
|
@@ -355,7 +355,7 @@ async function _writeBundle(args) {
|
|
|
355
355
|
checkpointId: String(checkpoint._id),
|
|
356
356
|
};
|
|
357
357
|
}
|
|
358
|
-
var manifestPath =
|
|
358
|
+
var manifestPath = nodePath.join(outDir, "manifest.json");
|
|
359
359
|
atomicFile.writeSync(manifestPath, _canonicalize(manifest), { fileMode: 0o600 });
|
|
360
360
|
return { manifest: manifest, manifestPath: manifestPath };
|
|
361
361
|
}
|
|
@@ -363,16 +363,16 @@ async function _writeBundle(args) {
|
|
|
363
363
|
// ---- Bundle reader ----
|
|
364
364
|
|
|
365
365
|
async function _readBundle(inDir, passphrase) {
|
|
366
|
-
if (typeof inDir !== "string" || !
|
|
366
|
+
if (typeof inDir !== "string" || !nodeFs.existsSync(inDir)) {
|
|
367
367
|
throw new AuditToolsError("audit-tools/no-bundle",
|
|
368
368
|
"bundle directory does not exist: " + inDir);
|
|
369
369
|
}
|
|
370
|
-
var manifestPath =
|
|
371
|
-
if (!
|
|
370
|
+
var manifestPath = nodePath.join(inDir, "manifest.json");
|
|
371
|
+
if (!nodeFs.existsSync(manifestPath)) {
|
|
372
372
|
throw new AuditToolsError("audit-tools/no-manifest",
|
|
373
373
|
"manifest.json missing in " + inDir);
|
|
374
374
|
}
|
|
375
|
-
var manifest =
|
|
375
|
+
var manifest = safeJson.parse(nodeFs.readFileSync(manifestPath, "utf8"));
|
|
376
376
|
if (!manifest || manifest.format !== BUNDLE_FORMAT) {
|
|
377
377
|
throw new AuditToolsError("audit-tools/bad-format",
|
|
378
378
|
"manifest.format is not " + BUNDLE_FORMAT);
|
|
@@ -382,12 +382,12 @@ async function _readBundle(inDir, passphrase) {
|
|
|
382
382
|
"manifest.kind must be one of " + Object.keys(VALID_KINDS).join(", "));
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
var rowsEncPath =
|
|
386
|
-
if (!
|
|
385
|
+
var rowsEncPath = nodePath.join(inDir, "rows.enc");
|
|
386
|
+
if (!nodeFs.existsSync(rowsEncPath)) {
|
|
387
387
|
throw new AuditToolsError("audit-tools/no-rows-blob",
|
|
388
388
|
"rows.enc missing in " + inDir);
|
|
389
389
|
}
|
|
390
|
-
var rowsEnc =
|
|
390
|
+
var rowsEnc = nodeFs.readFileSync(rowsEncPath);
|
|
391
391
|
if (manifest.checksum && manifest.checksum.rowsSha3_512 &&
|
|
392
392
|
backupCrypto.checksum(rowsEnc) !== manifest.checksum.rowsSha3_512) {
|
|
393
393
|
throw new AuditToolsError("audit-tools/rows-checksum-mismatch",
|
|
@@ -396,16 +396,16 @@ async function _readBundle(inDir, passphrase) {
|
|
|
396
396
|
var rowsPlainBuf = await backupCrypto.decryptWithPassphrase(rowsEnc, passphrase, manifest.salts.rows);
|
|
397
397
|
var rowsPlain = rowsPlainBuf.toString("utf8");
|
|
398
398
|
var lines = rowsPlain.split("\n").filter(function (l) { return l.length > 0; });
|
|
399
|
-
var rows = lines.map(function (l) { return _wireFormToRow(
|
|
399
|
+
var rows = lines.map(function (l) { return _wireFormToRow(safeJson.parse(l)); });
|
|
400
400
|
|
|
401
401
|
var checkpoint = null;
|
|
402
402
|
if (manifest.kind === KIND_ARCHIVE) {
|
|
403
|
-
var ckptPath =
|
|
404
|
-
if (!
|
|
403
|
+
var ckptPath = nodePath.join(inDir, "checkpoint.enc");
|
|
404
|
+
if (!nodeFs.existsSync(ckptPath)) {
|
|
405
405
|
throw new AuditToolsError("audit-tools/no-checkpoint-blob",
|
|
406
406
|
"checkpoint.enc missing in " + inDir + " (archive bundles must include the covering checkpoint)");
|
|
407
407
|
}
|
|
408
|
-
var ckptEnc =
|
|
408
|
+
var ckptEnc = nodeFs.readFileSync(ckptPath);
|
|
409
409
|
if (manifest.checksum && manifest.checksum.checkpointSha3_512 &&
|
|
410
410
|
backupCrypto.checksum(ckptEnc) !== manifest.checksum.checkpointSha3_512) {
|
|
411
411
|
throw new AuditToolsError("audit-tools/checkpoint-checksum-mismatch",
|
|
@@ -413,7 +413,7 @@ async function _readBundle(inDir, passphrase) {
|
|
|
413
413
|
}
|
|
414
414
|
var ckptPlain = (await backupCrypto.decryptWithPassphrase(ckptEnc, passphrase, manifest.salts.checkpoint))
|
|
415
415
|
.toString("utf8");
|
|
416
|
-
checkpoint = _wireFormToRow(
|
|
416
|
+
checkpoint = _wireFormToRow(safeJson.parse(ckptPlain));
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
return { manifest: manifest, rows: rows, checkpoint: checkpoint };
|
|
@@ -956,7 +956,7 @@ function _toCadfOutcome(outcome) {
|
|
|
956
956
|
function _toCadfEvent(row) {
|
|
957
957
|
var meta = null;
|
|
958
958
|
if (row.metadata) {
|
|
959
|
-
try { meta = typeof row.metadata === "string" ?
|
|
959
|
+
try { meta = typeof row.metadata === "string" ? safeJson.parse(row.metadata) : row.metadata; }
|
|
960
960
|
catch (_e) { meta = { raw: String(row.metadata) }; }
|
|
961
961
|
}
|
|
962
962
|
var ev = {
|
package/lib/auth/dpop.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
var nodeCrypto = require("crypto");
|
|
30
|
-
var
|
|
30
|
+
var bCrypto = require("../crypto");
|
|
31
31
|
var safeJson = require("../safe-json");
|
|
32
32
|
var safeUrl = require("../safe-url");
|
|
33
33
|
var validateOpts = require("../validate-opts");
|
|
@@ -417,7 +417,7 @@ async function verify(proof, opts) {
|
|
|
417
417
|
// Compute thumbprint for downstream binding (jkt → access-token cnf claim)
|
|
418
418
|
var jkt = thumbprint(header.jwk);
|
|
419
419
|
if (typeof opts.expectedThumbprint === "string" && opts.expectedThumbprint.length > 0) {
|
|
420
|
-
if (!
|
|
420
|
+
if (!bCrypto.timingSafeEqual(jkt, opts.expectedThumbprint)) {
|
|
421
421
|
throw new AuthError("auth-dpop/thumbprint-mismatch",
|
|
422
422
|
"proof key thumbprint does not match expected");
|
|
423
423
|
}
|
|
@@ -461,7 +461,7 @@ async function verify(proof, opts) {
|
|
|
461
461
|
throw new AuthError("auth-dpop/missing-ath",
|
|
462
462
|
"accessToken supplied but proof has no ath claim");
|
|
463
463
|
}
|
|
464
|
-
if (!
|
|
464
|
+
if (!bCrypto.timingSafeEqual(payload.ath, expectedAth)) {
|
|
465
465
|
throw new AuthError("auth-dpop/ath-mismatch",
|
|
466
466
|
"payload.ath does not match SHA-256 of access token");
|
|
467
467
|
}
|
|
@@ -36,7 +36,7 @@ var C = require("../constants");
|
|
|
36
36
|
var { AuthError } = require("../framework-error");
|
|
37
37
|
|
|
38
38
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
39
|
-
var
|
|
39
|
+
var bCrypto = lazyRequire(function () { return require("../crypto"); });
|
|
40
40
|
|
|
41
41
|
var DEFAULT_TTL_SEC = C.TIME.minutes(15) / C.TIME.seconds(1);
|
|
42
42
|
var MAX_TTL_SEC = C.TIME.hours(1) / C.TIME.seconds(1);
|
|
@@ -82,7 +82,7 @@ function _macFor(payloadB64) {
|
|
|
82
82
|
|
|
83
83
|
function _timingSafeEqualBuf(a, b) {
|
|
84
84
|
if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) return false;
|
|
85
|
-
return
|
|
85
|
+
return bCrypto().timingSafeEqual(a, b);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function create(opts) {
|
|
@@ -128,7 +128,7 @@ function create(opts) {
|
|
|
128
128
|
}
|
|
129
129
|
var nowSec = (typeof opts.now === "number" && isFinite(opts.now))
|
|
130
130
|
? opts.now : Math.floor(Date.now() / C.TIME.seconds(1));
|
|
131
|
-
var jti =
|
|
131
|
+
var jti = bCrypto().generateBytes(C.BYTES.bytes(16)).toString("base64url");
|
|
132
132
|
var payload = {
|
|
133
133
|
sub: opts.subject,
|
|
134
134
|
scope: opts.scope,
|
package/lib/auth/fido-mds3.js
CHANGED
|
@@ -47,8 +47,8 @@ var _wa = require("../vendor/simplewebauthn-server.cjs");
|
|
|
47
47
|
var { FidoMds3Error } = require("../framework-error");
|
|
48
48
|
|
|
49
49
|
var httpClient = lazyRequire(function () { return require("../http-client"); });
|
|
50
|
-
var
|
|
51
|
-
var
|
|
50
|
+
var cache = lazyRequire(function () { return require("../cache"); });
|
|
51
|
+
var audit = lazyRequire(function () { return require("../audit"); });
|
|
52
52
|
|
|
53
53
|
var DEFAULT_URL = "https://mds3.fidoalliance.org/";
|
|
54
54
|
var DEFAULT_TIMEOUT_MS = C.TIME.seconds(30);
|
|
@@ -289,7 +289,7 @@ function _verifyJws(jws, leafCert) {
|
|
|
289
289
|
var _sharedCache = null;
|
|
290
290
|
function _getCache() {
|
|
291
291
|
if (_sharedCache) return _sharedCache;
|
|
292
|
-
_sharedCache =
|
|
292
|
+
_sharedCache = cache().create({
|
|
293
293
|
namespace: "auth-fido-mds3.blob",
|
|
294
294
|
ttlMs: MAX_CACHE_TTL_MS,
|
|
295
295
|
maxEntries: 8, // allow:raw-byte-literal — operator-pinned URL set
|
|
@@ -440,7 +440,7 @@ async function fetch(opts) { // allow:raw-outbound-http — function name is f
|
|
|
440
440
|
},
|
|
441
441
|
});
|
|
442
442
|
} catch (e) {
|
|
443
|
-
try {
|
|
443
|
+
try { audit().safeEmit({
|
|
444
444
|
action: "auth.fido_mds3.fetch.network",
|
|
445
445
|
outcome: "failure",
|
|
446
446
|
metadata: { url: url, reason: (e && e.message) || String(e) },
|
|
@@ -482,7 +482,7 @@ async function fetch(opts) { // allow:raw-outbound-http — function name is f
|
|
|
482
482
|
// wrap-call's safe-minimum seed).
|
|
483
483
|
try { await c.set(cacheKey, record, _ttlFromNextUpdate(nextUpdate)); }
|
|
484
484
|
catch (_e) { /* cache.set best-effort */ }
|
|
485
|
-
try {
|
|
485
|
+
try { audit().safeEmit({
|
|
486
486
|
action: "auth.fido_mds3.fetch",
|
|
487
487
|
outcome: "success",
|
|
488
488
|
metadata: { url: url, no: payload.no, entries: payload.entries.length,
|
|
@@ -633,7 +633,7 @@ function verifyAuthenticator(blob, registrationInfo, vopts) {
|
|
|
633
633
|
}
|
|
634
634
|
var certifiedLevel = _certifiedLevel(statusReports);
|
|
635
635
|
if (refusedStatus) {
|
|
636
|
-
try {
|
|
636
|
+
try { audit().safeEmit({
|
|
637
637
|
action: "auth.fido_mds3.verify.refused",
|
|
638
638
|
outcome: "denied",
|
|
639
639
|
metadata: { aaguid: registrationInfo.aaguid, status: refusedStatus },
|
package/lib/auth/jwt-external.js
CHANGED
|
@@ -59,7 +59,7 @@ var { AuthError } = require("../framework-error");
|
|
|
59
59
|
|
|
60
60
|
var httpClient = lazyRequire(function () { return require("../http-client"); });
|
|
61
61
|
var cache = lazyRequire(function () { return require("../cache"); });
|
|
62
|
-
var
|
|
62
|
+
var audit = lazyRequire(function () { return require("../audit"); });
|
|
63
63
|
|
|
64
64
|
// ---- constants ----
|
|
65
65
|
|
|
@@ -271,7 +271,7 @@ async function verifyExternal(token, opts) {
|
|
|
271
271
|
// outright. Operators with JWE need a separate handler wired to
|
|
272
272
|
// their KMS — never a defaulted JWE path on the JWS verifier.
|
|
273
273
|
if (parts.length === 5) {
|
|
274
|
-
try {
|
|
274
|
+
try { audit().safeEmit({
|
|
275
275
|
action: "jwt.jwe.refused",
|
|
276
276
|
outcome: "denied",
|
|
277
277
|
metadata: { reason: "jwe-on-jws-verifier" },
|
package/lib/auth/sd-jwt-vc.js
CHANGED
|
@@ -61,14 +61,14 @@
|
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
63
|
var nodeCrypto = require("node:crypto");
|
|
64
|
-
var
|
|
64
|
+
var bCrypto = require("../crypto");
|
|
65
65
|
var safeBuffer = require("../safe-buffer");
|
|
66
66
|
var safeJson = require("../safe-json");
|
|
67
67
|
var validateOpts = require("../validate-opts");
|
|
68
68
|
|
|
69
69
|
function _timingSafeEqStr(a, b) {
|
|
70
70
|
if (typeof a !== "string" || typeof b !== "string") return false;
|
|
71
|
-
return
|
|
71
|
+
return bCrypto.timingSafeEqual(a, b);
|
|
72
72
|
}
|
|
73
73
|
var disclosure = require("./sd-jwt-vc-disclosure");
|
|
74
74
|
var sdJwtVcIssuer = require("./sd-jwt-vc-issuer");
|