@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/network-nts.js
CHANGED
|
@@ -5,6 +5,7 @@ var dgram = require("node:dgram");
|
|
|
5
5
|
var nodeCrypto = require("node:crypto");
|
|
6
6
|
|
|
7
7
|
var C = require("./constants");
|
|
8
|
+
var { timingSafeEqual } = require("./crypto");
|
|
8
9
|
var validateOpts = require("./validate-opts");
|
|
9
10
|
var { defineClass } = require("./framework-error");
|
|
10
11
|
|
|
@@ -12,8 +13,28 @@ var NtsError = defineClass("NtsError", { alwaysPermanent: false });
|
|
|
12
13
|
|
|
13
14
|
var NTS_KE_DEFAULT_PORT = 4460;
|
|
14
15
|
var NTPV4_DEFAULT_PORT = 123;
|
|
16
|
+
// RFC 5905 §6 — seconds between 1900-01-01T00:00Z (NTP epoch) and
|
|
17
|
+
// 1970-01-01T00:00Z (Unix epoch). Protocol-fixed (not a tunable).
|
|
15
18
|
var NTP_TO_UNIX_OFFSET_SECONDS = 2208988800;
|
|
16
19
|
|
|
20
|
+
// Protocol-fixed byte counts and offsets — every numeric literal that
|
|
21
|
+
// represents a wire-format size or position routes through C.BYTES.bytes
|
|
22
|
+
// (a value-passthrough) so the codebase has a single source of truth
|
|
23
|
+
// for "what shape is this number".
|
|
24
|
+
var AES_BLOCK_BYTES = C.BYTES.bytes(16); // AES-128/192/256 block size (RFC 3962)
|
|
25
|
+
var NTP_PACKET_BYTES = C.BYTES.bytes(48); // RFC 5905 §7.3 packet header length
|
|
26
|
+
var NTP_TX_TIMESTAMP_OFFSET = C.BYTES.bytes(40); // RFC 5905 §7.3 — Transmit Timestamp
|
|
27
|
+
var POLY1305_TAG_BYTES = C.BYTES.bytes(16); // RFC 8439 §2.5 tag length
|
|
28
|
+
var SIV_KEY_32_BYTES = C.BYTES.bytes(32);
|
|
29
|
+
var SIV_KEY_48_BYTES = C.BYTES.bytes(48);
|
|
30
|
+
var SIV_KEY_64_BYTES = C.BYTES.bytes(64);
|
|
31
|
+
var UNIQUE_ID_BYTES = C.BYTES.bytes(32); // RFC 8915 §5.3 unique-identifier extension
|
|
32
|
+
var CHACHA20_NONCE_BYTES = C.BYTES.bytes(12);
|
|
33
|
+
|
|
34
|
+
// Bits per byte — used to derive the AES key bit-width from the byte
|
|
35
|
+
// length (16-byte key → 128-bit cipher → "aes-128-..." suffix).
|
|
36
|
+
var BITS_PER_BYTE = 8;
|
|
37
|
+
|
|
17
38
|
var REC_END = 0;
|
|
18
39
|
var REC_NEXT_PROTOCOL = 1;
|
|
19
40
|
var REC_ERROR = 2;
|
|
@@ -64,7 +85,9 @@ function _decodeRecords(buf) {
|
|
|
64
85
|
}
|
|
65
86
|
|
|
66
87
|
function _aesEncryptBlock(key, block) {
|
|
67
|
-
|
|
88
|
+
// key.length is in bytes (16/24/32); multiply by BITS_PER_BYTE to get
|
|
89
|
+
// the OpenSSL cipher-name suffix (128/192/256).
|
|
90
|
+
var c = nodeCrypto.createCipheriv("aes-" + (key.length * BITS_PER_BYTE) + "-ecb", key, Buffer.alloc(0));
|
|
68
91
|
c.setAutoPadding(false);
|
|
69
92
|
return Buffer.concat([c.update(block), c.final()]);
|
|
70
93
|
}
|
|
@@ -87,7 +110,7 @@ function _xorBuf(a, b) {
|
|
|
87
110
|
}
|
|
88
111
|
|
|
89
112
|
function _cmacSubkeys(key) {
|
|
90
|
-
var L = _aesEncryptBlock(key, Buffer.alloc(
|
|
113
|
+
var L = _aesEncryptBlock(key, Buffer.alloc(AES_BLOCK_BYTES, 0));
|
|
91
114
|
var Rb = Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x87]);
|
|
92
115
|
var K1 = _shl1(L);
|
|
93
116
|
if (L[0] & 0x80) K1 = _xorBuf(K1, Rb);
|
|
@@ -98,25 +121,25 @@ function _cmacSubkeys(key) {
|
|
|
98
121
|
|
|
99
122
|
function _cmac(key, message) {
|
|
100
123
|
var subkeys = _cmacSubkeys(key);
|
|
101
|
-
var n = Math.ceil(message.length /
|
|
124
|
+
var n = Math.ceil(message.length / AES_BLOCK_BYTES);
|
|
102
125
|
if (n === 0) n = 1;
|
|
103
|
-
var lastIsComplete = (message.length > 0) && (message.length %
|
|
126
|
+
var lastIsComplete = (message.length > 0) && (message.length % AES_BLOCK_BYTES === 0);
|
|
104
127
|
var blocks = [];
|
|
105
128
|
for (var i = 0; i < n - 1; i++) {
|
|
106
|
-
blocks.push(message.slice(i *
|
|
129
|
+
blocks.push(message.slice(i * AES_BLOCK_BYTES, i * AES_BLOCK_BYTES + AES_BLOCK_BYTES));
|
|
107
130
|
}
|
|
108
131
|
var lastBlock;
|
|
109
132
|
if (lastIsComplete) {
|
|
110
|
-
lastBlock = _xorBuf(message.slice((n - 1) *
|
|
133
|
+
lastBlock = _xorBuf(message.slice((n - 1) * AES_BLOCK_BYTES, n * AES_BLOCK_BYTES), subkeys.K1);
|
|
111
134
|
} else {
|
|
112
|
-
var rem = message.slice((n - 1) *
|
|
113
|
-
var padded = Buffer.alloc(
|
|
135
|
+
var rem = message.slice((n - 1) * AES_BLOCK_BYTES);
|
|
136
|
+
var padded = Buffer.alloc(AES_BLOCK_BYTES);
|
|
114
137
|
rem.copy(padded);
|
|
115
138
|
padded[rem.length] = 0x80;
|
|
116
139
|
lastBlock = _xorBuf(padded, subkeys.K2);
|
|
117
140
|
}
|
|
118
141
|
blocks.push(lastBlock);
|
|
119
|
-
var X = Buffer.alloc(
|
|
142
|
+
var X = Buffer.alloc(AES_BLOCK_BYTES, 0);
|
|
120
143
|
for (var b = 0; b < blocks.length; b++) {
|
|
121
144
|
X = _aesEncryptBlock(key, _xorBuf(X, blocks[b]));
|
|
122
145
|
}
|
|
@@ -131,18 +154,18 @@ function _dbl(buf) {
|
|
|
131
154
|
}
|
|
132
155
|
|
|
133
156
|
function _s2v(K, strings, plaintext) {
|
|
134
|
-
var D = _cmac(K, Buffer.alloc(
|
|
157
|
+
var D = _cmac(K, Buffer.alloc(AES_BLOCK_BYTES, 0));
|
|
135
158
|
for (var i = 0; i < strings.length; i++) {
|
|
136
159
|
D = _xorBuf(_dbl(D), _cmac(K, strings[i]));
|
|
137
160
|
}
|
|
138
161
|
var T;
|
|
139
|
-
if (plaintext.length >=
|
|
140
|
-
var head = plaintext.slice(0, plaintext.length -
|
|
141
|
-
var tail = plaintext.slice(plaintext.length -
|
|
162
|
+
if (plaintext.length >= AES_BLOCK_BYTES) {
|
|
163
|
+
var head = plaintext.slice(0, plaintext.length - AES_BLOCK_BYTES);
|
|
164
|
+
var tail = plaintext.slice(plaintext.length - AES_BLOCK_BYTES);
|
|
142
165
|
var xored = _xorBuf(tail, D);
|
|
143
166
|
T = Buffer.concat([head, xored]);
|
|
144
167
|
} else {
|
|
145
|
-
var padded = Buffer.alloc(
|
|
168
|
+
var padded = Buffer.alloc(AES_BLOCK_BYTES);
|
|
146
169
|
plaintext.copy(padded);
|
|
147
170
|
padded[plaintext.length] = 0x80;
|
|
148
171
|
T = _xorBuf(_dbl(D), padded);
|
|
@@ -152,14 +175,15 @@ function _s2v(K, strings, plaintext) {
|
|
|
152
175
|
|
|
153
176
|
function _aesCtr(key, iv, data) {
|
|
154
177
|
var ivCopy = Buffer.from(iv);
|
|
178
|
+
// Clear the high bit of the 8th and 12th counter octets per RFC 5297 §2.6.
|
|
155
179
|
ivCopy[8] &= 0x7f;
|
|
156
180
|
ivCopy[12] &= 0x7f;
|
|
157
|
-
var c = nodeCrypto.createCipheriv("aes-" + (key.length *
|
|
181
|
+
var c = nodeCrypto.createCipheriv("aes-" + (key.length * BITS_PER_BYTE) + "-ctr", key, ivCopy);
|
|
158
182
|
return Buffer.concat([c.update(data), c.final()]);
|
|
159
183
|
}
|
|
160
184
|
|
|
161
185
|
function aesSivEncrypt(K, plaintext, associatedData) {
|
|
162
|
-
if (K.length !==
|
|
186
|
+
if (K.length !== SIV_KEY_32_BYTES && K.length !== SIV_KEY_48_BYTES && K.length !== SIV_KEY_64_BYTES) {
|
|
163
187
|
throw new NtsError("nts/bad-key", "AES-SIV key must be 32/48/64 bytes, got " + K.length);
|
|
164
188
|
}
|
|
165
189
|
var half = K.length / 2;
|
|
@@ -174,11 +198,11 @@ function aesSivDecrypt(K, ciphertextWithIv, associatedData) {
|
|
|
174
198
|
var half = K.length / 2;
|
|
175
199
|
var K1 = K.slice(0, half);
|
|
176
200
|
var K2 = K.slice(half);
|
|
177
|
-
var V = ciphertextWithIv.slice(0,
|
|
178
|
-
var ct = ciphertextWithIv.slice(
|
|
201
|
+
var V = ciphertextWithIv.slice(0, AES_BLOCK_BYTES);
|
|
202
|
+
var ct = ciphertextWithIv.slice(AES_BLOCK_BYTES);
|
|
179
203
|
var pt = _aesCtr(K2, V, ct);
|
|
180
204
|
var Vcheck = _s2v(K1, associatedData || [], pt);
|
|
181
|
-
if (!
|
|
205
|
+
if (!timingSafeEqual(V, Vcheck)) {
|
|
182
206
|
throw new NtsError("nts/auth-failed", "AES-SIV authentication failed");
|
|
183
207
|
}
|
|
184
208
|
return pt;
|
|
@@ -207,7 +231,7 @@ function _exportKeys(socket, aeadId) {
|
|
|
207
231
|
var label = "EXPORTER-network-time-security";
|
|
208
232
|
var contextC2S = Buffer.from([0x00, 0x00, (aeadId >> 8) & 0xff, aeadId & 0xff, 0x00]);
|
|
209
233
|
var contextS2C = Buffer.from([0x00, 0x00, (aeadId >> 8) & 0xff, aeadId & 0xff, 0x01]);
|
|
210
|
-
var keyLen = aeadId === AEAD_AES_SIV_CMAC_256 ?
|
|
234
|
+
var keyLen = aeadId === AEAD_AES_SIV_CMAC_256 ? SIV_KEY_32_BYTES : SIV_KEY_32_BYTES;
|
|
211
235
|
var c2s = socket.exportKeyingMaterial(keyLen, label, contextC2S);
|
|
212
236
|
var s2c = socket.exportKeyingMaterial(keyLen, label, contextS2C);
|
|
213
237
|
return { c2s: c2s, s2c: s2c };
|
|
@@ -216,9 +240,7 @@ function _exportKeys(socket, aeadId) {
|
|
|
216
240
|
function performKeHandshake(opts) {
|
|
217
241
|
opts = opts || {};
|
|
218
242
|
validateOpts(opts, ["host", "port", "servername", "aead", "ca", "timeoutMs"], "nts.performKeHandshake");
|
|
219
|
-
|
|
220
|
-
throw new NtsError("nts/bad-host", "nts.performKeHandshake: host required");
|
|
221
|
-
}
|
|
243
|
+
validateOpts.requireNonEmptyString(opts.host, "nts.performKeHandshake: host", NtsError, "nts/bad-host");
|
|
222
244
|
var timeoutMs = opts.timeoutMs || C.TIME.seconds(10);
|
|
223
245
|
return new Promise(function (resolve, reject) {
|
|
224
246
|
var settled = false;
|
|
@@ -238,7 +260,7 @@ function performKeHandshake(opts) {
|
|
|
238
260
|
if (opts.ca) connectOpts.ca = opts.ca;
|
|
239
261
|
var sock = tls.connect(connectOpts);
|
|
240
262
|
var timer = setTimeout(function () {
|
|
241
|
-
try { sock.destroy(); } catch (_e) {}
|
|
263
|
+
try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
242
264
|
done(new NtsError("nts/ke-timeout", "NTS-KE handshake timed out after " + timeoutMs + "ms"));
|
|
243
265
|
}, timeoutMs);
|
|
244
266
|
timer.unref && timer.unref();
|
|
@@ -249,7 +271,7 @@ function performKeHandshake(opts) {
|
|
|
249
271
|
sock.on("secureConnect", function () {
|
|
250
272
|
if (sock.alpnProtocol !== "ntske/1") {
|
|
251
273
|
clearTimeout(timer);
|
|
252
|
-
try { sock.destroy(); } catch (_e) {}
|
|
274
|
+
try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
253
275
|
done(new NtsError("nts/bad-alpn",
|
|
254
276
|
"NTS-KE server did not negotiate ALPN 'ntske/1', got " + JSON.stringify(sock.alpnProtocol)));
|
|
255
277
|
return;
|
|
@@ -268,7 +290,7 @@ function performKeHandshake(opts) {
|
|
|
268
290
|
clearTimeout(timer);
|
|
269
291
|
var errRec = records.find(function (r) { return r.type === REC_ERROR; });
|
|
270
292
|
if (errRec) {
|
|
271
|
-
try { sock.destroy(); } catch (_e) {}
|
|
293
|
+
try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
272
294
|
done(new NtsError("nts/ke-error", "NTS-KE server returned error code " + errRec.body.readUInt16BE(0)));
|
|
273
295
|
return;
|
|
274
296
|
}
|
|
@@ -280,20 +302,20 @@ function performKeHandshake(opts) {
|
|
|
280
302
|
}
|
|
281
303
|
var aeadRec = records.find(function (r) { return r.type === REC_AEAD_ALGORITHM; });
|
|
282
304
|
if (!aeadRec || aeadRec.body.length < 2) {
|
|
283
|
-
try { sock.destroy(); } catch (_e) {}
|
|
305
|
+
try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
284
306
|
done(new NtsError("nts/no-aead", "NTS-KE response missing AEAD algorithm"));
|
|
285
307
|
return;
|
|
286
308
|
}
|
|
287
309
|
var aeadId = aeadRec.body.readUInt16BE(0);
|
|
288
310
|
if (aeadId !== AEAD_AES_SIV_CMAC_256 && aeadId !== AEAD_CHACHA20_POLY1305) {
|
|
289
|
-
try { sock.destroy(); } catch (_e) {}
|
|
311
|
+
try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
290
312
|
done(new NtsError("nts/unsupported-aead", "NTS-KE server selected unsupported AEAD " + aeadId));
|
|
291
313
|
return;
|
|
292
314
|
}
|
|
293
315
|
var cookies = records.filter(function (r) { return r.type === REC_NEW_COOKIE; })
|
|
294
316
|
.map(function (r) { return r.body; });
|
|
295
317
|
if (cookies.length === 0) {
|
|
296
|
-
try { sock.destroy(); } catch (_e) {}
|
|
318
|
+
try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
297
319
|
done(new NtsError("nts/no-cookies", "NTS-KE response contained no cookies"));
|
|
298
320
|
return;
|
|
299
321
|
}
|
|
@@ -304,7 +326,7 @@ function performKeHandshake(opts) {
|
|
|
304
326
|
var portRec = records.find(function (r) { return r.type === REC_NTPV4_PORT; });
|
|
305
327
|
if (portRec && portRec.body.length >= 2) ntpPort = portRec.body.readUInt16BE(0);
|
|
306
328
|
var keys = _exportKeys(sock, aeadId);
|
|
307
|
-
try { sock.end(); } catch (_e) {}
|
|
329
|
+
try { sock.end(); } catch (_e) { /* best-effort socket close */ }
|
|
308
330
|
done(null, {
|
|
309
331
|
aeadId: aeadId,
|
|
310
332
|
c2sKey: keys.c2s,
|
|
@@ -316,7 +338,7 @@ function performKeHandshake(opts) {
|
|
|
316
338
|
});
|
|
317
339
|
} catch (e) {
|
|
318
340
|
clearTimeout(timer);
|
|
319
|
-
try { sock.destroy(); } catch (_e) {}
|
|
341
|
+
try { sock.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
320
342
|
done(e);
|
|
321
343
|
}
|
|
322
344
|
});
|
|
@@ -338,7 +360,7 @@ function _aeadEncrypt(aeadId, key, nonce, plaintext, aad) {
|
|
|
338
360
|
return aesSivEncrypt(key, plaintext, ad);
|
|
339
361
|
}
|
|
340
362
|
if (aeadId === AEAD_CHACHA20_POLY1305) {
|
|
341
|
-
var c = nodeCrypto.createCipheriv("chacha20-poly1305", key, nonce, { authTagLength:
|
|
363
|
+
var c = nodeCrypto.createCipheriv("chacha20-poly1305", key, nonce, { authTagLength: POLY1305_TAG_BYTES });
|
|
342
364
|
if (aad) c.setAAD(aad, { plaintextLength: plaintext.length });
|
|
343
365
|
var ct = Buffer.concat([c.update(plaintext), c.final()]);
|
|
344
366
|
var tag = c.getAuthTag();
|
|
@@ -353,9 +375,9 @@ function _aeadDecrypt(aeadId, key, nonce, ciphertext, aad) {
|
|
|
353
375
|
return aesSivDecrypt(key, ciphertext, ad);
|
|
354
376
|
}
|
|
355
377
|
if (aeadId === AEAD_CHACHA20_POLY1305) {
|
|
356
|
-
var ct = ciphertext.slice(0, ciphertext.length -
|
|
357
|
-
var tag = ciphertext.slice(ciphertext.length -
|
|
358
|
-
var d = nodeCrypto.createDecipheriv("chacha20-poly1305", key, nonce, { authTagLength:
|
|
378
|
+
var ct = ciphertext.slice(0, ciphertext.length - POLY1305_TAG_BYTES);
|
|
379
|
+
var tag = ciphertext.slice(ciphertext.length - POLY1305_TAG_BYTES);
|
|
380
|
+
var d = nodeCrypto.createDecipheriv("chacha20-poly1305", key, nonce, { authTagLength: POLY1305_TAG_BYTES });
|
|
359
381
|
if (aad) d.setAAD(aad, { plaintextLength: ct.length });
|
|
360
382
|
d.setAuthTag(tag);
|
|
361
383
|
return Buffer.concat([d.update(ct), d.final()]);
|
|
@@ -364,8 +386,8 @@ function _aeadDecrypt(aeadId, key, nonce, ciphertext, aad) {
|
|
|
364
386
|
}
|
|
365
387
|
|
|
366
388
|
function _nonceForAead(aeadId) {
|
|
367
|
-
if (aeadId === AEAD_AES_SIV_CMAC_256) return nodeCrypto.randomBytes(
|
|
368
|
-
return nodeCrypto.randomBytes(
|
|
389
|
+
if (aeadId === AEAD_AES_SIV_CMAC_256) return nodeCrypto.randomBytes(AES_BLOCK_BYTES);
|
|
390
|
+
return nodeCrypto.randomBytes(CHACHA20_NONCE_BYTES);
|
|
369
391
|
}
|
|
370
392
|
|
|
371
393
|
function _walkExtensions(msg, startOff) {
|
|
@@ -402,12 +424,12 @@ function querySingle(opts) {
|
|
|
402
424
|
function done(err, result) {
|
|
403
425
|
if (settled) return;
|
|
404
426
|
settled = true;
|
|
405
|
-
try { sock.close(); } catch (_e) {}
|
|
427
|
+
try { sock.close(); } catch (_e) { /* best-effort socket close */ }
|
|
406
428
|
if (err) reject(err); else resolve(result);
|
|
407
429
|
}
|
|
408
|
-
var unique = nodeCrypto.randomBytes(
|
|
430
|
+
var unique = nodeCrypto.randomBytes(UNIQUE_ID_BYTES);
|
|
409
431
|
var cookie = opts.cookies[0];
|
|
410
|
-
var packet = Buffer.alloc(
|
|
432
|
+
var packet = Buffer.alloc(NTP_PACKET_BYTES);
|
|
411
433
|
packet[0] = 0x23;
|
|
412
434
|
var ext1 = _encodeExtensionField(EXTENSION_UNIQUE_IDENTIFIER, unique);
|
|
413
435
|
var ext2 = _encodeExtensionField(EXTENSION_NTS_COOKIE, cookie);
|
|
@@ -436,16 +458,16 @@ function querySingle(opts) {
|
|
|
436
458
|
clearTimeout(timer);
|
|
437
459
|
var receiveTimeMs = Date.now();
|
|
438
460
|
try {
|
|
439
|
-
if (msg.length <
|
|
461
|
+
if (msg.length < NTP_PACKET_BYTES) {
|
|
440
462
|
done(new NtsError("nts/bad-reply", "NTS reply too short"));
|
|
441
463
|
return;
|
|
442
464
|
}
|
|
443
465
|
var exts;
|
|
444
|
-
try { exts = _walkExtensions(msg,
|
|
466
|
+
try { exts = _walkExtensions(msg, NTP_PACKET_BYTES); }
|
|
445
467
|
catch (e) { done(e); return; }
|
|
446
468
|
var uniqueExt = exts.find(function (e) { return e.type === EXTENSION_UNIQUE_IDENTIFIER; });
|
|
447
|
-
if (!uniqueExt || uniqueExt.body.length <
|
|
448
|
-
!
|
|
469
|
+
if (!uniqueExt || uniqueExt.body.length < UNIQUE_ID_BYTES ||
|
|
470
|
+
!timingSafeEqual(uniqueExt.body.slice(0, UNIQUE_ID_BYTES), unique)) {
|
|
449
471
|
done(new NtsError("nts/unique-mismatch", "NTS reply unique-identifier mismatch (replay or spoof)"));
|
|
450
472
|
return;
|
|
451
473
|
}
|
|
@@ -489,11 +511,11 @@ function querySingle(opts) {
|
|
|
489
511
|
opts.cookies.shift();
|
|
490
512
|
for (var ci = 0; ci < newCookies.length; ci++) opts.cookies.push(newCookies[ci]);
|
|
491
513
|
}
|
|
492
|
-
var ntpSeconds = msg.readUInt32BE(
|
|
493
|
-
var ntpFraction = msg.readUInt32BE(
|
|
514
|
+
var ntpSeconds = msg.readUInt32BE(NTP_TX_TIMESTAMP_OFFSET);
|
|
515
|
+
var ntpFraction = msg.readUInt32BE(NTP_TX_TIMESTAMP_OFFSET + 4);
|
|
494
516
|
var serverUnixSeconds = ntpSeconds - NTP_TO_UNIX_OFFSET_SECONDS;
|
|
495
|
-
var fracMs = Math.round(ntpFraction / 0x100000000
|
|
496
|
-
var serverTimeMs = serverUnixSeconds
|
|
517
|
+
var fracMs = Math.round(C.TIME.seconds(ntpFraction / 0x100000000));
|
|
518
|
+
var serverTimeMs = C.TIME.seconds(serverUnixSeconds) + fracMs;
|
|
497
519
|
var midpointMs = sendTimeMs + (receiveTimeMs - sendTimeMs) / 2;
|
|
498
520
|
var driftMs = serverTimeMs - midpointMs;
|
|
499
521
|
done(null, {
|
package/lib/network-proxy.js
CHANGED
|
@@ -6,12 +6,19 @@ var net = require("node:net");
|
|
|
6
6
|
var tls = require("node:tls");
|
|
7
7
|
|
|
8
8
|
var C = require("./constants");
|
|
9
|
-
var validateOpts = require("./validate-opts");
|
|
10
9
|
var lazyRequire = require("./lazy-require");
|
|
10
|
+
var safeUrl = require("./safe-url");
|
|
11
|
+
var validateOpts = require("./validate-opts");
|
|
11
12
|
var { defineClass } = require("./framework-error");
|
|
12
13
|
|
|
13
14
|
var ProxyError = defineClass("ProxyError", { alwaysPermanent: false });
|
|
14
15
|
|
|
16
|
+
// Protocol-fixed byte counts — passthrough through C.BYTES so the codebase
|
|
17
|
+
// has a single source of truth for "what shape is this number".
|
|
18
|
+
var IPV4_PREFIX_MAX_BITS = C.BYTES.bytes(32); // RFC 791 §3.1 IPv4 address bit-width
|
|
19
|
+
var DEFAULT_HTTPS_PORT = 443; // RFC 9110 §4.2.2
|
|
20
|
+
var DEFAULT_HTTP_PORT = C.BYTES.bytes(80); // RFC 9110 §4.2.1
|
|
21
|
+
|
|
15
22
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
16
23
|
|
|
17
24
|
var STATE = {
|
|
@@ -31,14 +38,18 @@ function _parseAuth(spec) {
|
|
|
31
38
|
|
|
32
39
|
function _parseProxyUrl(value) {
|
|
33
40
|
if (!value) return null;
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
try {
|
|
42
|
+
return safeUrl.parse(value, {
|
|
43
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
44
|
+
allowUserinfo: true, // proxy auth is conventionally placed in the URL
|
|
45
|
+
errorClass: ProxyError,
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (e && e.isSafeUrlError && e.code === "safe-url/protocol-disallowed") {
|
|
49
|
+
throw new ProxyError("proxy/bad-protocol", "proxy URL must be http:// or https://, got " + value);
|
|
50
|
+
}
|
|
36
51
|
throw new ProxyError("proxy/bad-url", "invalid proxy URL: " + value);
|
|
37
52
|
}
|
|
38
|
-
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
39
|
-
throw new ProxyError("proxy/bad-protocol", "proxy URL must be http:// or https://, got " + u.protocol);
|
|
40
|
-
}
|
|
41
|
-
return u;
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
function _parseNoProxy(spec) {
|
|
@@ -97,14 +108,22 @@ function _matchCidr(cidr, host) {
|
|
|
97
108
|
var network = cidr.slice(0, slash);
|
|
98
109
|
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
99
110
|
if (net.isIP(host) !== 4 || net.isIP(network) !== 4) return false;
|
|
100
|
-
if (!isFinite(prefix) || prefix < 0 || prefix >
|
|
111
|
+
if (!isFinite(prefix) || prefix < 0 || prefix > IPV4_PREFIX_MAX_BITS) return false;
|
|
101
112
|
if (prefix === 0) return true;
|
|
102
|
-
var mask = (0xffffffff << (
|
|
113
|
+
var mask = (0xffffffff << (IPV4_PREFIX_MAX_BITS - prefix)) >>> 0;
|
|
103
114
|
return (_ipv4ToInt(host) & mask) === (_ipv4ToInt(network) & mask);
|
|
104
115
|
}
|
|
105
116
|
|
|
117
|
+
function _parseTarget(targetUrl) {
|
|
118
|
+
if (targetUrl instanceof URL) return targetUrl;
|
|
119
|
+
return safeUrl.parse(String(targetUrl), {
|
|
120
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
121
|
+
errorClass: ProxyError,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
function shouldProxy(targetUrl) {
|
|
107
|
-
var u =
|
|
126
|
+
var u = _parseTarget(targetUrl);
|
|
108
127
|
var host = u.hostname;
|
|
109
128
|
var rules = STATE.noProxy;
|
|
110
129
|
for (var i = 0; i < rules.length; i++) {
|
|
@@ -121,7 +140,7 @@ function shouldProxy(targetUrl) {
|
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
function _proxyForUrl(targetUrl) {
|
|
124
|
-
var u =
|
|
143
|
+
var u = _parseTarget(targetUrl);
|
|
125
144
|
if (!shouldProxy(u)) return null;
|
|
126
145
|
return u.protocol === "https:" ? STATE.https : STATE.http;
|
|
127
146
|
}
|
|
@@ -134,7 +153,7 @@ function _proxyAuthHeader(proxyUrl) {
|
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
137
|
-
var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ?
|
|
156
|
+
var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
|
|
138
157
|
var proxySocket = proxyUrl.protocol === "https:"
|
|
139
158
|
? tls.connect({
|
|
140
159
|
host: proxyUrl.hostname,
|
|
@@ -167,7 +186,7 @@ function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
|
167
186
|
var match = /HTTP\/1\.[01] (\d{3})/.exec(status);
|
|
168
187
|
if (!match || parseInt(match[1], 10) !== 200) {
|
|
169
188
|
done(new ProxyError("proxy/connect-failed", "proxy CONNECT to " + targetHost + ":" + targetPort + " failed: " + status));
|
|
170
|
-
try { proxySocket.destroy(); } catch (_e) {}
|
|
189
|
+
try { proxySocket.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
171
190
|
return;
|
|
172
191
|
}
|
|
173
192
|
done(null, proxySocket);
|
|
@@ -179,7 +198,7 @@ function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
|
179
198
|
function agentFor(targetUrl) {
|
|
180
199
|
var proxy = _proxyForUrl(targetUrl);
|
|
181
200
|
if (!proxy) return null;
|
|
182
|
-
var u =
|
|
201
|
+
var u = _parseTarget(targetUrl);
|
|
183
202
|
var key = proxy.toString() + "|" + u.protocol;
|
|
184
203
|
if (STATE.agentCache.has(key)) return STATE.agentCache.get(key);
|
|
185
204
|
|
|
@@ -227,7 +246,7 @@ function snapshot() {
|
|
|
227
246
|
}
|
|
228
247
|
|
|
229
248
|
function _emitObs(name, fields) {
|
|
230
|
-
try { observability().emit(name, fields || {}); } catch (_e) {}
|
|
249
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
function _resetForTest() {
|
package/lib/network-tls.js
CHANGED
|
@@ -5,6 +5,8 @@ var fs = require("node:fs");
|
|
|
5
5
|
var path = require("node:path");
|
|
6
6
|
var nodeCrypto = require("node:crypto");
|
|
7
7
|
|
|
8
|
+
var C = require("./constants");
|
|
9
|
+
var safeBuffer = require("./safe-buffer");
|
|
8
10
|
var validateOpts = require("./validate-opts");
|
|
9
11
|
var lazyRequire = require("./lazy-require");
|
|
10
12
|
var { defineClass } = require("./framework-error");
|
|
@@ -66,8 +68,8 @@ function _certMetadata(pem) {
|
|
|
66
68
|
|
|
67
69
|
function _isPathLike(s) {
|
|
68
70
|
if (s.indexOf("-----BEGIN") !== -1) return false;
|
|
69
|
-
if (s.length >
|
|
70
|
-
if (
|
|
71
|
+
if (s.length > C.BYTES.kib(1)) return false;
|
|
72
|
+
if (safeBuffer.hasCrlf(s)) return false;
|
|
71
73
|
return true;
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -159,7 +161,7 @@ function _emitAuditRemove(metaList, reason) {
|
|
|
159
161
|
reason: reason || "operator",
|
|
160
162
|
},
|
|
161
163
|
});
|
|
162
|
-
} catch (_e) {}
|
|
164
|
+
} catch (_e) { /* audit best-effort — never break the caller */ }
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
|
|
@@ -292,12 +294,12 @@ function _emitAuditAdd(metaList, opts) {
|
|
|
292
294
|
label: opts.label || null,
|
|
293
295
|
},
|
|
294
296
|
});
|
|
295
|
-
} catch (_e) {}
|
|
297
|
+
} catch (_e) { /* audit best-effort — never break the caller */ }
|
|
296
298
|
}
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
function _emitObs(name, fields) {
|
|
300
|
-
try { observability().emit(name, fields || {}); } catch (_e) {}
|
|
302
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
function _resetForTest() {
|
package/lib/network.js
CHANGED
|
@@ -77,7 +77,7 @@ function applyToSocket(socket) {
|
|
|
77
77
|
if (typeof socket.setKeepAlive === "function") {
|
|
78
78
|
socket.setKeepAlive(SOCKET_DEFAULTS.keepAlive, SOCKET_DEFAULTS.keepAliveInitialDelayMs || 0);
|
|
79
79
|
}
|
|
80
|
-
} catch (_e) {}
|
|
80
|
+
} catch (_e) { /* best-effort socket-tuning — apply or skip */ }
|
|
81
81
|
return socket;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -172,7 +172,7 @@ function bootFromEnv(opts) {
|
|
|
172
172
|
outcome: "success",
|
|
173
173
|
metadata: applied,
|
|
174
174
|
});
|
|
175
|
-
} catch (_e) {}
|
|
175
|
+
} catch (_e) { /* audit best-effort — never break boot */ }
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
_emitObs("network.boot.from_env", { source: "env" });
|
|
@@ -197,7 +197,7 @@ function snapshot() {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
function _emitObs(name, fields) {
|
|
200
|
-
try { observability().emit(name, fields || {}); } catch (_e) {}
|
|
200
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
function _resetForTest() {
|
package/lib/notify.js
CHANGED
|
@@ -127,9 +127,7 @@ function _validateChannelEntry(name, entry) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function _validateCreateOpts(opts) {
|
|
130
|
-
|
|
131
|
-
throw _err("BAD_OPT", "notify.create: opts must be an object");
|
|
132
|
-
}
|
|
130
|
+
validateOpts.requireObject(opts, "notify.create", NotifyError);
|
|
133
131
|
if (!opts.channels || typeof opts.channels !== "object") {
|
|
134
132
|
throw _err("BAD_OPT", "notify.create: channels must be an object mapping channel names to transports");
|
|
135
133
|
}
|
|
@@ -137,17 +135,9 @@ function _validateCreateOpts(opts) {
|
|
|
137
135
|
if (keys.length === 0) {
|
|
138
136
|
throw _err("BAD_OPT", "notify.create: channels object must have at least one channel");
|
|
139
137
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (opts.auditSuccess !== undefined && typeof opts.auditSuccess !== "boolean") {
|
|
146
|
-
throw _err("BAD_OPT", "notify.create: auditSuccess must be a boolean");
|
|
147
|
-
}
|
|
148
|
-
if (opts.auditFailures !== undefined && typeof opts.auditFailures !== "boolean") {
|
|
149
|
-
throw _err("BAD_OPT", "notify.create: auditFailures must be a boolean");
|
|
150
|
-
}
|
|
138
|
+
validateOpts.auditShape(opts.audit, "notify.create", NotifyError);
|
|
139
|
+
validateOpts.optionalBoolean(opts.auditSuccess, "notify.create: auditSuccess", NotifyError);
|
|
140
|
+
validateOpts.optionalBoolean(opts.auditFailures, "notify.create: auditFailures", NotifyError);
|
|
151
141
|
if (opts.redact !== undefined && opts.redact !== null && typeof opts.redact !== "function") {
|
|
152
142
|
throw _err("BAD_OPT", "notify.create: redact must be a function returning a redacted message");
|
|
153
143
|
}
|
|
@@ -169,9 +159,7 @@ function _validateCreateOpts(opts) {
|
|
|
169
159
|
throw _err("BAD_OPT", "notify.create: queue must be a b.queue-shaped handle (enqueue fn)");
|
|
170
160
|
}
|
|
171
161
|
}
|
|
172
|
-
|
|
173
|
-
throw _err("BAD_OPT", "notify.create: clock must be a function");
|
|
174
|
-
}
|
|
162
|
+
validateOpts.optionalFunction(opts.clock, "notify.create: clock", NotifyError);
|
|
175
163
|
}
|
|
176
164
|
|
|
177
165
|
// ---- Built-in transports ----
|
|
@@ -183,9 +171,7 @@ function httpJson(opts) {
|
|
|
183
171
|
if (!opts || typeof opts !== "object") {
|
|
184
172
|
throw _err("BAD_OPT", "notify.transports.httpJson: opts must be { url, ... }");
|
|
185
173
|
}
|
|
186
|
-
|
|
187
|
-
throw _err("BAD_OPT", "notify.transports.httpJson: url is required");
|
|
188
|
-
}
|
|
174
|
+
validateOpts.requireNonEmptyString(opts.url, "notify.transports.httpJson: url", NotifyError, "BAD_OPT");
|
|
189
175
|
var allowedProtocols = opts.allowHttp ? safeUrl.ALLOW_HTTP_ALL : safeUrl.ALLOW_HTTP_TLS;
|
|
190
176
|
var allowInternal = opts.allowInternal != null ? opts.allowInternal : null;
|
|
191
177
|
// Validate URL at create time so bad URLs surface at boot, not at first send.
|
|
@@ -270,18 +256,14 @@ function httpJson(opts) {
|
|
|
270
256
|
// log — fire-and-forget developer logger. Never throws; audit + obs still emit.
|
|
271
257
|
function logTransport(opts) {
|
|
272
258
|
opts = opts || {};
|
|
259
|
+
// bootLog.boot() returns a callable with .info / .warn / .error
|
|
260
|
+
// attached; that shape satisfies the operator-supplied opts.logger
|
|
261
|
+
// contract directly. No fallback wrapper needed.
|
|
273
262
|
var logger;
|
|
274
263
|
if (opts.logger && typeof opts.logger.info === "function") {
|
|
275
264
|
logger = opts.logger;
|
|
276
265
|
} else {
|
|
277
266
|
logger = bootLog.boot("notify.log");
|
|
278
|
-
// boot returns a fn-with-fields shape; tolerate both.
|
|
279
|
-
if (typeof logger === "function") {
|
|
280
|
-
logger = { info: logger, error: function (m) { try { console.error(m); } catch (_e) {} } };
|
|
281
|
-
}
|
|
282
|
-
if (typeof logger.error !== "function") {
|
|
283
|
-
logger.error = function (m) { try { console.error(m); } catch (_e) {} };
|
|
284
|
-
}
|
|
285
267
|
}
|
|
286
268
|
return {
|
|
287
269
|
name: opts.name || "log",
|
|
@@ -321,16 +303,16 @@ function create(opts) {
|
|
|
321
303
|
"queue", "clock",
|
|
322
304
|
], "notify");
|
|
323
305
|
_validateCreateOpts(opts);
|
|
306
|
+
var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
|
|
324
307
|
|
|
325
|
-
var auditSuccess =
|
|
326
|
-
var auditFailures =
|
|
308
|
+
var auditSuccess = cfg.auditSuccess;
|
|
309
|
+
var auditFailures = cfg.auditFailures;
|
|
327
310
|
var audit = opts.audit || null;
|
|
328
311
|
var redactFn = (typeof opts.redact === "function")
|
|
329
312
|
? opts.redact
|
|
330
313
|
// Default: b.redact.redact — the framework's PII detector chain.
|
|
331
314
|
: function (m) { return redactModule().redact(m); };
|
|
332
|
-
var defaultTimeoutMs =
|
|
333
|
-
? DEFAULTS.defaultTimeoutMs : opts.defaultTimeoutMs;
|
|
315
|
+
var defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
334
316
|
var defaultRetry = opts.defaultRetry || null;
|
|
335
317
|
var defaultBreaker = opts.defaultBreaker || null;
|
|
336
318
|
var operatorQueue = opts.queue || null;
|
|
@@ -362,11 +344,7 @@ function create(opts) {
|
|
|
362
344
|
catch (_e) { /* drop-silent — observability sink must not crash send() */ }
|
|
363
345
|
}
|
|
364
346
|
|
|
365
|
-
|
|
366
|
-
if (!audit) return;
|
|
367
|
-
try { audit.safeEmit(Object.assign({ action: action }, info || {})); }
|
|
368
|
-
catch (_e) { /* audit best-effort */ }
|
|
369
|
-
}
|
|
347
|
+
var _emitAudit = validateOpts.makeAuditEmitter(audit);
|
|
370
348
|
|
|
371
349
|
function _actor(callerOpts) {
|
|
372
350
|
return requestHelpers.resolveActorWithOverride(callerOpts);
|
package/lib/ntp-check.js
CHANGED
|
@@ -34,7 +34,8 @@ var NTP_TO_UNIX_OFFSET_SECONDS = 2208988800;
|
|
|
34
34
|
|
|
35
35
|
var DEFAULT_SERVERS = ["pool.ntp.org", "time.cloudflare.com"];
|
|
36
36
|
var DEFAULT_PORT = 123;
|
|
37
|
-
var DEFAULT_TIMEOUT_MS =
|
|
37
|
+
var DEFAULT_TIMEOUT_MS = C.TIME.seconds(3);
|
|
38
|
+
var NTP_PACKET_BYTES = C.BYTES.bytes(48); // RFC 5905 §7.3
|
|
38
39
|
|
|
39
40
|
var DEFAULT_DRIFT_WARN_MS = C.TIME.minutes(5);
|
|
40
41
|
var DEFAULT_DRIFT_FATAL_MS = C.TIME.hours(1);
|
|
@@ -105,9 +106,9 @@ function querySingle(server, opts) {
|
|
|
105
106
|
}, timeoutMs);
|
|
106
107
|
timer.unref();
|
|
107
108
|
|
|
108
|
-
// SNTPv4 client request:
|
|
109
|
+
// SNTPv4 client request: NTP_PACKET_BYTES buffer, byte 0 = 0b00_100_011 = 0x23
|
|
109
110
|
// LI=0 (no warning), VN=4, Mode=3 (client). Other bytes zero.
|
|
110
|
-
var req = Buffer.alloc(
|
|
111
|
+
var req = Buffer.alloc(NTP_PACKET_BYTES);
|
|
111
112
|
req[0] = 0x23;
|
|
112
113
|
var sendTimeMs = Date.now();
|
|
113
114
|
|
|
@@ -119,15 +120,15 @@ function querySingle(server, opts) {
|
|
|
119
120
|
socket.on("message", function (msg) {
|
|
120
121
|
clearTimeout(timer);
|
|
121
122
|
var receiveTimeMs = Date.now();
|
|
122
|
-
if (!Buffer.isBuffer(msg) || msg.length <
|
|
123
|
+
if (!Buffer.isBuffer(msg) || msg.length < NTP_PACKET_BYTES) {
|
|
123
124
|
return done({ code: "ntp/bad-reply", message: "reply too short (" + (msg && msg.length) + " bytes)" });
|
|
124
125
|
}
|
|
125
126
|
// Bytes 40-47 = Transmit Timestamp (NTP epoch seconds.fraction)
|
|
126
127
|
var ntpSeconds = msg.readUInt32BE(40);
|
|
127
128
|
var ntpFraction = msg.readUInt32BE(44);
|
|
128
129
|
var serverUnixSeconds = ntpSeconds - NTP_TO_UNIX_OFFSET_SECONDS;
|
|
129
|
-
var fracMs = Math.round(ntpFraction / 0x100000000
|
|
130
|
-
var serverTimeMs = serverUnixSeconds
|
|
130
|
+
var fracMs = Math.round(C.TIME.seconds(ntpFraction / 0x100000000));
|
|
131
|
+
var serverTimeMs = C.TIME.seconds(serverUnixSeconds) + fracMs;
|
|
131
132
|
|
|
132
133
|
// Round-trip-corrected drift: assume the server's reply transmit
|
|
133
134
|
// timestamp is approximately at the midpoint of our send/receive.
|