@blamejs/blamejs-shop 0.4.53 → 0.4.55
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 +4 -0
- package/lib/admin.js +255 -1
- package/lib/asset-manifest.json +3 -3
- package/lib/storefront.js +135 -0
- package/lib/vendor/MANIFEST.json +41 -35
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/SECURITY.md +1 -0
- package/lib/vendor/blamejs/api-snapshot.json +10 -2
- package/lib/vendor/blamejs/examples/wiki/lib/html-entities.js +24 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +7 -5
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +9 -1
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +2 -8
- package/lib/vendor/blamejs/lib/acme.js +7 -11
- package/lib/vendor/blamejs/lib/client-hints.js +3 -1
- package/lib/vendor/blamejs/lib/cluster.js +4 -2
- package/lib/vendor/blamejs/lib/guard-filename.js +6 -2
- package/lib/vendor/blamejs/lib/http-client-cache.js +3 -1
- package/lib/vendor/blamejs/lib/http-message-signature.js +25 -8
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +12 -1
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +6 -0
- package/lib/vendor/blamejs/lib/log.js +24 -2
- package/lib/vendor/blamejs/lib/mail.js +5 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +48 -6
- package/lib/vendor/blamejs/lib/network-dns.js +22 -26
- package/lib/vendor/blamejs/lib/network-heartbeat.js +3 -3
- package/lib/vendor/blamejs/lib/network-proxy.js +3 -7
- package/lib/vendor/blamejs/lib/network-tls.js +34 -13
- package/lib/vendor/blamejs/lib/network.js +2 -6
- package/lib/vendor/blamejs/lib/notify.js +7 -12
- package/lib/vendor/blamejs/lib/seeders.js +5 -10
- package/lib/vendor/blamejs/lib/structured-fields.js +38 -1
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.15.12.json +47 -0
- package/lib/vendor/blamejs/test/00-primitives.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-error-redaction.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +18 -8
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +11 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +14 -0
- package/package.json +1 -1
|
@@ -1089,8 +1089,10 @@ function discoveryHandler() {
|
|
|
1089
1089
|
body = { leader: null, self: selfInfo };
|
|
1090
1090
|
status = 503;
|
|
1091
1091
|
}
|
|
1092
|
-
} catch (
|
|
1093
|
-
|
|
1092
|
+
} catch (_e) {
|
|
1093
|
+
// Generic client-facing reason — the caught error's message (a DB error
|
|
1094
|
+
// detail / DSN / host:port) is not echoed to the client (CWE-209).
|
|
1095
|
+
body = { leader: null, self: selfInfo, error: "leader lookup unavailable" };
|
|
1094
1096
|
status = 503;
|
|
1095
1097
|
}
|
|
1096
1098
|
var json = JSON.stringify(body);
|
|
@@ -80,7 +80,10 @@ var _err = GuardFilenameError.factory;
|
|
|
80
80
|
// Windows: < > : " / \ | ? *
|
|
81
81
|
// Unix: /
|
|
82
82
|
// Both: null and C0 controls (handled separately via codepoint-class)
|
|
83
|
-
|
|
83
|
+
// Global so reservedCharPolicy:"strip" replaces EVERY reserved char, not just
|
|
84
|
+
// the first (CodeQL js/incomplete-multi-character-sanitization). Only consumer
|
|
85
|
+
// is the .replace() in _sanitize — no stateful .test()/.exec() lastIndex hazard.
|
|
86
|
+
var RESERVED_CHARS_RE = /[<>:"/\\|?*]/g;
|
|
84
87
|
|
|
85
88
|
// Windows reserved device names (case-insensitive). Match either the
|
|
86
89
|
// bare name or `<name>.<anything>`.
|
|
@@ -586,8 +589,9 @@ function _sanitize(input, opts) {
|
|
|
586
589
|
|
|
587
590
|
// Strip reserved chars when policy says strip.
|
|
588
591
|
if (opts.reservedCharPolicy === "strip") {
|
|
592
|
+
// Single global strip — RESERVED_CHARS_RE (now /g) covers the whole
|
|
593
|
+
// reserved class INCLUDING path separators, so no second pass is needed.
|
|
589
594
|
name = name.replace(RESERVED_CHARS_RE, "_"); // allow:dynamic-regex — RESERVED_CHARS_RE is a compile-time literal
|
|
590
|
-
name = name.replace(/[<>:"|?*]/g, "_");
|
|
591
595
|
} else if (opts.reservedCharPolicy === "reject") {
|
|
592
596
|
if (/[<>:"|?*]/.test(name)) {
|
|
593
597
|
throw _err("filename.reserved-char", "filename contains reserved character");
|
|
@@ -107,7 +107,9 @@ function _parseCacheControl(value) {
|
|
|
107
107
|
else { k = p.slice(0, eq).trim(); v = p.slice(eq + 1).trim(); }
|
|
108
108
|
// Strip surrounding quotes from value.
|
|
109
109
|
if (v.length >= 2 && v.charAt(0) === '"' && v.charAt(v.length - 1) === '"') {
|
|
110
|
-
|
|
110
|
+
// Single-pass RFC 8941 unescape (chained .replace() mis-decodes
|
|
111
|
+
// an escaped backslash adjacent to another escape).
|
|
112
|
+
v = structuredFields.unescapeSfStringBody(v.slice(1, v.length - 1));
|
|
111
113
|
}
|
|
112
114
|
out[k.toLowerCase()] = v;
|
|
113
115
|
}
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
var nodeCrypto = require("node:crypto");
|
|
63
|
+
var bCrypto = require("./crypto");
|
|
63
64
|
var safeUrl = require("./safe-url");
|
|
64
65
|
var safeBuffer = require("./safe-buffer");
|
|
65
66
|
var C = require("./constants");
|
|
@@ -406,7 +407,9 @@ function _parseSignatureInput(headerValue) {
|
|
|
406
407
|
throw _err("BAD_HEADER",
|
|
407
408
|
"httpSig: Signature-Input: unterminated quoted token");
|
|
408
409
|
}
|
|
409
|
-
|
|
410
|
+
// Single-pass RFC 8941 §3.3.3 unescape — NOT two chained .replace() passes,
|
|
411
|
+
// which mis-decode an escaped backslash adjacent to another escape.
|
|
412
|
+
var bareName = structuredFields.unescapeSfStringBody(coveredRaw.slice(qStart, qEnd));
|
|
410
413
|
i2 = qEnd + 1;
|
|
411
414
|
// Optional ;param=value;param=... suffix immediately following.
|
|
412
415
|
var suffixStart = i2;
|
|
@@ -525,13 +528,27 @@ function verify(msg, opts) {
|
|
|
525
528
|
if (!presented) {
|
|
526
529
|
return { valid: false, reason: "content-digest-header-missing" };
|
|
527
530
|
}
|
|
528
|
-
|
|
529
|
-
// RFC 9530
|
|
530
|
-
// sha-256
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
|
|
531
|
+
// contentDigest() returns the canonical structured-field form
|
|
532
|
+
// `sha3-512=:<base64>:`. RFC 9530 permits a multi-member header
|
|
533
|
+
// (e.g. `sha-256=:...:, sha3-512=:...:`); split on top-level commas and
|
|
534
|
+
// match the sha3-512 member EXACTLY, in constant time, rather than by an
|
|
535
|
+
// unanchored substring scan that could spuriously match the digest text
|
|
536
|
+
// buried inside another member's value or parameters. Peer-supplied
|
|
537
|
+
// sha-512 / sha-256 identifiers stay the operator's responsibility.
|
|
538
|
+
var expectedDigest = contentDigest(m.body); // "sha3-512=:<b64>:"
|
|
539
|
+
var matchedDigest = false;
|
|
540
|
+
var digestMembers = structuredFields.splitTopLevel(presented, ",");
|
|
541
|
+
for (var di = 0; di < digestMembers.length; di++) {
|
|
542
|
+
var member = digestMembers[di].trim();
|
|
543
|
+
var deq = member.indexOf("=");
|
|
544
|
+
if (deq < 1) continue;
|
|
545
|
+
if (member.slice(0, deq).trim().toLowerCase() !== "sha3-512") continue;
|
|
546
|
+
var memberCanonical = "sha3-512=" + member.slice(deq + 1).trim();
|
|
547
|
+
// crypto.timingSafeEqual is the length-tolerant constant-time wrapper
|
|
548
|
+
// (returns false for unequal lengths without leaking via a length branch).
|
|
549
|
+
if (bCrypto.timingSafeEqual(memberCanonical, expectedDigest)) { matchedDigest = true; break; }
|
|
550
|
+
}
|
|
551
|
+
if (!matchedDigest) {
|
|
535
552
|
return { valid: false, reason: "content-digest-mismatch" };
|
|
536
553
|
}
|
|
537
554
|
}
|
|
@@ -38,6 +38,9 @@ var lazyRequire = require("./lazy-require");
|
|
|
38
38
|
// scrub attribute values through the telemetry redactor before they cross the
|
|
39
39
|
// OTLP egress boundary (CWE-532).
|
|
40
40
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
41
|
+
// Lazy — network-tls is widely required; audit an insecure (cert-validation-
|
|
42
|
+
// disabled) outbound TLS session at honor time, same surface as connectWithEch.
|
|
43
|
+
var networkTls = lazyRequire(function () { return require("./network-tls"); });
|
|
41
44
|
|
|
42
45
|
var _err = LogStreamError.factory;
|
|
43
46
|
var _log = boot("log-stream-otlp-grpc");
|
|
@@ -215,7 +218,14 @@ function _makeClient(cfg) {
|
|
|
215
218
|
var sessionOpts = {};
|
|
216
219
|
if (cfg.ca) sessionOpts.ca = cfg.ca;
|
|
217
220
|
if (cfg.servername) sessionOpts.servername = cfg.servername;
|
|
218
|
-
if (cfg.allowInsecure
|
|
221
|
+
if (cfg.allowInsecure && url.protocol === "https:") {
|
|
222
|
+
// allowInsecure only has meaning on a TLS session. For an h2c endpoint
|
|
223
|
+
// (http://, cleartext HTTP/2) there is no certificate to validate and
|
|
224
|
+
// nothing to skip, so neither rejectUnauthorized nor the insecure-TLS
|
|
225
|
+
// audit applies — emitting it there would be a false security event.
|
|
226
|
+
sessionOpts.rejectUnauthorized = false;
|
|
227
|
+
networkTls().auditInsecureTls({ host: authority, source: "log-stream.otlp-grpc" });
|
|
228
|
+
}
|
|
219
229
|
var session = http2.connect(authority, sessionOpts);
|
|
220
230
|
session.on("error", function () { /* surfaced through request err */ });
|
|
221
231
|
if (typeof session.unref === "function") session.unref();
|
|
@@ -409,6 +419,7 @@ module.exports = {
|
|
|
409
419
|
create: create,
|
|
410
420
|
// Exposed for layer-0 tests that verify the wire encoding without
|
|
411
421
|
// standing up an HTTP/2 server.
|
|
422
|
+
_makeClient: _makeClient,
|
|
412
423
|
_encodeAnyValue: _encodeAnyValue,
|
|
413
424
|
_encodeKeyValue: _encodeKeyValue,
|
|
414
425
|
_encodeLogRecord: _encodeLogRecord,
|
|
@@ -37,6 +37,9 @@ var safeAsync = require("./safe-async");
|
|
|
37
37
|
var safeBuffer = require("./safe-buffer");
|
|
38
38
|
var safeUrl = require("./safe-url");
|
|
39
39
|
var { LogStreamError } = require("./framework-error");
|
|
40
|
+
var lazyRequire = require("./lazy-require");
|
|
41
|
+
// Lazy — audit a cert-validation-disabled syslog/TLS session at honor time.
|
|
42
|
+
var networkTls = lazyRequire(function () { return require("./network-tls"); });
|
|
40
43
|
|
|
41
44
|
var _err = LogStreamError.factory;
|
|
42
45
|
var log = boot("log-stream-syslog");
|
|
@@ -223,6 +226,9 @@ function create(config) {
|
|
|
223
226
|
});
|
|
224
227
|
if (cfg.ca) tlsOpts.ca = cfg.ca;
|
|
225
228
|
if (cfg.servername) tlsOpts.servername = cfg.servername;
|
|
229
|
+
if (cfg.rejectUnauthorized === false) {
|
|
230
|
+
networkTls().auditInsecureTls({ host: cfg.host, port: cfg.port, source: "log-stream.syslog" });
|
|
231
|
+
}
|
|
226
232
|
sock = nodeTls.connect(tlsOpts, onConnect);
|
|
227
233
|
} else {
|
|
228
234
|
sock = net.connect(connectOpts, onConnect);
|
|
@@ -464,7 +464,11 @@ function boot(name) {
|
|
|
464
464
|
var stream = (LEVELS[levelName] >= LEVELS.warn) ? process.stderr : process.stdout;
|
|
465
465
|
var isTty = !!(stream && stream.isTTY);
|
|
466
466
|
if (isTty) {
|
|
467
|
-
|
|
467
|
+
// Raw human-readable line — escape BOTH the C0/newline (line-forging)
|
|
468
|
+
// and bidi (re-ordering) control classes the create() path neutralizes,
|
|
469
|
+
// so a hostile boot message can't inject lines or re-order the visible
|
|
470
|
+
// line on a TTY / syslog reader (CWE-117 / Trojan-Source CVE-2021-42574).
|
|
471
|
+
sink(_escapeBidiControls(_escapeC0Controls(prefix + String(msg))));
|
|
468
472
|
return;
|
|
469
473
|
}
|
|
470
474
|
var entry = {
|
|
@@ -474,7 +478,9 @@ function boot(name) {
|
|
|
474
478
|
component: name,
|
|
475
479
|
boot: true,
|
|
476
480
|
};
|
|
477
|
-
|
|
481
|
+
// JSON.stringify already escapes C0/newlines; bidi/format controls survive
|
|
482
|
+
// raw into a piped aggregator, so apply the same bidi escape create() uses.
|
|
483
|
+
sink(_escapeBidiControls(JSON.stringify(entry)));
|
|
478
484
|
}
|
|
479
485
|
|
|
480
486
|
function debug(msg, fields) {
|
|
@@ -560,6 +566,22 @@ function _escapeBidiControls(s) {
|
|
|
560
566
|
});
|
|
561
567
|
}
|
|
562
568
|
|
|
569
|
+
// C0 control chars (incl. CR / LF / TAB) + DEL — escaped to `\uXXXX` so a
|
|
570
|
+
// hostile message can't forge extra log lines on a raw (non-JSON) TTY sink
|
|
571
|
+
// (log-injection, CWE-117). The create() path gets this for free from
|
|
572
|
+
// JSON.stringify; the boot() TTY branch writes raw text and needs it
|
|
573
|
+
// explicitly. Pairs with _escapeBidiControls (which only covers the bidi set).
|
|
574
|
+
var _C0_CONTROL_RE = /[\u0000-\u001f\u007f]/g; // eslint-disable-line no-control-regex -- the C0/DEL set is what we escape
|
|
575
|
+
|
|
576
|
+
function _escapeC0Controls(s) {
|
|
577
|
+
if (typeof s !== "string" || s.length === 0) return s;
|
|
578
|
+
return s.replace(_C0_CONTROL_RE, function (ch) {
|
|
579
|
+
var code = ch.charCodeAt(0).toString(16);
|
|
580
|
+
while (code.length < 4) code = "0" + code;
|
|
581
|
+
return "\\u" + code;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
563
585
|
// runs before safeEnv on the boot path; safeEnv requires log, so log
|
|
564
586
|
// can't go through safeEnv to read its own level.
|
|
565
587
|
function _bootMinLevel() {
|
|
@@ -76,6 +76,8 @@ var networkDns = lazyRequire(function () { return require("./network-dns"); });
|
|
|
76
76
|
var nodeUrl = require("node:url");
|
|
77
77
|
var numericBounds = require("./numeric-bounds");
|
|
78
78
|
var nodeTls = lazyRequire(function () { return require("node:tls"); });
|
|
79
|
+
// Lazy — audit a cert-validation-disabled SMTP/TLS session at honor time.
|
|
80
|
+
var networkTls = lazyRequire(function () { return require("./network-tls"); });
|
|
79
81
|
var safeJson = require("./safe-json");
|
|
80
82
|
var safeSchema = require("./safe-schema");
|
|
81
83
|
var validateOpts = require("./validate-opts");
|
|
@@ -778,6 +780,9 @@ function smtpTransport(opts) {
|
|
|
778
780
|
var port = opts.port || 587;
|
|
779
781
|
var useImplicitTLS = port === 465 || opts.implicitTls === true;
|
|
780
782
|
var rejectUnauthorized = opts.rejectUnauthorized !== false;
|
|
783
|
+
if (rejectUnauthorized === false) {
|
|
784
|
+
networkTls().auditInsecureTls({ host: opts.host, port: port, source: "mail.smtp" });
|
|
785
|
+
}
|
|
781
786
|
var ehloName = opts.ehloName || "blamejs";
|
|
782
787
|
// GHSA-c7w3-x93f-qmm8 / GHSA-vvjj-xcjg-gr5g (nodemailer CRLF-injection
|
|
783
788
|
// class) — any string concatenated into an outbound SMTP wire command
|
|
@@ -375,6 +375,19 @@ function _detectSmuggling(req) {
|
|
|
375
375
|
return null;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
+
// Generic, operator-safe status phrase for a client error body — used when the
|
|
379
|
+
// thrown error is NOT a framework-classified BodyParserError (or is a 5xx), so
|
|
380
|
+
// an internal exception's message (fs errno + tmp path, a parse hook's thrown
|
|
381
|
+
// detail) is never echoed to the client (CWE-209 / CodeQL js/stack-trace-exposure).
|
|
382
|
+
var _GENERIC_REASON = {};
|
|
383
|
+
_GENERIC_REASON[HTTP_STATUS.BAD_REQUEST] = "Bad Request";
|
|
384
|
+
_GENERIC_REASON[HTTP_STATUS.PAYLOAD_TOO_LARGE] = "Payload Too Large";
|
|
385
|
+
_GENERIC_REASON[HTTP_STATUS.UNSUPPORTED_MEDIA_TYPE] = "Unsupported Media Type";
|
|
386
|
+
_GENERIC_REASON[HTTP_STATUS.INTERNAL_SERVER_ERROR] = "Internal Server Error";
|
|
387
|
+
function _genericReason(status) {
|
|
388
|
+
return _GENERIC_REASON[status] || (status >= 500 ? "Internal Server Error" : "Bad Request");
|
|
389
|
+
}
|
|
390
|
+
|
|
378
391
|
function _writeError(res, status, message, code) {
|
|
379
392
|
if (res.headersSent) return;
|
|
380
393
|
var body = JSON.stringify({ error: message, code: code });
|
|
@@ -469,10 +482,13 @@ async function _parseJson(req, opts) {
|
|
|
469
482
|
}
|
|
470
483
|
if (typeof opts.parseHook === "function") {
|
|
471
484
|
try { parsed = opts.parseHook(parsed); }
|
|
472
|
-
catch (
|
|
485
|
+
catch (_e) {
|
|
473
486
|
throw new BodyParserError(
|
|
474
487
|
"body-parser/json-hook",
|
|
475
|
-
|
|
488
|
+
// Operator parseHook threw — surface a fixed, safe message; the hook's
|
|
489
|
+
// own thrown detail (which may carry secrets) is the operator's to log,
|
|
490
|
+
// never echoed to the client (CWE-209).
|
|
491
|
+
"request body rejected by parse hook",
|
|
476
492
|
true, HTTP_STATUS.BAD_REQUEST
|
|
477
493
|
);
|
|
478
494
|
}
|
|
@@ -1476,8 +1492,32 @@ function create(opts) {
|
|
|
1476
1492
|
}
|
|
1477
1493
|
var status = (e && typeof e.statusCode === "number") ? e.statusCode : HTTP_STATUS.BAD_REQUEST;
|
|
1478
1494
|
var code = (e && typeof e.code === "string") ? e.code : "body-parser/error";
|
|
1479
|
-
|
|
1480
|
-
|
|
1495
|
+
// Only a framework-classified 4xx BodyParserError carries a curated,
|
|
1496
|
+
// operator-safe message (malformed JSON, poisoned key, smuggling). Any
|
|
1497
|
+
// other thrown error — and every 5xx — gets a generic status phrase, so
|
|
1498
|
+
// an internal exception (fs errno + tmp path, a parse hook's thrown
|
|
1499
|
+
// secret) is never echoed to the client (CWE-209). The full diagnostic
|
|
1500
|
+
// stays on the audit chain, redacted by safeEmit.
|
|
1501
|
+
// Object(e) tolerates a non-object throw (throw null, or a string from
|
|
1502
|
+
// an operator parse hook) without a truthiness guard on the caught
|
|
1503
|
+
// binding — the caught value is always defined to CodeQL, so `e && …`
|
|
1504
|
+
// reads as a dead sub-condition; this keeps the null-safety explicit.
|
|
1505
|
+
var eo = Object(e);
|
|
1506
|
+
var clientMessage = (eo.isBodyParserError === true && status < 500 && typeof eo.message === "string")
|
|
1507
|
+
? eo.message
|
|
1508
|
+
: _genericReason(status);
|
|
1509
|
+
try {
|
|
1510
|
+
audit().safeEmit({
|
|
1511
|
+
action: "body-parser.error",
|
|
1512
|
+
outcome: status >= 500 ? "failure" : "denied",
|
|
1513
|
+
metadata: {
|
|
1514
|
+
status: status,
|
|
1515
|
+
code: code,
|
|
1516
|
+
message: (e && e.message) ? String(e.message).slice(0, 256) : "",
|
|
1517
|
+
},
|
|
1518
|
+
});
|
|
1519
|
+
} catch (_e) { /* audit best-effort — never mask the response */ }
|
|
1520
|
+
_writeError(res, status, clientMessage, code);
|
|
1481
1521
|
}
|
|
1482
1522
|
};
|
|
1483
1523
|
}
|
|
@@ -1501,9 +1541,11 @@ async function _parseJsonFromBuf(buf, opts) {
|
|
|
1501
1541
|
}
|
|
1502
1542
|
if (typeof opts.parseHook === "function") {
|
|
1503
1543
|
try { parsed = opts.parseHook(parsed); }
|
|
1504
|
-
catch (
|
|
1544
|
+
catch (_e) {
|
|
1545
|
+
// Operator parseHook threw — fixed safe message; the hook's thrown
|
|
1546
|
+
// detail (possible secrets) is never echoed to the client (CWE-209).
|
|
1505
1547
|
throw new BodyParserError("body-parser/json-hook",
|
|
1506
|
-
"
|
|
1548
|
+
"request body rejected by parse hook", true, HTTP_STATUS.BAD_REQUEST);
|
|
1507
1549
|
}
|
|
1508
1550
|
}
|
|
1509
1551
|
return parsed;
|
|
@@ -149,7 +149,7 @@ function setServers(serverList) {
|
|
|
149
149
|
throw new DnsError("dns/setservers-failed", "dns.setServers failed: " + e.message);
|
|
150
150
|
}
|
|
151
151
|
_clearCache();
|
|
152
|
-
|
|
152
|
+
observability().safeEvent("network.dns.servers.set", 1, { count: serverList.length });
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
function getServers() {
|
|
@@ -169,7 +169,7 @@ function setResultOrder(order) {
|
|
|
169
169
|
try { dns.setDefaultResultOrder(order); } catch (_e) { /* node may not support setter on this version — best-effort */ }
|
|
170
170
|
}
|
|
171
171
|
_clearCache();
|
|
172
|
-
|
|
172
|
+
observability().safeEvent("network.dns.result_order.set", 1, { order: order });
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
function setFamily(fam) {
|
|
@@ -215,7 +215,7 @@ function useSystemResolver() {
|
|
|
215
215
|
STATE.systemResolver = true;
|
|
216
216
|
_resetDotPool();
|
|
217
217
|
_clearCache();
|
|
218
|
-
|
|
218
|
+
observability().safeEvent("network.dns.system_resolver.set", 1, {});
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
function useDnsOverHttps(opts) {
|
|
@@ -246,7 +246,7 @@ function useDnsOverHttps(opts) {
|
|
|
246
246
|
}
|
|
247
247
|
STATE.doh = { url: url, method: method, ca: opts.ca || null };
|
|
248
248
|
_clearCache();
|
|
249
|
-
|
|
249
|
+
observability().safeEvent("network.dns.doh.set", 1, { url: url, method: method || "auto" });
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
function useDnsOverTls(opts) {
|
|
@@ -267,7 +267,7 @@ function useDnsOverTls(opts) {
|
|
|
267
267
|
};
|
|
268
268
|
_resetDotPool();
|
|
269
269
|
_clearCache();
|
|
270
|
-
|
|
270
|
+
observability().safeEvent("network.dns.dot.set", 1, { host: STATE.dot.host, port: STATE.dot.port });
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
function _withTimeout(promise, ms, host) {
|
|
@@ -1178,13 +1178,13 @@ async function _querySvcbLike(host, qtype, opts) {
|
|
|
1178
1178
|
throw new DnsError("dns/bad-transport",
|
|
1179
1179
|
"dns.querySvcb: transport must be 'doh' | 'dot' | 'system' | undefined");
|
|
1180
1180
|
}
|
|
1181
|
-
|
|
1181
|
+
observability().safeEvent("network.dns.svcb.requested", 1, { qtype: qtype, transport: opts.transport || "auto" });
|
|
1182
1182
|
var startMs = _now();
|
|
1183
1183
|
var reply;
|
|
1184
1184
|
try {
|
|
1185
1185
|
reply = await _rawQuery(host, qtype, opts.transport);
|
|
1186
1186
|
} catch (e) {
|
|
1187
|
-
|
|
1187
|
+
observability().safeEvent("network.dns.svcb.failure", 1, {
|
|
1188
1188
|
latencyMs: _now() - startMs,
|
|
1189
1189
|
code: e.code || "unknown",
|
|
1190
1190
|
});
|
|
@@ -1198,7 +1198,7 @@ async function _querySvcbLike(host, qtype, opts) {
|
|
|
1198
1198
|
records.push(_parseSvcbRdata(decoded.msg, ans.rdataOff, ans.rdlen));
|
|
1199
1199
|
}
|
|
1200
1200
|
records.sort(function (a, b) { return a.priority - b.priority; });
|
|
1201
|
-
|
|
1201
|
+
observability().safeEvent("network.dns.svcb.success", 1, {
|
|
1202
1202
|
latencyMs: _now() - startMs,
|
|
1203
1203
|
count: records.length,
|
|
1204
1204
|
qtype: qtype,
|
|
@@ -1322,7 +1322,7 @@ async function discoverEncrypted(opts) {
|
|
|
1322
1322
|
try {
|
|
1323
1323
|
records = await _querySvcbLike(name, DNS_QTYPE_SVCB, { transport: transport });
|
|
1324
1324
|
} catch (e) {
|
|
1325
|
-
|
|
1325
|
+
observability().safeEvent("network.dns.ddr.failure", 1, {
|
|
1326
1326
|
latencyMs: _now() - startMs,
|
|
1327
1327
|
code: e.code || "unknown",
|
|
1328
1328
|
});
|
|
@@ -1333,7 +1333,7 @@ async function discoverEncrypted(opts) {
|
|
|
1333
1333
|
throw e;
|
|
1334
1334
|
}
|
|
1335
1335
|
if (records.length === 0) {
|
|
1336
|
-
|
|
1336
|
+
observability().safeEvent("network.dns.ddr.empty", 1, { latencyMs: _now() - startMs });
|
|
1337
1337
|
throw new DnsError("dns/ddr-not-discovered",
|
|
1338
1338
|
"dns.discoverEncrypted: resolver returned empty DDR record set at " + name);
|
|
1339
1339
|
}
|
|
@@ -1364,7 +1364,7 @@ async function discoverEncrypted(opts) {
|
|
|
1364
1364
|
throw new DnsError("dns/ddr-not-discovered",
|
|
1365
1365
|
"dns.discoverEncrypted: DDR records present but none advertised a recognized transport (alpn=dot/h2/h3)");
|
|
1366
1366
|
}
|
|
1367
|
-
|
|
1367
|
+
observability().safeEvent("network.dns.ddr.success", 1, {
|
|
1368
1368
|
latencyMs: _now() - startMs,
|
|
1369
1369
|
count: resolvers.length,
|
|
1370
1370
|
});
|
|
@@ -1454,7 +1454,7 @@ function useDesignatedResolvers(list) {
|
|
|
1454
1454
|
});
|
|
1455
1455
|
}
|
|
1456
1456
|
_designatedResolvers = validated.slice();
|
|
1457
|
-
|
|
1457
|
+
observability().safeEvent("network.dns.dnr.set", 1, {
|
|
1458
1458
|
count: validated.length,
|
|
1459
1459
|
active: j,
|
|
1460
1460
|
transport: v.transport,
|
|
@@ -1462,7 +1462,7 @@ function useDesignatedResolvers(list) {
|
|
|
1462
1462
|
return { active: j, count: validated.length };
|
|
1463
1463
|
} catch (e) {
|
|
1464
1464
|
lastErr = e;
|
|
1465
|
-
|
|
1465
|
+
observability().safeEvent("network.dns.dnr.entry_failed", 1, {
|
|
1466
1466
|
index: j,
|
|
1467
1467
|
transport: v.transport,
|
|
1468
1468
|
code: e.code || "unknown",
|
|
@@ -1511,7 +1511,7 @@ async function lookup(host, opts) {
|
|
|
1511
1511
|
if (cached.error) throw cached.error;
|
|
1512
1512
|
return opts.all ? cached.value : cached.value[0];
|
|
1513
1513
|
}
|
|
1514
|
-
|
|
1514
|
+
observability().safeEvent("network.dns.lookup.requested", 1, { family: cacheKey });
|
|
1515
1515
|
var startMs = _now();
|
|
1516
1516
|
// Resolve secure-DNS default on first use. Idempotent.
|
|
1517
1517
|
_ensureSecureDefault();
|
|
@@ -1546,11 +1546,11 @@ async function lookup(host, opts) {
|
|
|
1546
1546
|
throw new DnsError("dns/no-result", "dns lookup of '" + host + "' returned no addresses");
|
|
1547
1547
|
}
|
|
1548
1548
|
_cachePutPositive(host, cacheKey, normalized);
|
|
1549
|
-
|
|
1549
|
+
observability().safeEvent("network.dns.lookup.success", 1, { latencyMs: _now() - startMs, count: normalized.length });
|
|
1550
1550
|
return opts.all ? normalized : normalized[0];
|
|
1551
1551
|
} catch (e) {
|
|
1552
1552
|
_cachePutNegative(host, cacheKey, e);
|
|
1553
|
-
|
|
1553
|
+
observability().safeEvent("network.dns.lookup.failure", 1, { latencyMs: _now() - startMs, code: e.code || "unknown" });
|
|
1554
1554
|
throw e;
|
|
1555
1555
|
}
|
|
1556
1556
|
}
|
|
@@ -1571,7 +1571,7 @@ async function _resolveProtocol(host, family, opts) {
|
|
|
1571
1571
|
throw new DnsError("dns/bad-transport",
|
|
1572
1572
|
"dns.resolve" + family + ": transport must be 'doh' | 'dot' | 'system' | undefined");
|
|
1573
1573
|
}
|
|
1574
|
-
|
|
1574
|
+
observability().safeEvent("network.dns.resolve.requested", 1, { family: family, transport: opts.transport || "auto" });
|
|
1575
1575
|
var startMs = _now();
|
|
1576
1576
|
try {
|
|
1577
1577
|
var addrs;
|
|
@@ -1597,10 +1597,10 @@ async function _resolveProtocol(host, family, opts) {
|
|
|
1597
1597
|
if (normalized.length === 0) {
|
|
1598
1598
|
throw new DnsError("dns/no-result", "dns.resolve" + family + " of '" + host + "' returned no addresses");
|
|
1599
1599
|
}
|
|
1600
|
-
|
|
1600
|
+
observability().safeEvent("network.dns.resolve.success", 1, { family: family, latencyMs: _now() - startMs, count: normalized.length });
|
|
1601
1601
|
return normalized;
|
|
1602
1602
|
} catch (e) {
|
|
1603
|
-
|
|
1603
|
+
observability().safeEvent("network.dns.resolve.failure", 1, { family: family, latencyMs: _now() - startMs, code: e.code || "unknown" });
|
|
1604
1604
|
if (e instanceof DnsError) throw e;
|
|
1605
1605
|
throw new DnsError("dns/resolve-failed",
|
|
1606
1606
|
"dns.resolve" + family + " of '" + host + "' failed: " + (e.message || String(e)));
|
|
@@ -1643,16 +1643,16 @@ async function reverse(ip) {
|
|
|
1643
1643
|
throw new DnsError("dns/bad-ip",
|
|
1644
1644
|
"dns.reverse: '" + ip + "' is not a valid IPv4 or IPv6 address");
|
|
1645
1645
|
}
|
|
1646
|
-
|
|
1646
|
+
observability().safeEvent("network.dns.reverse.requested", 1, { family: net.isIPv6(ip) ? 6 : 4 });
|
|
1647
1647
|
var startMs = _now();
|
|
1648
1648
|
try {
|
|
1649
1649
|
var ptrs = await _withTimeout(dnsPromises.reverse(ip), STATE.lookupTimeoutMs, ip);
|
|
1650
|
-
|
|
1650
|
+
observability().safeEvent("network.dns.reverse.success", 1, {
|
|
1651
1651
|
latencyMs: _now() - startMs, count: Array.isArray(ptrs) ? ptrs.length : 0,
|
|
1652
1652
|
});
|
|
1653
1653
|
return Array.isArray(ptrs) ? ptrs : [];
|
|
1654
1654
|
} catch (e) {
|
|
1655
|
-
|
|
1655
|
+
observability().safeEvent("network.dns.reverse.failure", 1, {
|
|
1656
1656
|
latencyMs: _now() - startMs, code: e.code || "unknown",
|
|
1657
1657
|
});
|
|
1658
1658
|
if (e instanceof DnsError) throw e;
|
|
@@ -1674,10 +1674,6 @@ function nodeLookup(host, options, callback) {
|
|
|
1674
1674
|
);
|
|
1675
1675
|
}
|
|
1676
1676
|
|
|
1677
|
-
function _emitObs(name, fields) {
|
|
1678
|
-
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
1677
|
function _stateForTest() { return STATE; }
|
|
1682
1678
|
function _resetForTest() {
|
|
1683
1679
|
STATE.servers = null; STATE.resultOrder = null; STATE.family = 0;
|
|
@@ -259,7 +259,7 @@ function statuses() {
|
|
|
259
259
|
|
|
260
260
|
function _emitObsProbe(entry, result) {
|
|
261
261
|
try {
|
|
262
|
-
observability().
|
|
262
|
+
observability().safeEvent("network.heartbeat.probe", 1, {
|
|
263
263
|
name: entry.target.name,
|
|
264
264
|
type: entry.target.type,
|
|
265
265
|
ok: result.ok,
|
|
@@ -381,7 +381,7 @@ function passive(opts) {
|
|
|
381
381
|
|
|
382
382
|
function _emitObsPong(state) {
|
|
383
383
|
try {
|
|
384
|
-
observability().
|
|
384
|
+
observability().safeEvent("network.heartbeat.passive.pong", 1, {
|
|
385
385
|
pongCount: state.pongCount,
|
|
386
386
|
timeoutMs: state.timeoutMs,
|
|
387
387
|
});
|
|
@@ -390,7 +390,7 @@ function _emitObsPong(state) {
|
|
|
390
390
|
|
|
391
391
|
function _emitObsTimeout(state) {
|
|
392
392
|
try {
|
|
393
|
-
observability().
|
|
393
|
+
observability().safeEvent("network.heartbeat.passive.timeout", 1, {
|
|
394
394
|
pongCount: state.pongCount,
|
|
395
395
|
lastPongMs: state.lastPongMs,
|
|
396
396
|
timeoutMs: state.timeoutMs,
|
|
@@ -70,7 +70,7 @@ function set(opts) {
|
|
|
70
70
|
if (opts.no !== undefined) STATE.noProxy = _parseNoProxy(opts.no);
|
|
71
71
|
if (opts.auth !== undefined) STATE.auth = opts.auth ? _parseAuth(opts.auth) : null;
|
|
72
72
|
STATE.agentCache.clear();
|
|
73
|
-
|
|
73
|
+
observability().safeEvent("network.proxy.set", 1, {
|
|
74
74
|
httpSet: !!STATE.http,
|
|
75
75
|
httpsSet: !!STATE.https,
|
|
76
76
|
noProxyCount: STATE.noProxy.length,
|
|
@@ -93,7 +93,7 @@ function fromEnv(envObj) {
|
|
|
93
93
|
if (authEnv) { STATE.auth = _parseAuth(authEnv); changed = true; }
|
|
94
94
|
if (changed) {
|
|
95
95
|
STATE.agentCache.clear();
|
|
96
|
-
|
|
96
|
+
observability().safeEvent("network.proxy.from_env", 1, {
|
|
97
97
|
httpSet: !!STATE.http,
|
|
98
98
|
httpsSet: !!STATE.https,
|
|
99
99
|
noProxyCount: STATE.noProxy.length,
|
|
@@ -255,7 +255,7 @@ function agentFor(targetUrl) {
|
|
|
255
255
|
};
|
|
256
256
|
}
|
|
257
257
|
STATE.agentCache.set(key, agent);
|
|
258
|
-
|
|
258
|
+
observability().safeEvent("network.proxy.agent.created", 1, { protocol: u.protocol });
|
|
259
259
|
return agent;
|
|
260
260
|
}
|
|
261
261
|
|
|
@@ -268,10 +268,6 @@ function snapshot() {
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
function _emitObs(name, fields) {
|
|
272
|
-
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
273
|
-
}
|
|
274
|
-
|
|
275
271
|
function _resetForTest() {
|
|
276
272
|
STATE.http = null; STATE.https = null; STATE.noProxy = []; STATE.auth = null;
|
|
277
273
|
STATE.agentCache.clear();
|