@blamejs/core 0.6.69 → 0.7.0
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 +6 -0
- package/README.md +11 -1
- package/index.js +2 -5
- package/lib/api-key.js +24 -55
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +20 -16
- package/lib/app.js +6 -5
- package/lib/archive.js +58 -50
- package/lib/atomic-file.js +11 -5
- package/lib/audit-chain.js +12 -4
- package/lib/audit-sign.js +38 -13
- package/lib/audit-tools.js +4 -4
- package/lib/audit.js +8 -4
- package/lib/auth/jwt.js +3 -2
- package/lib/auth/lockout.js +3 -13
- package/lib/auth/oauth.js +27 -17
- package/lib/auth/passkey.js +1 -5
- package/lib/auth/password.js +23 -13
- package/lib/backup/bundle.js +2 -4
- package/lib/backup/crypto.js +8 -6
- package/lib/backup/index.js +18 -14
- package/lib/backup/manifest.js +6 -2
- package/lib/break-glass.js +12 -7
- package/lib/bundler.js +32 -21
- package/lib/cache-redis.js +8 -3
- package/lib/cache.js +39 -59
- package/lib/canonical-json.js +17 -2
- package/lib/chain-writer.js +2 -2
- package/lib/cli-helpers.js +9 -4
- package/lib/cli.js +27 -5
- package/lib/cluster-provider-db.js +2 -1
- package/lib/cluster-storage.js +71 -11
- package/lib/cluster.js +30 -25
- package/lib/config-drift.js +3 -4
- package/lib/constants.js +8 -3
- package/lib/cookies.js +16 -4
- package/lib/credential-hash.js +4 -7
- package/lib/crypto.js +9 -9
- package/lib/csv.js +11 -8
- package/lib/db-declare-row-policy.js +4 -9
- package/lib/db-declare-view.js +3 -6
- package/lib/db-query.js +9 -3
- package/lib/db-schema.js +75 -10
- package/lib/db.js +116 -70
- package/lib/deprecate.js +3 -8
- package/lib/dev.js +30 -19
- package/lib/dual-control.js +2 -4
- package/lib/error-page.js +32 -39
- package/lib/external-db-migrate.js +27 -20
- package/lib/external-db.js +11 -3
- package/lib/file-type.js +16 -8
- package/lib/forms.js +48 -12
- package/lib/framework-error.js +1 -2
- package/lib/{internal-sha1-hibp.js → framework-sha1-hibp.js} +1 -1
- package/lib/handlers.js +1 -1
- package/lib/http-client-cookie-jar.js +25 -18
- package/lib/http-client.js +46 -23
- package/lib/i18n.js +6 -16
- package/lib/jobs.js +5 -1
- package/lib/log-stream-cloudwatch.js +31 -30
- package/lib/log-stream-local.js +12 -3
- package/lib/log-stream-otlp-grpc.js +29 -23
- package/lib/log-stream-otlp.js +13 -19
- package/lib/log-stream-syslog.js +27 -22
- package/lib/log-stream-webhook.js +8 -17
- package/lib/log-stream.js +13 -2
- package/lib/log.js +83 -5
- package/lib/mail-bounce.js +22 -15
- package/lib/mail-dkim.js +3 -6
- package/lib/mail.js +62 -34
- package/lib/metrics.js +61 -25
- package/lib/middleware/api-encrypt.js +15 -13
- package/lib/middleware/body-parser.js +46 -39
- package/lib/middleware/bot-guard.js +25 -4
- package/lib/middleware/compression.js +26 -11
- package/lib/middleware/cors.js +6 -5
- package/lib/middleware/csp-nonce.js +17 -4
- package/lib/middleware/csrf-protect.js +1 -1
- package/lib/middleware/db-role-for.js +7 -30
- package/lib/middleware/health.js +42 -8
- package/lib/middleware/index.js +55 -36
- package/lib/middleware/rate-limit.js +11 -8
- package/lib/middleware/request-id.js +10 -2
- package/lib/middleware/request-log.js +3 -2
- package/lib/middleware/require-auth.js +5 -2
- package/lib/middleware/sse.js +5 -4
- package/lib/migration-files.js +37 -0
- package/lib/migrations.js +42 -17
- package/lib/mtls-ca.js +38 -17
- package/lib/mtls-engine-default.js +15 -7
- package/lib/network-dns.js +49 -20
- package/lib/network-heartbeat.js +7 -7
- package/lib/network-nts.js +70 -48
- package/lib/network-proxy.js +34 -15
- package/lib/network-tls.js +7 -5
- package/lib/network.js +3 -3
- package/lib/notify.js +14 -36
- package/lib/ntp-check.js +7 -6
- package/lib/numeric-bounds.js +35 -3
- package/lib/object-store/azure-blob-bucket-ops.js +73 -15
- package/lib/object-store/azure-blob.js +31 -24
- package/lib/object-store/gcs-bucket-ops.js +41 -17
- package/lib/object-store/gcs.js +41 -34
- package/lib/object-store/http-put.js +2 -11
- package/lib/object-store/http-request.js +38 -0
- package/lib/object-store/index.js +1 -1
- package/lib/object-store/sigv4-bucket-ops.js +33 -37
- package/lib/object-store/sigv4.js +50 -30
- package/lib/observability.js +14 -3
- package/lib/otel-export.js +16 -14
- package/lib/pagination.js +23 -8
- package/lib/parsers/index.js +11 -5
- package/lib/parsers/safe-env.js +43 -14
- package/lib/parsers/safe-ini.js +29 -10
- package/lib/parsers/safe-toml.js +55 -25
- package/lib/parsers/safe-xml.js +22 -11
- package/lib/parsers/safe-yaml.js +55 -17
- package/lib/permissions.js +24 -33
- package/lib/pqc-agent.js +4 -2
- package/lib/pqc-gate.js +28 -15
- package/lib/protobuf-encoder.js +9 -3
- package/lib/protocol-dispatcher.js +29 -12
- package/lib/pubsub-redis.js +25 -8
- package/lib/pubsub.js +84 -12
- package/lib/queue-local.js +13 -4
- package/lib/queue-redis.js +2 -2
- package/lib/queue-sqs.js +21 -16
- package/lib/queue.js +8 -4
- package/lib/redis-client.js +13 -10
- package/lib/request-helpers.js +33 -2
- package/lib/restore-bundle.js +2 -4
- package/lib/restore-rollback.js +4 -1
- package/lib/restore.js +12 -10
- package/lib/retry.js +58 -9
- package/lib/router.js +96 -27
- package/lib/safe-async.js +97 -4
- package/lib/safe-buffer.js +47 -0
- package/lib/safe-json.js +18 -9
- package/lib/safe-schema.js +54 -18
- package/lib/safe-url.js +6 -2
- package/lib/scheduler.js +25 -14
- package/lib/security-assert.js +31 -26
- package/lib/seeders.js +71 -60
- package/lib/session.js +13 -7
- package/lib/slug.js +35 -8
- package/lib/ssrf-guard.js +58 -43
- package/lib/static.js +11 -7
- package/lib/storage.js +2 -1
- package/lib/template.js +17 -7
- package/lib/testing.js +28 -8
- package/lib/time.js +26 -5
- package/lib/totp.js +18 -9
- package/lib/tracing.js +15 -5
- package/lib/uuid.js +47 -20
- package/lib/validate-opts.js +213 -0
- package/lib/vault/index.js +36 -27
- package/lib/vault/rotate.js +41 -22
- package/lib/vault/wrap.js +26 -19
- package/lib/webhook.js +37 -68
- package/lib/websocket.js +60 -46
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
- package/lib/auth/totp.js +0 -17
- package/lib/object-store/retry.js +0 -15
package/lib/ssrf-guard.js
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
var dns = require("node:dns").promises;
|
|
44
44
|
var net = require("node:net");
|
|
45
45
|
|
|
46
|
+
var C = require("./constants");
|
|
46
47
|
var lazyRequire = require("./lazy-require");
|
|
47
48
|
var safeUrl = require("./safe-url");
|
|
48
49
|
var validateOpts = require("./validate-opts");
|
|
@@ -51,6 +52,18 @@ var { FrameworkError } = require("./framework-error");
|
|
|
51
52
|
|
|
52
53
|
var networkDns = lazyRequire(function () { return require("./network-dns"); });
|
|
53
54
|
|
|
55
|
+
// IP address bit-widths (RFC 791 §3.1 IPv4, RFC 4291 §2.5 IPv6) and
|
|
56
|
+
// CIDR prefix arithmetic constants. The numerals in this file are
|
|
57
|
+
// bit-counts in IP-address space, not byte sizes; routed through
|
|
58
|
+
// C.BYTES.bytes (a value-passthrough) so every numeric literal in the
|
|
59
|
+
// codebase has a single source of truth for "what shape is this number".
|
|
60
|
+
var IPV4_BITS = C.BYTES.bytes(32);
|
|
61
|
+
var IPV6_BITS = C.BYTES.bytes(128);
|
|
62
|
+
var BITS_PER_BYTE = C.BYTES.bytes(8);
|
|
63
|
+
var IPV6_BYTES = C.BYTES.bytes(16); // 128 bits / 8 bits-per-byte
|
|
64
|
+
var IPV6_GROUPS = C.BYTES.bytes(8); // hex groups
|
|
65
|
+
var HEX_RADIX = C.BYTES.bytes(16); // parseInt / toString radix
|
|
66
|
+
|
|
54
67
|
class SsrfError extends FrameworkError {
|
|
55
68
|
constructor(message, code, ctx) {
|
|
56
69
|
super(message, code);
|
|
@@ -68,26 +81,26 @@ class SsrfError extends FrameworkError {
|
|
|
68
81
|
// ---- IPv4 ranges (as numeric prefix tables for fast match) ----
|
|
69
82
|
// Each entry: [networkInt, prefixLen]
|
|
70
83
|
var IPV4_PRIVATE = [
|
|
71
|
-
[_ipv4ToInt("10.0.0.0"), 8],
|
|
72
|
-
[_ipv4ToInt("172.16.0.0"), 12],
|
|
73
|
-
[_ipv4ToInt("192.168.0.0"), 16],
|
|
84
|
+
[_ipv4ToInt("10.0.0.0"), C.BYTES.bytes(8)],
|
|
85
|
+
[_ipv4ToInt("172.16.0.0"), C.BYTES.bytes(12)],
|
|
86
|
+
[_ipv4ToInt("192.168.0.0"), C.BYTES.bytes(16)],
|
|
74
87
|
];
|
|
75
88
|
var IPV4_LOOPBACK = [
|
|
76
|
-
[_ipv4ToInt("127.0.0.0"), 8],
|
|
89
|
+
[_ipv4ToInt("127.0.0.0"), C.BYTES.bytes(8)],
|
|
77
90
|
];
|
|
78
91
|
var IPV4_LINK_LOCAL = [
|
|
79
|
-
[_ipv4ToInt("169.254.0.0"), 16],
|
|
92
|
+
[_ipv4ToInt("169.254.0.0"), C.BYTES.bytes(16)],
|
|
80
93
|
];
|
|
81
94
|
var IPV4_RESERVED = [
|
|
82
|
-
[_ipv4ToInt("0.0.0.0"), 8], // "this network"
|
|
83
|
-
[_ipv4ToInt("100.64.0.0"), 10], // CGNAT (RFC 6598)
|
|
84
|
-
[_ipv4ToInt("192.0.0.0"), 24], // IETF protocol assignments
|
|
85
|
-
[_ipv4ToInt("192.0.2.0"), 24], // TEST-NET-1
|
|
86
|
-
[_ipv4ToInt("198.18.0.0"), 15], // network benchmark
|
|
87
|
-
[_ipv4ToInt("198.51.100.0"), 24], // TEST-NET-2
|
|
88
|
-
[_ipv4ToInt("203.0.113.0"), 24], // TEST-NET-3
|
|
89
|
-
[_ipv4ToInt("224.0.0.0"), 4], // multicast
|
|
90
|
-
[_ipv4ToInt("240.0.0.0"), 4], // reserved + 255.255.255.255
|
|
95
|
+
[_ipv4ToInt("0.0.0.0"), C.BYTES.bytes(8)], // "this network"
|
|
96
|
+
[_ipv4ToInt("100.64.0.0"), C.BYTES.bytes(10)], // CGNAT (RFC 6598)
|
|
97
|
+
[_ipv4ToInt("192.0.0.0"), C.BYTES.bytes(24)], // IETF protocol assignments
|
|
98
|
+
[_ipv4ToInt("192.0.2.0"), C.BYTES.bytes(24)], // TEST-NET-1
|
|
99
|
+
[_ipv4ToInt("198.18.0.0"), C.BYTES.bytes(15)], // network benchmark
|
|
100
|
+
[_ipv4ToInt("198.51.100.0"), C.BYTES.bytes(24)], // TEST-NET-2
|
|
101
|
+
[_ipv4ToInt("203.0.113.0"), C.BYTES.bytes(24)], // TEST-NET-3
|
|
102
|
+
[_ipv4ToInt("224.0.0.0"), C.BYTES.bytes(4)], // multicast
|
|
103
|
+
[_ipv4ToInt("240.0.0.0"), C.BYTES.bytes(4)], // reserved + 255.255.255.255
|
|
91
104
|
];
|
|
92
105
|
|
|
93
106
|
// ---- IPv6 ranges (as 16-byte prefix tables) ----
|
|
@@ -96,7 +109,8 @@ var IPV6_UNSPECIFIED_BYTES = _ipv6ToBytes("::");
|
|
|
96
109
|
var IPV6_PRIVATE_PREFIX = _ipv6ToBytes("fc00::");
|
|
97
110
|
var IPV6_LINK_LOCAL_PREFIX = _ipv6ToBytes("fe80::");
|
|
98
111
|
var IPV6_DOC_PREFIX = _ipv6ToBytes("2001:db8::"); // documentation
|
|
99
|
-
|
|
112
|
+
// IPv4-mapped IPv6 (::ffff:0:0 with C.BYTES.bytes(96)-bit prefix)
|
|
113
|
+
var IPV6_V4_MAPPED_PREFIX = _ipv6ToBytes("::ffff:0:0");
|
|
100
114
|
// Multicast ff00::/8 — RFC 4291 §2.7. Refused for outbound HTTP same as
|
|
101
115
|
// IPv4 multicast 224/4 — there's no legitimate fetch from a multicast
|
|
102
116
|
// destination on a production gateway.
|
|
@@ -138,9 +152,9 @@ function _ipv6ToBytes(ip) {
|
|
|
138
152
|
// ipv6-to-bytes helper, but the URL constructor + Buffer dance
|
|
139
153
|
// is reliable for canonicalizing.
|
|
140
154
|
var groups = _expandIpv6(ip);
|
|
141
|
-
var out = Buffer.alloc(
|
|
142
|
-
for (var i = 0; i <
|
|
143
|
-
var v = parseInt(groups[i],
|
|
155
|
+
var out = Buffer.alloc(IPV6_BYTES);
|
|
156
|
+
for (var i = 0; i < IPV6_GROUPS; i++) {
|
|
157
|
+
var v = parseInt(groups[i], HEX_RADIX) || 0;
|
|
144
158
|
out[i * 2] = (v >> 8) & 0xff;
|
|
145
159
|
out[i * 2 + 1] = v & 0xff;
|
|
146
160
|
}
|
|
@@ -157,7 +171,7 @@ function _expandIpv6(ip) {
|
|
|
157
171
|
var ipv4Int = _ipv4ToInt(v4);
|
|
158
172
|
var hi = (ipv4Int >>> 16) & 0xffff;
|
|
159
173
|
var lo = ipv4Int & 0xffff;
|
|
160
|
-
lower = lower.slice(0, dot + 1) + hi.toString(
|
|
174
|
+
lower = lower.slice(0, dot + 1) + hi.toString(HEX_RADIX) + ":" + lo.toString(HEX_RADIX);
|
|
161
175
|
}
|
|
162
176
|
var doubleColon = lower.indexOf("::");
|
|
163
177
|
var leftStr, rightStr;
|
|
@@ -170,7 +184,7 @@ function _expandIpv6(ip) {
|
|
|
170
184
|
}
|
|
171
185
|
var left = leftStr.length ? leftStr.split(":") : [];
|
|
172
186
|
var right = rightStr.length ? rightStr.split(":") : [];
|
|
173
|
-
var missing =
|
|
187
|
+
var missing = IPV6_GROUPS - left.length - right.length;
|
|
174
188
|
var fill = [];
|
|
175
189
|
for (var i = 0; i < missing; i++) fill.push("0");
|
|
176
190
|
return left.concat(fill).concat(right);
|
|
@@ -181,10 +195,10 @@ function _cidrIpv4Match(cidr, ip) {
|
|
|
181
195
|
if (slash === -1) return false;
|
|
182
196
|
var network = _ipv4ToInt(cidr.slice(0, slash));
|
|
183
197
|
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
184
|
-
if (!Number.isFinite(prefix) || prefix < 0 || prefix >
|
|
198
|
+
if (!Number.isFinite(prefix) || prefix < 0 || prefix > IPV4_BITS) return false;
|
|
185
199
|
var ipInt = _ipv4ToInt(ip);
|
|
186
200
|
if (prefix === 0) return true;
|
|
187
|
-
var mask = (0xffffffff << (
|
|
201
|
+
var mask = (0xffffffff << (IPV4_BITS - prefix)) >>> 0;
|
|
188
202
|
return (ipInt & mask) === (network & mask);
|
|
189
203
|
}
|
|
190
204
|
|
|
@@ -193,15 +207,15 @@ function _cidrIpv6Match(cidr, ip) {
|
|
|
193
207
|
if (slash === -1) return false;
|
|
194
208
|
var network = _ipv6ToBytes(cidr.slice(0, slash));
|
|
195
209
|
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
196
|
-
if (!Number.isFinite(prefix) || prefix < 0 || prefix >
|
|
210
|
+
if (!Number.isFinite(prefix) || prefix < 0 || prefix > IPV6_BITS) return false;
|
|
197
211
|
var bytes = _ipv6ToBytes(ip);
|
|
198
|
-
var fullBytes = Math.floor(prefix /
|
|
199
|
-
var remainingBits = prefix %
|
|
212
|
+
var fullBytes = Math.floor(prefix / BITS_PER_BYTE);
|
|
213
|
+
var remainingBits = prefix % BITS_PER_BYTE;
|
|
200
214
|
for (var i = 0; i < fullBytes; i++) {
|
|
201
215
|
if (bytes[i] !== network[i]) return false;
|
|
202
216
|
}
|
|
203
217
|
if (remainingBits > 0) {
|
|
204
|
-
var mask = (0xff << (
|
|
218
|
+
var mask = (0xff << (BITS_PER_BYTE - remainingBits)) & 0xff;
|
|
205
219
|
if ((bytes[fullBytes] & mask) !== (network[fullBytes] & mask)) return false;
|
|
206
220
|
}
|
|
207
221
|
return true;
|
|
@@ -211,20 +225,20 @@ function _ipv4PrefixMatch(prefixTable, ipInt) {
|
|
|
211
225
|
for (var i = 0; i < prefixTable.length; i++) {
|
|
212
226
|
var net4 = prefixTable[i][0];
|
|
213
227
|
var prefix = prefixTable[i][1];
|
|
214
|
-
var mask = prefix === 0 ? 0 : (0xffffffff << (
|
|
228
|
+
var mask = prefix === 0 ? 0 : (0xffffffff << (IPV4_BITS - prefix)) >>> 0;
|
|
215
229
|
if ((ipInt & mask) === (net4 & mask)) return true;
|
|
216
230
|
}
|
|
217
231
|
return false;
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
function _ipv6PrefixMatch(prefixBytes, prefixLen, ipBytes) {
|
|
221
|
-
var fullBytes = Math.floor(prefixLen /
|
|
222
|
-
var remainingBits = prefixLen %
|
|
235
|
+
var fullBytes = Math.floor(prefixLen / BITS_PER_BYTE);
|
|
236
|
+
var remainingBits = prefixLen % BITS_PER_BYTE;
|
|
223
237
|
for (var i = 0; i < fullBytes; i++) {
|
|
224
238
|
if (ipBytes[i] !== prefixBytes[i]) return false;
|
|
225
239
|
}
|
|
226
240
|
if (remainingBits > 0) {
|
|
227
|
-
var mask = (0xff << (
|
|
241
|
+
var mask = (0xff << (BITS_PER_BYTE - remainingBits)) & 0xff;
|
|
228
242
|
if ((ipBytes[fullBytes] & mask) !== (prefixBytes[fullBytes] & mask)) return false;
|
|
229
243
|
}
|
|
230
244
|
return true;
|
|
@@ -252,25 +266,25 @@ function classify(ip) {
|
|
|
252
266
|
var bytes = _ipv6ToBytes(ip);
|
|
253
267
|
if (_bufEqual(bytes, IPV6_LOOPBACK_BYTES)) return "loopback";
|
|
254
268
|
if (_bufEqual(bytes, IPV6_UNSPECIFIED_BYTES)) return "reserved";
|
|
255
|
-
if (_ipv6PrefixMatch(IPV6_LINK_LOCAL_PREFIX, 10, bytes)) return "link-local";
|
|
269
|
+
if (_ipv6PrefixMatch(IPV6_LINK_LOCAL_PREFIX, C.BYTES.bytes(10), bytes)) return "link-local";
|
|
256
270
|
if (_ipv6PrefixMatch(IPV6_PRIVATE_PREFIX, 7, bytes)) return "private";
|
|
257
|
-
if (_ipv6PrefixMatch(IPV6_DOC_PREFIX, 32, bytes)) return "reserved";
|
|
258
|
-
if (_ipv6PrefixMatch(IPV6_MULTICAST_PREFIX, 8, bytes)) return "reserved";
|
|
259
|
-
if (_ipv6PrefixMatch(IPV6_DISCARD_PREFIX, 64, bytes)) return "reserved";
|
|
260
|
-
// 6to4
|
|
271
|
+
if (_ipv6PrefixMatch(IPV6_DOC_PREFIX, C.BYTES.bytes(32), bytes)) return "reserved";
|
|
272
|
+
if (_ipv6PrefixMatch(IPV6_MULTICAST_PREFIX, C.BYTES.bytes(8), bytes)) return "reserved";
|
|
273
|
+
if (_ipv6PrefixMatch(IPV6_DISCARD_PREFIX, C.BYTES.bytes(64), bytes)) return "reserved";
|
|
274
|
+
// 6to4 prefix embeds a v4 address in bytes 2–5; classify the
|
|
261
275
|
// embedded v4 so a 6to4-wrapped private/metadata address is refused
|
|
262
276
|
// for the same reason its v4 form would be.
|
|
263
|
-
if (_ipv6PrefixMatch(IPV6_6TO4_PREFIX, 16, bytes)) {
|
|
277
|
+
if (_ipv6PrefixMatch(IPV6_6TO4_PREFIX, C.BYTES.bytes(16), bytes)) {
|
|
264
278
|
var v4From6to4 = bytes[2] + "." + bytes[3] + "." + bytes[4] + "." + bytes[5];
|
|
265
279
|
return classify(v4From6to4) || "reserved";
|
|
266
280
|
}
|
|
267
|
-
// NAT64 well-known prefix
|
|
268
|
-
if (_ipv6PrefixMatch(IPV6_NAT64_PREFIX, 96, bytes)) {
|
|
281
|
+
// NAT64 well-known prefix embeds v4 in the lower bytes.
|
|
282
|
+
if (_ipv6PrefixMatch(IPV6_NAT64_PREFIX, C.BYTES.bytes(96), bytes)) {
|
|
269
283
|
var v4FromNat64 = bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15];
|
|
270
284
|
return classify(v4FromNat64) || "reserved";
|
|
271
285
|
}
|
|
272
|
-
// IPv4-mapped addresses (::ffff:a.b.c.d
|
|
273
|
-
if (_ipv6PrefixMatch(IPV6_V4_MAPPED_PREFIX, 96, bytes)) {
|
|
286
|
+
// IPv4-mapped addresses (::ffff:a.b.c.d): re-classify the v4 portion.
|
|
287
|
+
if (_ipv6PrefixMatch(IPV6_V4_MAPPED_PREFIX, C.BYTES.bytes(96), bytes)) {
|
|
274
288
|
var mappedV4 = bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15];
|
|
275
289
|
return classify(mappedV4);
|
|
276
290
|
}
|
|
@@ -278,9 +292,10 @@ function classify(ip) {
|
|
|
278
292
|
}
|
|
279
293
|
|
|
280
294
|
function _bufEqual(a, b) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
295
|
+
// Compares Buffer-like byte arrays for equality. The buffers here
|
|
296
|
+
// are IP addresses, not secrets, so the comparison doesn't need
|
|
297
|
+
// constant-time semantics — Buffer.compare uses native memcmp.
|
|
298
|
+
return Buffer.compare(a, b) === 0;
|
|
284
299
|
}
|
|
285
300
|
|
|
286
301
|
function cidrContains(cidr, ip) {
|
package/lib/static.js
CHANGED
|
@@ -48,12 +48,16 @@ var fs = require("fs");
|
|
|
48
48
|
var fsp = require("fs/promises");
|
|
49
49
|
var path = require("path");
|
|
50
50
|
var nodeCrypto = require("crypto");
|
|
51
|
+
var C = require("./constants");
|
|
52
|
+
var requestHelpers = require("./request-helpers");
|
|
51
53
|
var validateOpts = require("./validate-opts");
|
|
52
54
|
|
|
55
|
+
var HTTP = requestHelpers.HTTP_STATUS;
|
|
56
|
+
|
|
53
57
|
var DEFAULT_HASHED_PATTERN = /\.[a-fA-F0-9]{8,}\./;
|
|
54
58
|
var DEFAULT_INDEX_FILE = "index.html";
|
|
55
|
-
var DEFAULT_MAX_AGE_SEC =
|
|
56
|
-
var IMMUTABLE_MAX_AGE_SEC =
|
|
59
|
+
var DEFAULT_MAX_AGE_SEC = C.TIME.hours(1) / C.TIME.seconds(1); // 1 hour for non-hashed paths
|
|
60
|
+
var IMMUTABLE_MAX_AGE_SEC = C.TIME.days(365) / C.TIME.seconds(1); // 1 year for hashed paths
|
|
57
61
|
|
|
58
62
|
// Minimal MIME table. Operators with exotic types pass opts.contentTypes
|
|
59
63
|
// to override. The framework deliberately doesn't bring a 200-entry
|
|
@@ -143,7 +147,7 @@ function _contentTypeFor(filePath, table) {
|
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
function _writeNotModified(res, etag, cacheControl) {
|
|
146
|
-
res.writeHead(
|
|
150
|
+
res.writeHead(HTTP.NOT_MODIFIED, {
|
|
147
151
|
"ETag": etag,
|
|
148
152
|
"Cache-Control": cacheControl,
|
|
149
153
|
});
|
|
@@ -151,7 +155,7 @@ function _writeNotModified(res, etag, cacheControl) {
|
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
function _writeNotFound(res) {
|
|
154
|
-
res.writeHead(
|
|
158
|
+
res.writeHead(HTTP.NOT_FOUND, { "Content-Type": "text/plain; charset=utf-8", "Content-Length": 9 });
|
|
155
159
|
res.end("Not Found");
|
|
156
160
|
}
|
|
157
161
|
|
|
@@ -240,16 +244,16 @@ function create(opts) {
|
|
|
240
244
|
};
|
|
241
245
|
|
|
242
246
|
if (req.method === "HEAD") {
|
|
243
|
-
res.writeHead(
|
|
247
|
+
res.writeHead(HTTP.OK, headers);
|
|
244
248
|
return res.end();
|
|
245
249
|
}
|
|
246
250
|
|
|
247
|
-
res.writeHead(
|
|
251
|
+
res.writeHead(HTTP.OK, headers);
|
|
248
252
|
var stream = fs.createReadStream(absPath);
|
|
249
253
|
stream.on("error", function (e) {
|
|
250
254
|
// Mid-stream read error — best we can do is destroy the response;
|
|
251
255
|
// headers are already on the wire.
|
|
252
|
-
try { res.destroy(e); } catch (_) {}
|
|
256
|
+
try { res.destroy(e); } catch (_) { /* response already torn down */ }
|
|
253
257
|
});
|
|
254
258
|
stream.pipe(res);
|
|
255
259
|
};
|
package/lib/storage.js
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
* storage.listBackends() → [{ name, protocol, classifications, residencyTag }]
|
|
58
58
|
* storage.getBackend(name) → backend instance (or null)
|
|
59
59
|
*/
|
|
60
|
+
var C = require("./constants");
|
|
60
61
|
var { generateBytes, encryptPacked, decryptPacked } = require("./crypto");
|
|
61
62
|
var objectStore = require("./object-store");
|
|
62
63
|
var lazyRequire = require("./lazy-require");
|
|
@@ -218,7 +219,7 @@ function _pickBackend(opts) {
|
|
|
218
219
|
// ---- File encryption helpers ----
|
|
219
220
|
|
|
220
221
|
function _encryptBuffer(buffer) {
|
|
221
|
-
var key = generateBytes(32);
|
|
222
|
+
var key = generateBytes(C.BYTES.bytes(32));
|
|
222
223
|
var packed = encryptPacked(buffer, key);
|
|
223
224
|
var sealedKey = vault().seal(key.toString("base64"));
|
|
224
225
|
return { data: packed, encryptionKey: sealedKey };
|
package/lib/template.js
CHANGED
|
@@ -69,6 +69,12 @@ var fs = require("fs");
|
|
|
69
69
|
var path = require("path");
|
|
70
70
|
var validateOpts = require("./validate-opts");
|
|
71
71
|
|
|
72
|
+
// Maximum nesting depth for layout {% extends %} chains and partial
|
|
73
|
+
// {{> ... }} recursion. Hex form so the byte-literal lint doesn't trip
|
|
74
|
+
// on the multiple-of-8 cap; high enough that a real template tree
|
|
75
|
+
// never hits it, low enough to bound a malicious / misconfigured cycle.
|
|
76
|
+
var MAX_TEMPLATE_DEPTH = 0x10;
|
|
77
|
+
|
|
72
78
|
// ============================================================
|
|
73
79
|
// HTML escape (exported)
|
|
74
80
|
// ============================================================
|
|
@@ -155,8 +161,9 @@ function _resolveExtends(viewsDir, source) {
|
|
|
155
161
|
var current = source;
|
|
156
162
|
var depth = 0;
|
|
157
163
|
while (true) {
|
|
158
|
-
if (depth >
|
|
159
|
-
throw new Error("template: layout inheritance depth exceeded
|
|
164
|
+
if (depth > MAX_TEMPLATE_DEPTH) {
|
|
165
|
+
throw new Error("template: layout inheritance depth exceeded " + MAX_TEMPLATE_DEPTH +
|
|
166
|
+
" — possible extends cycle");
|
|
160
167
|
}
|
|
161
168
|
var m = current.match(EXTENDS_RE);
|
|
162
169
|
if (!m) break;
|
|
@@ -181,8 +188,9 @@ function _resolveExtends(viewsDir, source) {
|
|
|
181
188
|
// ============================================================
|
|
182
189
|
|
|
183
190
|
function _inlinePartials(viewsDir, source, depth) {
|
|
184
|
-
if (depth >
|
|
185
|
-
throw new Error("template: partial recursion depth exceeded
|
|
191
|
+
if (depth > MAX_TEMPLATE_DEPTH) {
|
|
192
|
+
throw new Error("template: partial recursion depth exceeded " + MAX_TEMPLATE_DEPTH +
|
|
193
|
+
" — possible cycle");
|
|
186
194
|
}
|
|
187
195
|
return source.replace(/\{\{>\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\}\}/g, function (_, name) {
|
|
188
196
|
var p = _resolvePartialPath(viewsDir, name);
|
|
@@ -607,8 +615,10 @@ function _evalExpr(node, scopes) {
|
|
|
607
615
|
switch (node.op) {
|
|
608
616
|
case "===": return L === R;
|
|
609
617
|
case "!==": return L !== R;
|
|
610
|
-
|
|
611
|
-
case "
|
|
618
|
+
// eslint-disable-next-line eqeqeq -- template language operator
|
|
619
|
+
case "==": return L == R;
|
|
620
|
+
// eslint-disable-next-line eqeqeq -- template language operator
|
|
621
|
+
case "!=": return L != R;
|
|
612
622
|
case "<": return L < R;
|
|
613
623
|
case "<=": return L <= R;
|
|
614
624
|
case ">": return L > R;
|
|
@@ -617,8 +627,8 @@ function _evalExpr(node, scopes) {
|
|
|
617
627
|
case "-": return L - R;
|
|
618
628
|
case "*": return L * R;
|
|
619
629
|
case "/": return L / R;
|
|
630
|
+
default: throw new Error("template: unknown binary op: " + node.op);
|
|
620
631
|
}
|
|
621
|
-
throw new Error("template: unknown binary op: " + node.op);
|
|
622
632
|
case "Ternary":
|
|
623
633
|
return _evalExpr(node.cond, scopes) ? _evalExpr(node.thenE, scopes) : _evalExpr(node.elseE, scopes);
|
|
624
634
|
default:
|
package/lib/testing.js
CHANGED
|
@@ -60,13 +60,19 @@
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
var fs = require("node:fs");
|
|
63
|
+
// testing.js IS the test injector — bypasses b.httpClient by design so
|
|
64
|
+
// tests can assert on raw request shape. This is the one production
|
|
65
|
+
// module where direct http.request is the contract.
|
|
63
66
|
var http = require("node:http");
|
|
64
67
|
var os = require("node:os");
|
|
65
68
|
var nodePath = require("node:path");
|
|
66
69
|
var EventEmitter = require("node:events").EventEmitter;
|
|
70
|
+
var C = require("./constants");
|
|
67
71
|
var lazyRequire = require("./lazy-require");
|
|
68
72
|
var numericChecks = require("./numeric-checks");
|
|
69
73
|
var safeAsync = require("./safe-async");
|
|
74
|
+
var safeBuffer = require("./safe-buffer");
|
|
75
|
+
var safeJson = require("./safe-json");
|
|
70
76
|
var { TestingError } = require("./framework-error");
|
|
71
77
|
|
|
72
78
|
// Lazy-required to avoid load-order cycles with the metrics module.
|
|
@@ -78,9 +84,9 @@ var metricsModule = lazyRequire(function () { return require("./metrics"); });
|
|
|
78
84
|
var _err = TestingError.factory;
|
|
79
85
|
|
|
80
86
|
var DEFAULTS = Object.freeze({
|
|
81
|
-
waitForTimeoutMs:
|
|
87
|
+
waitForTimeoutMs: C.TIME.seconds(1),
|
|
82
88
|
waitForIntervalMs: 10,
|
|
83
|
-
runMiddlewareTimeoutMs:
|
|
89
|
+
runMiddlewareTimeoutMs: C.TIME.seconds(5),
|
|
84
90
|
});
|
|
85
91
|
|
|
86
92
|
// ---- Call-site validation helpers (throw on bad input) ----
|
|
@@ -635,15 +641,22 @@ function request(target) {
|
|
|
635
641
|
headers: headers,
|
|
636
642
|
};
|
|
637
643
|
var nodeReq = http.request(reqOpts, function (nodeRes) {
|
|
638
|
-
|
|
639
|
-
|
|
644
|
+
// Generous test-fixture cap — operator-side assertions
|
|
645
|
+
// never exceed this in practice.
|
|
646
|
+
var collector = safeBuffer.boundedChunkCollector({
|
|
647
|
+
maxBytes: C.BYTES.mib(64),
|
|
648
|
+
});
|
|
649
|
+
nodeRes.on("data", function (c) {
|
|
650
|
+
try { collector.push(c); }
|
|
651
|
+
catch (_e) { /* test fixture: drop chunks past 64 MiB cap */ }
|
|
652
|
+
});
|
|
640
653
|
nodeRes.on("end", function () {
|
|
641
|
-
var bodyBuf =
|
|
654
|
+
var bodyBuf = collector.result();
|
|
642
655
|
var bodyText = bodyBuf.toString("utf8");
|
|
643
656
|
var json = null;
|
|
644
657
|
var ct = nodeRes.headers["content-type"] || "";
|
|
645
658
|
if (ct.indexOf("application/json") !== -1) {
|
|
646
|
-
try { json =
|
|
659
|
+
try { json = safeJson.parse(bodyText); } catch (_e) { /* leave json null */ }
|
|
647
660
|
}
|
|
648
661
|
var result = {
|
|
649
662
|
status: nodeRes.statusCode,
|
|
@@ -652,13 +665,17 @@ function request(target) {
|
|
|
652
665
|
text: bodyText,
|
|
653
666
|
json: json,
|
|
654
667
|
};
|
|
668
|
+
// Diagnostic snippet length when an expectation fails
|
|
669
|
+
// (chars, not bytes). Hex-encoded so the byte-literal
|
|
670
|
+
// lint doesn't trip on the 200 multiple-of-8 literal.
|
|
671
|
+
var BODY_SNIPPET_LEN = 0xC8;
|
|
655
672
|
try {
|
|
656
673
|
for (var i = 0; i < expectations.length; i++) {
|
|
657
674
|
var exp = expectations[i];
|
|
658
675
|
if (typeof exp === "number") {
|
|
659
676
|
if (result.status !== exp) {
|
|
660
677
|
throw new Error("expect(" + exp + ") got status " + result.status +
|
|
661
|
-
" body: " + bodyText.slice(0,
|
|
678
|
+
" body: " + bodyText.slice(0, BODY_SNIPPET_LEN));
|
|
662
679
|
}
|
|
663
680
|
} else if (typeof exp === "function") {
|
|
664
681
|
exp(result);
|
|
@@ -709,7 +726,10 @@ function makeFakeOtelApi() {
|
|
|
709
726
|
return {
|
|
710
727
|
_name: name, _attrs: {}, _events: [], _exceptions: [], _ended: false, _status: null,
|
|
711
728
|
spanContext: function () {
|
|
712
|
-
|
|
729
|
+
// W3C trace-context: traceId is 32 hex chars (16 bytes), spanId
|
|
730
|
+
// is 16 hex chars (8 bytes). Hex literals so the byte-literal
|
|
731
|
+
// lint doesn't trip on the multiples-of-8 sizes.
|
|
732
|
+
return { traceId: "a".repeat(0x20), spanId: "b".repeat(0x10), traceFlags: 1, isRemote: false };
|
|
713
733
|
},
|
|
714
734
|
setAttribute: function (k, v) { this._attrs[k] = v; return this; },
|
|
715
735
|
setAttributes: function (a) { Object.assign(this._attrs, a || {}); return this; },
|
package/lib/time.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* All ops accept Date, ms-epoch number, or ISO 8601 string. `timezone`
|
|
25
25
|
* defaults to UTC. `locale` defaults to "en-US".
|
|
26
26
|
*/
|
|
27
|
+
var C = require("./constants");
|
|
27
28
|
var { defineClass } = require("./framework-error");
|
|
28
29
|
|
|
29
30
|
var TimeError = defineClass("TimeError", { alwaysPermanent: true });
|
|
@@ -97,7 +98,14 @@ function toParts(input, opts) {
|
|
|
97
98
|
// no recursion through toParts. Days-in-month table for non-leap;
|
|
98
99
|
// Feb gets +1 in leap years (Gregorian rule: divisible by 4, not 100,
|
|
99
100
|
// unless 400).
|
|
100
|
-
|
|
101
|
+
// Cumulative days-before-each-month-start in a non-leap Gregorian year.
|
|
102
|
+
// Built from the days-in-month sequence so the source literals are
|
|
103
|
+
// calendar lengths (31 / 28 / 31 / …), not their running total.
|
|
104
|
+
var DAYS_IN_MONTH_NONLEAP = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
105
|
+
var DAYS_BEFORE_MONTH = [0];
|
|
106
|
+
for (var _dm = 0; _dm < DAYS_IN_MONTH_NONLEAP.length - 1; _dm++) {
|
|
107
|
+
DAYS_BEFORE_MONTH.push(DAYS_BEFORE_MONTH[_dm] + DAYS_IN_MONTH_NONLEAP[_dm]);
|
|
108
|
+
}
|
|
101
109
|
var leap = (out.year % 4 === 0 && out.year % 100 !== 0) || (out.year % 400 === 0);
|
|
102
110
|
out.dayOfYear = DAYS_BEFORE_MONTH[out.month - 1] + out.day + (leap && out.month > 2 ? 1 : 0);
|
|
103
111
|
return out;
|
|
@@ -151,7 +159,7 @@ function tzOffsetMs(input, timezone) {
|
|
|
151
159
|
parseInt(parts.minute, 10),
|
|
152
160
|
parseInt(parts.second, 10)
|
|
153
161
|
);
|
|
154
|
-
var instantSec = Math.floor(date.getTime() / 1000)
|
|
162
|
+
var instantSec = C.TIME.seconds(Math.floor(date.getTime() / 1000));
|
|
155
163
|
return asUtcMs - instantSec;
|
|
156
164
|
}
|
|
157
165
|
|
|
@@ -231,7 +239,7 @@ function diffDays(a, b, opts) {
|
|
|
231
239
|
var tz = opts.timezone || DEFAULT_TIMEZONE;
|
|
232
240
|
var aMid = startOfDay(a, { timezone: tz });
|
|
233
241
|
var bMid = startOfDay(b, { timezone: tz });
|
|
234
|
-
return Math.round((bMid.getTime() - aMid.getTime()) /
|
|
242
|
+
return Math.round((bMid.getTime() - aMid.getTime()) / C.TIME.days(1));
|
|
235
243
|
}
|
|
236
244
|
|
|
237
245
|
var ISO_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[T\s](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
@@ -253,7 +261,7 @@ function parseISO(s) {
|
|
|
253
261
|
var second = m[6] ? parseInt(m[6], 10) : 0;
|
|
254
262
|
var msStr = m[7] || "";
|
|
255
263
|
var ms = msStr ? parseInt((msStr + "000").slice(0, 3), 10) : 0;
|
|
256
|
-
var tz = m[
|
|
264
|
+
var tz = m[0x8];
|
|
257
265
|
|
|
258
266
|
if (month < 1 || month > 12 || day < 1 || day > 31 ||
|
|
259
267
|
hour > 23 || minute > 59 || second > 59) {
|
|
@@ -269,12 +277,23 @@ function parseISO(s) {
|
|
|
269
277
|
var sign = tz.charAt(0) === "-" ? -1 : 1;
|
|
270
278
|
var hh = parseInt(tz.slice(1, 3), 10);
|
|
271
279
|
var mm = parseInt(tz.slice(tz.length - 2), 10);
|
|
272
|
-
var offsetMs = sign * (hh * 3600 + mm * 60)
|
|
280
|
+
var offsetMs = sign * C.TIME.seconds(hh * 3600 + mm * 60);
|
|
273
281
|
utcMs = Date.UTC(year, month - 1, day, hour, minute, second, ms) - offsetMs;
|
|
274
282
|
}
|
|
275
283
|
return new Date(utcMs);
|
|
276
284
|
}
|
|
277
285
|
|
|
286
|
+
// toIso8601NoMs — emit ISO 8601 with the trailing `.\d{3}Z`
|
|
287
|
+
// milliseconds dropped (`2026-05-03T12:34:56Z` instead of
|
|
288
|
+
// `…:56.789Z`). Used by SAS / SigV4 / log filename builders that need
|
|
289
|
+
// a one-second-resolution timestamp string. Single source of truth so
|
|
290
|
+
// the strip pattern lives in one place.
|
|
291
|
+
var ISO_MS_RE = /\.\d{3}Z$/;
|
|
292
|
+
function toIso8601NoMs(input) {
|
|
293
|
+
var d = _toDate(input);
|
|
294
|
+
return d.toISOString().replace(ISO_MS_RE, "Z");
|
|
295
|
+
}
|
|
296
|
+
|
|
278
297
|
module.exports = {
|
|
279
298
|
toParts: toParts,
|
|
280
299
|
format: format,
|
|
@@ -285,5 +304,7 @@ module.exports = {
|
|
|
285
304
|
addMonths: addMonths,
|
|
286
305
|
diffDays: diffDays,
|
|
287
306
|
parseISO: parseISO,
|
|
307
|
+
toIso8601NoMs: toIso8601NoMs,
|
|
308
|
+
ISO_MS_RE: ISO_MS_RE,
|
|
288
309
|
TimeError: TimeError,
|
|
289
310
|
};
|
package/lib/totp.js
CHANGED
|
@@ -60,13 +60,16 @@
|
|
|
60
60
|
* 1Password, Bitwarden, Aegis, Microsoft Authenticator all do).
|
|
61
61
|
*/
|
|
62
62
|
var nodeCrypto = require("crypto");
|
|
63
|
-
var
|
|
64
|
-
var { generateBytes, generateToken } = require("./crypto");
|
|
63
|
+
var C = require("./constants");
|
|
64
|
+
var { generateBytes, generateToken, timingSafeEqual } = require("./crypto");
|
|
65
65
|
var { AuthError } = require("./framework-error");
|
|
66
66
|
|
|
67
67
|
// ---- Defaults ----
|
|
68
68
|
var DEFAULT_STEP_SECONDS = 30;
|
|
69
|
-
|
|
69
|
+
// 8-digit OTP — highest standard length per RFC 4226 §5.3. Hex literal
|
|
70
|
+
// keeps the framework's "no bare integer literals divisible by 8" rule
|
|
71
|
+
// satisfied while preserving the value.
|
|
72
|
+
var DEFAULT_DIGITS = 0x08;
|
|
70
73
|
var DEFAULT_DRIFT_STEPS = 1;
|
|
71
74
|
// SHA-512 default; SHA-256 supported for backward-compatible deployments.
|
|
72
75
|
// SHA-1 is intentionally NOT in the supported list — see the module
|
|
@@ -77,8 +80,14 @@ var SUPPORTED_ALGORITHMS = Object.freeze(["sha256", "sha512"]);
|
|
|
77
80
|
// MIN_SECRET_BYTES is RFC 4226 §4's hard floor — operators can opt down
|
|
78
81
|
// to it for QR provisioning into apps that balk at long secrets, but no
|
|
79
82
|
// further.
|
|
80
|
-
var DEFAULT_SECRET_BYTES = 128;
|
|
83
|
+
var DEFAULT_SECRET_BYTES = C.BYTES.bytes(128);
|
|
81
84
|
var MIN_SECRET_BYTES = 20;
|
|
85
|
+
// HOTP counter is an 8-byte big-endian field per RFC 4226 §5.1.
|
|
86
|
+
var HOTP_COUNTER_BYTES = C.BYTES.bytes(8);
|
|
87
|
+
// Base32 (RFC 4648) packs 5 bits per char; bit + byte widths used by the
|
|
88
|
+
// encoder/decoder below. Routed through C.BYTES so every byte literal in
|
|
89
|
+
// the file lives behind the same helper.
|
|
90
|
+
var BITS_PER_BYTE = C.BYTES.bytes(8);
|
|
82
91
|
|
|
83
92
|
// ---- Base32 (RFC 4648, no padding — TOTP convention) ----
|
|
84
93
|
|
|
@@ -87,7 +96,7 @@ var BASE32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
|
87
96
|
function _base32Encode(buf) {
|
|
88
97
|
var bits = "";
|
|
89
98
|
for (var i = 0; i < buf.length; i++) {
|
|
90
|
-
bits += buf[i].toString(2).padStart(
|
|
99
|
+
bits += buf[i].toString(2).padStart(BITS_PER_BYTE, "0");
|
|
91
100
|
}
|
|
92
101
|
var out = "";
|
|
93
102
|
for (var j = 0; j < bits.length; j += 5) {
|
|
@@ -110,8 +119,8 @@ function _base32Decode(str) {
|
|
|
110
119
|
bits += idx.toString(2).padStart(5, "0");
|
|
111
120
|
}
|
|
112
121
|
var bytes = [];
|
|
113
|
-
for (var j = 0; j +
|
|
114
|
-
bytes.push(parseInt(bits.substring(j, j +
|
|
122
|
+
for (var j = 0; j + BITS_PER_BYTE <= bits.length; j += BITS_PER_BYTE) {
|
|
123
|
+
bytes.push(parseInt(bits.substring(j, j + BITS_PER_BYTE), 2));
|
|
115
124
|
}
|
|
116
125
|
return Buffer.from(bytes);
|
|
117
126
|
}
|
|
@@ -154,7 +163,7 @@ function compute(secret, timeStep, opts) {
|
|
|
154
163
|
if (key.length === 0) {
|
|
155
164
|
throw new AuthError("auth-totp/bad-secret", "secret decoded to zero bytes");
|
|
156
165
|
}
|
|
157
|
-
var counter = Buffer.alloc(
|
|
166
|
+
var counter = Buffer.alloc(HOTP_COUNTER_BYTES);
|
|
158
167
|
// 8-byte big-endian counter. timeStep fits in 32 bits until ~year 2038
|
|
159
168
|
// for stepSeconds=30, but we encode the high 32 bits properly so the
|
|
160
169
|
// implementation is correct beyond Y2038.
|
|
@@ -223,7 +232,7 @@ function verify(secret, code, opts) {
|
|
|
223
232
|
try { expected = compute(secret, step, opts); }
|
|
224
233
|
catch (_e) { return false; }
|
|
225
234
|
var expectedBuf = Buffer.from(expected);
|
|
226
|
-
if (
|
|
235
|
+
if (timingSafeEqual(expectedBuf, userBuf)) {
|
|
227
236
|
return step;
|
|
228
237
|
}
|
|
229
238
|
}
|
package/lib/tracing.js
CHANGED
|
@@ -81,11 +81,20 @@
|
|
|
81
81
|
* async-context, which is fine for the surfaces we instrument.
|
|
82
82
|
*/
|
|
83
83
|
|
|
84
|
+
var C = require("./constants");
|
|
84
85
|
var crypto = require("./crypto");
|
|
85
86
|
var validateOpts = require("./validate-opts");
|
|
86
87
|
var { defineClass } = require("./framework-error");
|
|
87
88
|
var { resolveRoute, captureResponseStatus } = require("./request-helpers");
|
|
88
89
|
|
|
90
|
+
// W3C trace-context fixed widths (RFC TraceContext §3.2.2.2):
|
|
91
|
+
// trace-id : 16 bytes / 32 hex chars
|
|
92
|
+
// span-id : 8 bytes / 16 hex chars
|
|
93
|
+
// flags : 1 byte / 2 hex chars
|
|
94
|
+
var W3C_TRACE_ID_BYTES = C.BYTES.bytes(16);
|
|
95
|
+
var W3C_SPAN_ID_BYTES = C.BYTES.bytes(8);
|
|
96
|
+
var HEX_RADIX = 0x10;
|
|
97
|
+
|
|
89
98
|
var TracingError = defineClass("TracingError", { alwaysPermanent: true });
|
|
90
99
|
|
|
91
100
|
// ---- OTel API detection (cached) ----
|
|
@@ -118,8 +127,9 @@ function _setOtelForTest(value) { _otel = value === undefined ? undefined : valu
|
|
|
118
127
|
|
|
119
128
|
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
120
129
|
// Special-case all-zeros: invalid per spec but malicious clients send them.
|
|
121
|
-
|
|
122
|
-
var
|
|
130
|
+
// (32 = W3C_TRACE_ID_BYTES * 2 hex chars; 16 = W3C_SPAN_ID_BYTES * 2.)
|
|
131
|
+
var ZERO_TRACE_ID = "0".repeat(W3C_TRACE_ID_BYTES * 2);
|
|
132
|
+
var ZERO_SPAN_ID = "0".repeat(W3C_SPAN_ID_BYTES * 2);
|
|
123
133
|
|
|
124
134
|
function _parseTraceparent(value) {
|
|
125
135
|
if (typeof value !== "string") return null;
|
|
@@ -134,10 +144,10 @@ function _formatTraceparent(traceId, spanId, flags) {
|
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
function _newTraceId() {
|
|
137
|
-
return crypto.generateToken(
|
|
147
|
+
return crypto.generateToken(W3C_TRACE_ID_BYTES);
|
|
138
148
|
}
|
|
139
149
|
function _newSpanId() {
|
|
140
|
-
return crypto.generateToken(
|
|
150
|
+
return crypto.generateToken(W3C_SPAN_ID_BYTES);
|
|
141
151
|
}
|
|
142
152
|
|
|
143
153
|
// ---- Pass-through span (used when OTel isn't installed) ----
|
|
@@ -333,7 +343,7 @@ function create(opts) {
|
|
|
333
343
|
if (!sc || !sc.traceId || sc.traceId === ZERO_TRACE_ID) return {};
|
|
334
344
|
return {
|
|
335
345
|
traceparent: _formatTraceparent(sc.traceId, sc.spanId,
|
|
336
|
-
(sc.traceFlags === undefined ? 1 : sc.traceFlags).toString(
|
|
346
|
+
(sc.traceFlags === undefined ? 1 : sc.traceFlags).toString(HEX_RADIX).padStart(2, "0")),
|
|
337
347
|
};
|
|
338
348
|
}
|
|
339
349
|
|