@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/metrics.js
CHANGED
|
@@ -39,9 +39,12 @@
|
|
|
39
39
|
|
|
40
40
|
var C = require("./constants");
|
|
41
41
|
var canonicalJson = require("./canonical-json");
|
|
42
|
+
var nodeFs = require("node:fs");
|
|
43
|
+
var atomicFile = require("./atomic-file");
|
|
44
|
+
var safeJson = require("./safe-json");
|
|
42
45
|
var { defineClass } = require("./framework-error");
|
|
43
46
|
var { boot } = require("./log");
|
|
44
|
-
var
|
|
47
|
+
var numericBounds = require("./numeric-bounds");
|
|
45
48
|
var { resolveRoute, captureResponseStatus, HTTP_STATUS } = require("./request-helpers");
|
|
46
49
|
var validateOpts = require("./validate-opts");
|
|
47
50
|
|
|
@@ -276,7 +279,7 @@ function create(opts) {
|
|
|
276
279
|
], "b.metrics");
|
|
277
280
|
var namespace = opts.namespace || "";
|
|
278
281
|
var defaultLabels = opts.defaultLabels || {};
|
|
279
|
-
|
|
282
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.labelCardinalityCap,
|
|
280
283
|
"labelCardinalityCap", MetricsError, "metrics/bad-opt");
|
|
281
284
|
var cardinalityCap = opts.labelCardinalityCap || DEFAULT_CARDINALITY_CAP;
|
|
282
285
|
|
|
@@ -678,9 +681,253 @@ function _resetForTest() {
|
|
|
678
681
|
_activeTap = null;
|
|
679
682
|
}
|
|
680
683
|
|
|
684
|
+
// ---- Snapshot writer/reader ----
|
|
685
|
+
//
|
|
686
|
+
// Out-of-process metrics export pattern for long-running daemons:
|
|
687
|
+
// the daemon writes a JSON snapshot atomically every N seconds; a
|
|
688
|
+
// separate CLI process reads + renders. Bypasses the HTTP-port +
|
|
689
|
+
// Unix-socket coupling that the regular Prometheus exposition
|
|
690
|
+
// handler requires. Useful for systemd daemons that don't want to
|
|
691
|
+
// bind a stats port at all (operator runs `daemon stats` and the
|
|
692
|
+
// CLI just reads the file).
|
|
693
|
+
//
|
|
694
|
+
// The writer is atomic — every write goes through atomic-file's
|
|
695
|
+
// writeSync (temp-file + rename + fsync) so a reader that lands
|
|
696
|
+
// between rename and fsync sees the previous complete snapshot
|
|
697
|
+
// rather than a partially-written one.
|
|
698
|
+
//
|
|
699
|
+
// Surface:
|
|
700
|
+
//
|
|
701
|
+
// var stop = b.metrics.snapshot.startWriter({
|
|
702
|
+
// path: "/run/blamejs-daemon/metrics.json",
|
|
703
|
+
// intervalMs: 5000,
|
|
704
|
+
// fields: function () { return { uptimeMs: ..., counters: {...} }; },
|
|
705
|
+
// });
|
|
706
|
+
// // ...later:
|
|
707
|
+
// stop(); // clears timer; runs one final fields() flush before returning
|
|
708
|
+
//
|
|
709
|
+
// var snap = b.metrics.snapshot.read("/run/blamejs-daemon/metrics.json");
|
|
710
|
+
// process.stdout.write(b.metrics.snapshot.render(snap, { format: "text" }));
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* @primitive b.metrics.snapshot.startWriter
|
|
714
|
+
* @signature b.metrics.snapshot.startWriter(opts)
|
|
715
|
+
* @since 0.9.13
|
|
716
|
+
* @status stable
|
|
717
|
+
* @related b.metrics.snapshot.read, b.metrics.snapshot.render
|
|
718
|
+
*
|
|
719
|
+
* Start a periodic writer that calls `opts.fields()` every
|
|
720
|
+
* `opts.intervalMs` and writes the returned object as JSON to
|
|
721
|
+
* `opts.path` atomically. Returns a `stop()` function that clears
|
|
722
|
+
* the timer + performs one final flush before resolving.
|
|
723
|
+
*
|
|
724
|
+
* @opts
|
|
725
|
+
* path: string, // absolute path to write the snapshot
|
|
726
|
+
* intervalMs: number, // milliseconds between flushes (>=100)
|
|
727
|
+
* fields: Function, // returns an object — written as JSON
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* var stop = b.metrics.snapshot.startWriter({
|
|
731
|
+
* path: "/run/blamejs/metrics.json",
|
|
732
|
+
* intervalMs: 5000,
|
|
733
|
+
* fields: function () {
|
|
734
|
+
* return {
|
|
735
|
+
* uptimeMs: process.uptime() * 1000,
|
|
736
|
+
* queueDepth: myQueue.size,
|
|
737
|
+
* lastSyncAt: lastSyncAt,
|
|
738
|
+
* };
|
|
739
|
+
* },
|
|
740
|
+
* });
|
|
741
|
+
* // ... on SIGTERM:
|
|
742
|
+
* stop();
|
|
743
|
+
*/
|
|
744
|
+
function snapshotStartWriter(opts) {
|
|
745
|
+
opts = opts || {};
|
|
746
|
+
validateOpts.requireNonEmptyString(opts.path,
|
|
747
|
+
"metrics.snapshot.startWriter: opts.path",
|
|
748
|
+
MetricsError, "metrics-snapshot/bad-path");
|
|
749
|
+
if (typeof opts.intervalMs !== "number" || !isFinite(opts.intervalMs) || opts.intervalMs < 100) {
|
|
750
|
+
throw new MetricsError("metrics-snapshot/bad-interval",
|
|
751
|
+
"metrics.snapshot.startWriter: opts.intervalMs must be a finite number >= 100, got " + opts.intervalMs);
|
|
752
|
+
}
|
|
753
|
+
if (typeof opts.fields !== "function") {
|
|
754
|
+
throw new MetricsError("metrics-snapshot/bad-fields",
|
|
755
|
+
"metrics.snapshot.startWriter: opts.fields must be a function returning the snapshot object");
|
|
756
|
+
}
|
|
757
|
+
var p = opts.path;
|
|
758
|
+
var fieldsFn = opts.fields;
|
|
759
|
+
var intervalMs = opts.intervalMs;
|
|
760
|
+
|
|
761
|
+
var doFlush = function () {
|
|
762
|
+
var snap;
|
|
763
|
+
try {
|
|
764
|
+
snap = fieldsFn();
|
|
765
|
+
} catch (e) {
|
|
766
|
+
log("snapshot.fields() threw: " + (e && e.message ? e.message : String(e)));
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (!snap || typeof snap !== "object") {
|
|
770
|
+
log("snapshot.fields() returned non-object; skipping flush");
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
var payload = {
|
|
774
|
+
writtenAt: new Date().toISOString(),
|
|
775
|
+
fields: snap,
|
|
776
|
+
};
|
|
777
|
+
try {
|
|
778
|
+
atomicFile.writeSync(p, JSON.stringify(payload) + "\n", { fileMode: 0o644 });
|
|
779
|
+
} catch (e) {
|
|
780
|
+
log("snapshot.writeSync failed: " + (e && e.message ? e.message : String(e)));
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
// First flush is synchronous so the file exists by the time
|
|
785
|
+
// startWriter returns. Subsequent flushes run on the interval.
|
|
786
|
+
doFlush();
|
|
787
|
+
var timer = setInterval(doFlush, intervalMs);
|
|
788
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
789
|
+
|
|
790
|
+
return function stop() {
|
|
791
|
+
clearInterval(timer);
|
|
792
|
+
doFlush(); // final flush captures last state before the daemon exits
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* @primitive b.metrics.snapshot.read
|
|
798
|
+
* @signature b.metrics.snapshot.read(path)
|
|
799
|
+
* @since 0.9.13
|
|
800
|
+
* @status stable
|
|
801
|
+
* @related b.metrics.snapshot.startWriter, b.metrics.snapshot.render
|
|
802
|
+
*
|
|
803
|
+
* Read + parse a snapshot file written by `startWriter`. Returns
|
|
804
|
+
* `{ writtenAt, fields }`. Throws `MetricsError` with code
|
|
805
|
+
* `metrics-snapshot/...` on missing file, parse failure, or
|
|
806
|
+
* shape mismatch.
|
|
807
|
+
*
|
|
808
|
+
* @example
|
|
809
|
+
* var snap = b.metrics.snapshot.read("/run/blamejs/metrics.json");
|
|
810
|
+
* console.log("uptime:", snap.fields.uptimeMs);
|
|
811
|
+
* console.log("written at:", snap.writtenAt);
|
|
812
|
+
*/
|
|
813
|
+
function snapshotRead(p) {
|
|
814
|
+
validateOpts.requireNonEmptyString(p,
|
|
815
|
+
"metrics.snapshot.read: path",
|
|
816
|
+
MetricsError, "metrics-snapshot/bad-path");
|
|
817
|
+
var raw;
|
|
818
|
+
try {
|
|
819
|
+
raw = nodeFs.readFileSync(p, "utf8");
|
|
820
|
+
} catch (e) {
|
|
821
|
+
throw new MetricsError("metrics-snapshot/not-found",
|
|
822
|
+
"metrics.snapshot.read: " + p + " — " + (e && e.message ? e.message : String(e)));
|
|
823
|
+
}
|
|
824
|
+
var parsed;
|
|
825
|
+
// safeJson.parse with bounded maxBytes — the snapshot file is read
|
|
826
|
+
// by a separate CLI / sidecar process from where it's written, and a
|
|
827
|
+
// hostile actor with write access to the snapshot path could replace
|
|
828
|
+
// it with a multi-GB file that would OOM the reader. 4 MiB ceiling
|
|
829
|
+
// is well above the framework's expected snapshot size (~5-50 KiB)
|
|
830
|
+
// and the safeJson absolute cap stays within reach.
|
|
831
|
+
try {
|
|
832
|
+
parsed = safeJson.parse(raw, { maxBytes: 4 * 1024 * 1024 }); // allow:raw-byte-literal — 4 MiB snapshot-file ceiling
|
|
833
|
+
} catch (e) {
|
|
834
|
+
throw new MetricsError("metrics-snapshot/bad-json",
|
|
835
|
+
"metrics.snapshot.read: " + p + " contains invalid JSON: " + (e && e.message ? e.message : String(e)));
|
|
836
|
+
}
|
|
837
|
+
if (!parsed || typeof parsed !== "object" ||
|
|
838
|
+
typeof parsed.writtenAt !== "string" || !parsed.fields ||
|
|
839
|
+
typeof parsed.fields !== "object") {
|
|
840
|
+
throw new MetricsError("metrics-snapshot/bad-shape",
|
|
841
|
+
"metrics.snapshot.read: " + p + " is not a startWriter-produced snapshot (missing writtenAt or fields)");
|
|
842
|
+
}
|
|
843
|
+
return parsed;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* @primitive b.metrics.snapshot.render
|
|
848
|
+
* @signature b.metrics.snapshot.render(snap, opts)
|
|
849
|
+
* @since 0.9.13
|
|
850
|
+
* @status stable
|
|
851
|
+
* @related b.metrics.snapshot.read
|
|
852
|
+
*
|
|
853
|
+
* Format a snapshot object for human or machine consumption.
|
|
854
|
+
*
|
|
855
|
+
* format: "text" — operator-readable lines, one field per row (default)
|
|
856
|
+
* format: "prometheus" — Prometheus 0.0.4 text format, gauge metrics
|
|
857
|
+
* named with a configurable prefix; only top-level
|
|
858
|
+
* numeric fields under `snap.fields` are emitted
|
|
859
|
+
*
|
|
860
|
+
* @opts
|
|
861
|
+
* format: "text" | "prometheus", // default: "text"
|
|
862
|
+
* prefix: string, // prometheus-only; default: "blamejs"
|
|
863
|
+
*
|
|
864
|
+
* @example
|
|
865
|
+
* var snap = b.metrics.snapshot.read("/run/blamejs/metrics.json");
|
|
866
|
+
* process.stdout.write(b.metrics.snapshot.render(snap));
|
|
867
|
+
* // or for Prometheus scraping:
|
|
868
|
+
* res.setHeader("Content-Type", "text/plain; version=0.0.4");
|
|
869
|
+
* res.end(b.metrics.snapshot.render(snap, { format: "prometheus", prefix: "myapp" }));
|
|
870
|
+
*/
|
|
871
|
+
function snapshotRender(snap, opts) {
|
|
872
|
+
opts = opts || {};
|
|
873
|
+
var format = opts.format || "text";
|
|
874
|
+
if (!snap || typeof snap !== "object" || !snap.fields) {
|
|
875
|
+
throw new MetricsError("metrics-snapshot/bad-snap",
|
|
876
|
+
"metrics.snapshot.render: snap must be a startWriter-produced object (got " + typeof snap + ")");
|
|
877
|
+
}
|
|
878
|
+
var fields = snap.fields;
|
|
879
|
+
if (format === "text") {
|
|
880
|
+
var lines = ["snapshot written-at: " + snap.writtenAt];
|
|
881
|
+
// allow:bare-canonicalize-walk — sort is for stable human-readable
|
|
882
|
+
// output ordering, not canonicalize-for-hashing
|
|
883
|
+
var keys = Object.keys(fields).sort();
|
|
884
|
+
for (var i = 0; i < keys.length; i++) {
|
|
885
|
+
var k = keys[i];
|
|
886
|
+
var v = fields[k];
|
|
887
|
+
var s;
|
|
888
|
+
if (typeof v === "number") s = String(v);
|
|
889
|
+
else if (typeof v === "string") s = v;
|
|
890
|
+
else if (typeof v === "boolean") s = v ? "true" : "false";
|
|
891
|
+
else s = JSON.stringify(v);
|
|
892
|
+
lines.push(" " + k + ": " + s);
|
|
893
|
+
}
|
|
894
|
+
return lines.join("\n") + "\n";
|
|
895
|
+
}
|
|
896
|
+
if (format === "prometheus") {
|
|
897
|
+
var prefix = opts.prefix || "blamejs";
|
|
898
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(prefix)) {
|
|
899
|
+
throw new MetricsError("metrics-snapshot/bad-prefix",
|
|
900
|
+
"metrics.snapshot.render: prometheus prefix must match [a-zA-Z_][a-zA-Z0-9_]*, got '" + prefix + "'");
|
|
901
|
+
}
|
|
902
|
+
var out = [];
|
|
903
|
+
// allow:bare-canonicalize-walk — sort is for stable Prometheus
|
|
904
|
+
// exposition output ordering, not canonicalize-for-hashing
|
|
905
|
+
var keys2 = Object.keys(fields).sort();
|
|
906
|
+
for (var j = 0; j < keys2.length; j++) {
|
|
907
|
+
var k2 = keys2[j];
|
|
908
|
+
var v2 = fields[k2];
|
|
909
|
+
if (typeof v2 !== "number" || !isFinite(v2)) continue; // only numeric scalars
|
|
910
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(k2)) continue; // skip prom-incompatible names
|
|
911
|
+
var metric = prefix + "_" + k2;
|
|
912
|
+
out.push("# TYPE " + metric + " gauge");
|
|
913
|
+
out.push(metric + " " + v2);
|
|
914
|
+
}
|
|
915
|
+
return out.join("\n") + "\n";
|
|
916
|
+
}
|
|
917
|
+
throw new MetricsError("metrics-snapshot/bad-format",
|
|
918
|
+
"metrics.snapshot.render: format must be 'text' or 'prometheus', got '" + format + "'");
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
var snapshot = {
|
|
922
|
+
startWriter: snapshotStartWriter,
|
|
923
|
+
read: snapshotRead,
|
|
924
|
+
render: snapshotRender,
|
|
925
|
+
};
|
|
926
|
+
|
|
681
927
|
module.exports = {
|
|
682
928
|
create: create,
|
|
683
929
|
tap: tap,
|
|
930
|
+
snapshot: snapshot,
|
|
684
931
|
MetricsError: MetricsError,
|
|
685
932
|
DEFAULT_HTTP_BUCKETS: DEFAULT_HTTP_BUCKETS,
|
|
686
933
|
DEFAULT_CARDINALITY_CAP: DEFAULT_CARDINALITY_CAP,
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
* public/private routes can debug their wiring.
|
|
97
97
|
*/
|
|
98
98
|
|
|
99
|
-
var
|
|
99
|
+
var bCrypto = require("../crypto");
|
|
100
100
|
var C = require("../constants");
|
|
101
101
|
var lazyRequire = require("../lazy-require");
|
|
102
102
|
var nonceStoreLib = require("../nonce-store");
|
|
@@ -449,7 +449,7 @@ function create(opts) {
|
|
|
449
449
|
res.json = function (data) {
|
|
450
450
|
try {
|
|
451
451
|
var ptBuf = Buffer.from(JSON.stringify(data), "utf8");
|
|
452
|
-
var ctBuf =
|
|
452
|
+
var ctBuf = bCrypto.encryptPacked(ptBuf, sessionKey);
|
|
453
453
|
var encrypted = { _ct: ctBuf.toString("base64") };
|
|
454
454
|
if (sessionCtx) {
|
|
455
455
|
encrypted._sid = sessionCtx.sid;
|
|
@@ -480,7 +480,7 @@ function create(opts) {
|
|
|
480
480
|
function _decryptEkToSessionKey(ek) {
|
|
481
481
|
for (var ki = 0; ki < keypairs.length; ki++) {
|
|
482
482
|
try {
|
|
483
|
-
var sessionKeyB64 =
|
|
483
|
+
var sessionKeyB64 = bCrypto.decrypt(ek, keypairs[ki]);
|
|
484
484
|
var candidate = Buffer.from(sessionKeyB64, "base64");
|
|
485
485
|
if (candidate.length === SESSION_KEY_BYTES) return candidate;
|
|
486
486
|
} catch (_e) { /* try next keypair */ }
|
|
@@ -518,7 +518,7 @@ function create(opts) {
|
|
|
518
518
|
|
|
519
519
|
if (typeof ek === "string" && typeof nonce === "string") {
|
|
520
520
|
// ---- Bootstrap path (per-request mode OR first request of session) ----
|
|
521
|
-
var nonceHash =
|
|
521
|
+
var nonceHash = bCrypto.sha3Hash(nonce, "hex");
|
|
522
522
|
var expireAt = now + replayWindowMs;
|
|
523
523
|
var freshNonce;
|
|
524
524
|
try { freshNonce = await nonceStore.checkAndInsert(nonceHash, expireAt); }
|
|
@@ -655,7 +655,7 @@ function create(opts) {
|
|
|
655
655
|
var clearObj;
|
|
656
656
|
try {
|
|
657
657
|
var ctBuf = Buffer.from(ct, "base64");
|
|
658
|
-
var ptBuf =
|
|
658
|
+
var ptBuf = bCrypto.decryptPacked(ctBuf, sessionKey);
|
|
659
659
|
clearObj = safeJson.parse(ptBuf.toString("utf8"), { maxBytes: maxDecryptedBytes });
|
|
660
660
|
} catch (_e) {
|
|
661
661
|
_emitFailure(req, "tag");
|
|
@@ -751,7 +751,7 @@ function client(opts) {
|
|
|
751
751
|
var perSessionLastResCtr = 0;
|
|
752
752
|
|
|
753
753
|
function _resetSession() {
|
|
754
|
-
perSessionKey =
|
|
754
|
+
perSessionKey = bCrypto.generateBytes(SESSION_KEY_BYTES);
|
|
755
755
|
perSessionSid = _generateUuidV4();
|
|
756
756
|
perSessionReqCtr = 0;
|
|
757
757
|
perSessionLastResCtr = 0;
|
|
@@ -774,7 +774,7 @@ function client(opts) {
|
|
|
774
774
|
}
|
|
775
775
|
perSessionLastResCtr = responseBody._ctr;
|
|
776
776
|
var resCtBuf = Buffer.from(responseBody._ct, "base64");
|
|
777
|
-
var resPtBuf =
|
|
777
|
+
var resPtBuf = bCrypto.decryptPacked(resCtBuf, perSessionKey);
|
|
778
778
|
return safeJson.parse(resPtBuf.toString("utf8"), { maxBytes: maxDecryptedBytes });
|
|
779
779
|
}
|
|
780
780
|
|
|
@@ -783,13 +783,13 @@ function client(opts) {
|
|
|
783
783
|
if (!perSessionKey) _resetSession();
|
|
784
784
|
var ts = Date.now();
|
|
785
785
|
var ptBuf = Buffer.from(JSON.stringify(payload), "utf8");
|
|
786
|
-
var ctBuf =
|
|
786
|
+
var ctBuf = bCrypto.encryptPacked(ptBuf, perSessionKey);
|
|
787
787
|
perSessionReqCtr += 1;
|
|
788
788
|
var body;
|
|
789
789
|
if (perSessionReqCtr === 1) {
|
|
790
790
|
// Bootstrap envelope — full _ek + _nonce; server stores sid → sessionKey.
|
|
791
|
-
var ek =
|
|
792
|
-
var nonce =
|
|
791
|
+
var ek = bCrypto.encrypt(perSessionKey.toString("base64"), pubkey);
|
|
792
|
+
var nonce = bCrypto.generateBytes(REQUEST_NONCE_BYTES).toString("hex");
|
|
793
793
|
body = {
|
|
794
794
|
_ek: ek,
|
|
795
795
|
_ct: ctBuf.toString("base64"),
|
|
@@ -812,11 +812,11 @@ function client(opts) {
|
|
|
812
812
|
|
|
813
813
|
function _encryptPerRequest(payload) {
|
|
814
814
|
if (payload === undefined) payload = null;
|
|
815
|
-
var sessionKey =
|
|
816
|
-
var ek =
|
|
815
|
+
var sessionKey = bCrypto.generateBytes(SESSION_KEY_BYTES);
|
|
816
|
+
var ek = bCrypto.encrypt(sessionKey.toString("base64"), pubkey);
|
|
817
817
|
var ptBuf = Buffer.from(JSON.stringify(payload), "utf8");
|
|
818
|
-
var ctBuf =
|
|
819
|
-
var requestNonce =
|
|
818
|
+
var ctBuf = bCrypto.encryptPacked(ptBuf, sessionKey);
|
|
819
|
+
var requestNonce = bCrypto.generateBytes(REQUEST_NONCE_BYTES).toString("hex");
|
|
820
820
|
var ts = Date.now();
|
|
821
821
|
return {
|
|
822
822
|
body: {
|
|
@@ -832,7 +832,7 @@ function client(opts) {
|
|
|
832
832
|
"apiEncrypt.client: response missing _ct field");
|
|
833
833
|
}
|
|
834
834
|
var resCtBuf = Buffer.from(responseBody._ct, "base64");
|
|
835
|
-
var resPtBuf =
|
|
835
|
+
var resPtBuf = bCrypto.decryptPacked(resCtBuf, sessionKey);
|
|
836
836
|
return safeJson.parse(resPtBuf.toString("utf8"), { maxBytes: maxDecryptedBytes });
|
|
837
837
|
},
|
|
838
838
|
};
|
|
@@ -857,7 +857,7 @@ function client(opts) {
|
|
|
857
857
|
// Slice offsets are RFC 4122 UUID hex-byte boundaries (`xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx`)
|
|
858
858
|
// — protocol-fixed values, not byte sizes. allow:raw-byte-literal
|
|
859
859
|
function _generateUuidV4() {
|
|
860
|
-
var b =
|
|
860
|
+
var b = bCrypto.generateBytes(16); // allow:raw-byte-literal — UUID is exactly 16 bytes
|
|
861
861
|
// Set version (4) and variant (10x) bits per RFC 4122.
|
|
862
862
|
b[6] = (b[6] & 0x0f) | 0x40;
|
|
863
863
|
b[8] = (b[8] & 0x3f) | 0x80;
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
* dots collapsed, control characters stripped, length capped at 255.
|
|
101
101
|
* Tmp file path is generated by the framework, never derived from
|
|
102
102
|
* the operator-supplied filename — so a malicious filename can't
|
|
103
|
-
* collide with a sensitive
|
|
103
|
+
* collide with a sensitive nodePath.
|
|
104
104
|
* - Multipart parser refuses fields whose `name` is in POISONED_KEYS
|
|
105
105
|
* (consistent with the JSON path).
|
|
106
106
|
* - Tmp files set with mode 0o600, parent dir created with 0o700.
|
|
@@ -108,12 +108,12 @@
|
|
|
108
108
|
* error) so a crashing handler doesn't leak files.
|
|
109
109
|
*/
|
|
110
110
|
|
|
111
|
-
var
|
|
111
|
+
var nodeFs = require("fs");
|
|
112
112
|
var os = require("os");
|
|
113
|
-
var
|
|
113
|
+
var nodePath = require("path");
|
|
114
114
|
var nodeCrypto = require("node:crypto");
|
|
115
115
|
var atomicFile = require("../atomic-file");
|
|
116
|
-
var
|
|
116
|
+
var bCrypto = require("../crypto");
|
|
117
117
|
var lazyRequire = require("../lazy-require");
|
|
118
118
|
var requestHelpers = require("../request-helpers");
|
|
119
119
|
var safeBuffer = require("../safe-buffer");
|
|
@@ -682,7 +682,7 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
682
682
|
}
|
|
683
683
|
// Resolve tmpDir per-request so directory-creation failure surfaces as a
|
|
684
684
|
// structured error rather than a deferred fs throw.
|
|
685
|
-
var tmpDir = opts.tmpDir ||
|
|
685
|
+
var tmpDir = opts.tmpDir || nodePath.join(os.tmpdir(), "blamejs-uploads");
|
|
686
686
|
try { atomicFile.ensureDir(tmpDir, 0o700); }
|
|
687
687
|
catch (e) {
|
|
688
688
|
throw new BodyParserError(
|
|
@@ -733,7 +733,7 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
733
733
|
currentFilename = null;
|
|
734
734
|
currentMime = null;
|
|
735
735
|
currentTmpPath = null;
|
|
736
|
-
if (currentFd !== null) { try {
|
|
736
|
+
if (currentFd !== null) { try { nodeFs.closeSync(currentFd); } catch (_e) { /* fd already closed */ } currentFd = null; }
|
|
737
737
|
currentSize = 0;
|
|
738
738
|
currentHash = null;
|
|
739
739
|
currentBuf = null;
|
|
@@ -762,10 +762,10 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
762
762
|
}
|
|
763
763
|
|
|
764
764
|
function _cleanup() {
|
|
765
|
-
if (currentFd !== null) { try {
|
|
766
|
-
if (currentTmpPath) { try {
|
|
765
|
+
if (currentFd !== null) { try { nodeFs.closeSync(currentFd); } catch (_e) { /* fd already closed */ } currentFd = null; }
|
|
766
|
+
if (currentTmpPath) { try { nodeFs.unlinkSync(currentTmpPath); } catch (_e) { /* tmp file already removed */ } }
|
|
767
767
|
for (var i = 0; i < files.length; i++) {
|
|
768
|
-
try {
|
|
768
|
+
try { nodeFs.unlinkSync(files[i].path); } catch (_e) { /* tmp file already removed */ }
|
|
769
769
|
}
|
|
770
770
|
}
|
|
771
771
|
|
|
@@ -945,10 +945,10 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
945
945
|
|
|
946
946
|
// Generate the tmp path — never derived from the
|
|
947
947
|
// operator-supplied filename.
|
|
948
|
-
var unique =
|
|
949
|
-
currentTmpPath =
|
|
948
|
+
var unique = bCrypto.generateToken(C.BYTES.bytes(16));
|
|
949
|
+
currentTmpPath = nodePath.join(tmpDir, "blamejs-up-" + unique);
|
|
950
950
|
try {
|
|
951
|
-
currentFd =
|
|
951
|
+
currentFd = nodeFs.openSync(currentTmpPath, "wx", 0o600);
|
|
952
952
|
} catch (e) {
|
|
953
953
|
done(new BodyParserError("body-parser/multipart-tmp-open",
|
|
954
954
|
"could not open multipart tmp file: " + ((e && e.message) || String(e)),
|
|
@@ -1027,7 +1027,7 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
1027
1027
|
try {
|
|
1028
1028
|
var written = 0;
|
|
1029
1029
|
while (written < bodyChunk.length) {
|
|
1030
|
-
written +=
|
|
1030
|
+
written += nodeFs.writeSync(currentFd, bodyChunk, written, bodyChunk.length - written);
|
|
1031
1031
|
}
|
|
1032
1032
|
} catch (e) {
|
|
1033
1033
|
done(new BodyParserError("body-parser/multipart-tmp-write",
|
|
@@ -1068,7 +1068,7 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
1068
1068
|
// fileFilter rejected — already recorded in filesRejected; no
|
|
1069
1069
|
// tmp file was opened, nothing to clean up here.
|
|
1070
1070
|
} else if (currentFd !== null) {
|
|
1071
|
-
try {
|
|
1071
|
+
try { nodeFs.closeSync(currentFd); } catch (_e) { /* fd already closed */ }
|
|
1072
1072
|
currentFd = null;
|
|
1073
1073
|
files.push({
|
|
1074
1074
|
field: currentField,
|
|
@@ -1247,7 +1247,7 @@ function create(opts) {
|
|
|
1247
1247
|
if (cleanedUp) return;
|
|
1248
1248
|
cleanedUp = true;
|
|
1249
1249
|
for (var i = 0; i < mpResult.files.length; i++) {
|
|
1250
|
-
try {
|
|
1250
|
+
try { nodeFs.unlinkSync(mpResult.files[i].path); } catch (_e) { /* tmp file already removed */ }
|
|
1251
1251
|
}
|
|
1252
1252
|
}
|
|
1253
1253
|
res.on("finish", cleanup);
|
|
@@ -1500,7 +1500,7 @@ module.exports = {
|
|
|
1500
1500
|
BodyParserError: BodyParserError,
|
|
1501
1501
|
// Standalone async helpers — surfaced via b.parsers.{json,multipart}.
|
|
1502
1502
|
// The middleware composes these so the request-handling pipeline and
|
|
1503
|
-
// the operator-callable surface share one parsing
|
|
1503
|
+
// the operator-callable surface share one parsing nodePath.
|
|
1504
1504
|
parseJson: parseJsonStandalone,
|
|
1505
1505
|
parseMultipart: parseMultipartStandalone,
|
|
1506
1506
|
// Internal helpers exposed for tests + the csrf-protect refactor.
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
|
|
91
91
|
var zlib = require("node:zlib");
|
|
92
92
|
var C = require("../constants");
|
|
93
|
-
var
|
|
93
|
+
var numericBounds = require("../numeric-bounds");
|
|
94
94
|
var requestHelpers = require("../request-helpers");
|
|
95
95
|
var validateOpts = require("../validate-opts");
|
|
96
96
|
var { defineClass } = require("../framework-error");
|
|
@@ -271,12 +271,12 @@ function create(opts) {
|
|
|
271
271
|
var threshold;
|
|
272
272
|
if (opts.threshold === undefined) {
|
|
273
273
|
threshold = DEFAULT_OPTS.threshold;
|
|
274
|
-
} else if (
|
|
274
|
+
} else if (numericBounds.isNonNegativeFiniteInt(opts.threshold)) {
|
|
275
275
|
threshold = opts.threshold;
|
|
276
276
|
} else {
|
|
277
277
|
throw new CompressionError("compression/bad-opt",
|
|
278
278
|
"middleware.compression: threshold must be a non-negative finite integer; got " +
|
|
279
|
-
|
|
279
|
+
numericBounds.shape(opts.threshold));
|
|
280
280
|
}
|
|
281
281
|
var encodings = Array.isArray(opts.encodings) && opts.encodings.length > 0
|
|
282
282
|
? opts.encodings.slice() : DEFAULT_OPTS.encodings.slice();
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
*/
|
|
114
114
|
|
|
115
115
|
var C = require("../constants");
|
|
116
|
-
var
|
|
116
|
+
var bCrypto = require("../crypto");
|
|
117
117
|
var numericBounds = require("../numeric-bounds");
|
|
118
118
|
var validateOpts = require("../validate-opts");
|
|
119
119
|
var { defineClass } = require("../framework-error");
|
|
@@ -283,7 +283,7 @@ function create(opts) {
|
|
|
283
283
|
// Pre-fix the typeof-only check accepted Infinity / NaN — both
|
|
284
284
|
// bypassed the `< MIN_NONCE_BYTES` guard (NaN < N is always false,
|
|
285
285
|
// Infinity < N is always false), then crashed per-request when
|
|
286
|
-
// `
|
|
286
|
+
// `bCrypto.generateBytes(Infinity)` hit ERR_OUT_OF_RANGE. Route through
|
|
287
287
|
// shared numeric-bounds (positive finite int) before the lower-bound
|
|
288
288
|
// check so the typo / coercion is caught at create() time.
|
|
289
289
|
if (!numericBounds.isPositiveFiniteInt(nonceBytes)) {
|
|
@@ -322,7 +322,7 @@ function create(opts) {
|
|
|
322
322
|
if (opts.placeholder === undefined) {
|
|
323
323
|
// OS-RNG → SHAKE256 → hex via the framework random helper.
|
|
324
324
|
placeholder = PLACEHOLDER_PREFIX +
|
|
325
|
-
|
|
325
|
+
bCrypto.generateToken(PLACEHOLDER_RAND_BYTES) +
|
|
326
326
|
PLACEHOLDER_SUFFIX;
|
|
327
327
|
} else if (typeof opts.placeholder !== "string" || opts.placeholder.length === 0) {
|
|
328
328
|
throw new CspNonceError("csp-nonce/bad-placeholder",
|
|
@@ -336,7 +336,7 @@ function create(opts) {
|
|
|
336
336
|
// Generate the nonce. Cheap (16 bytes from getrandom → SHAKE256 →
|
|
337
337
|
// base64 encode); do it always for consistency unless `always:
|
|
338
338
|
// false` was set explicitly.
|
|
339
|
-
var nonce =
|
|
339
|
+
var nonce = bCrypto.generateBytes(nonceBytes).toString("base64");
|
|
340
340
|
|
|
341
341
|
// Attach to req for handler access.
|
|
342
342
|
req[property] = nonce;
|
package/lib/middleware/health.js
CHANGED
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
*/
|
|
101
101
|
|
|
102
102
|
var C = require("../constants");
|
|
103
|
-
var
|
|
103
|
+
var numericBounds = require("../numeric-bounds");
|
|
104
104
|
var requestHelpers = require("../request-helpers");
|
|
105
105
|
var safeAsync = require("../safe-async");
|
|
106
106
|
var validateOpts = require("../validate-opts");
|
|
@@ -179,22 +179,22 @@ function create(opts) {
|
|
|
179
179
|
var defaultTimeoutMs;
|
|
180
180
|
if (opts.defaultTimeoutMs === undefined) {
|
|
181
181
|
defaultTimeoutMs = DEFAULT_TIMEOUT_MS;
|
|
182
|
-
} else if (
|
|
182
|
+
} else if (numericBounds.isPositiveFiniteInt(opts.defaultTimeoutMs)) {
|
|
183
183
|
defaultTimeoutMs = opts.defaultTimeoutMs;
|
|
184
184
|
} else {
|
|
185
185
|
throw new HealthError("health/bad-opt",
|
|
186
186
|
"defaultTimeoutMs must be a positive finite integer; got " +
|
|
187
|
-
|
|
187
|
+
numericBounds.shape(opts.defaultTimeoutMs));
|
|
188
188
|
}
|
|
189
189
|
var cacheMs;
|
|
190
190
|
if (opts.cacheMs === undefined) {
|
|
191
191
|
cacheMs = 0;
|
|
192
|
-
} else if (
|
|
192
|
+
} else if (numericBounds.isNonNegativeFiniteInt(opts.cacheMs)) {
|
|
193
193
|
cacheMs = opts.cacheMs;
|
|
194
194
|
} else {
|
|
195
195
|
throw new HealthError("health/bad-opt",
|
|
196
196
|
"cacheMs must be a non-negative finite integer; got " +
|
|
197
|
-
|
|
197
|
+
numericBounds.shape(opts.cacheMs));
|
|
198
198
|
}
|
|
199
199
|
var includeMeta = opts.includeMeta !== false;
|
|
200
200
|
var version = opts.version || null;
|
|
@@ -237,12 +237,12 @@ function create(opts) {
|
|
|
237
237
|
var timeoutMs;
|
|
238
238
|
if (copts.timeoutMs === undefined) {
|
|
239
239
|
timeoutMs = defaultTimeoutMs;
|
|
240
|
-
} else if (
|
|
240
|
+
} else if (numericBounds.isPositiveFiniteInt(copts.timeoutMs)) {
|
|
241
241
|
timeoutMs = copts.timeoutMs;
|
|
242
242
|
} else {
|
|
243
243
|
throw new HealthError("health/bad-opt",
|
|
244
244
|
"registerCheck: timeoutMs must be a positive finite integer; got " +
|
|
245
|
-
|
|
245
|
+
numericBounds.shape(copts.timeoutMs));
|
|
246
246
|
}
|
|
247
247
|
checks.push({
|
|
248
248
|
name: name,
|