@blamejs/core 0.7.64 → 0.7.73
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 +18 -0
- package/index.js +2 -0
- package/lib/auth/aal.js +149 -0
- package/lib/auth/dpop.js +512 -0
- package/lib/auth/jwt.js +67 -0
- package/lib/auth/oauth.js +13 -6
- package/lib/cookies.js +2 -1
- package/lib/mail-unsubscribe.js +160 -0
- package/lib/mail.js +131 -6
- package/lib/middleware/dpop.js +173 -0
- package/lib/middleware/gpc.js +120 -0
- package/lib/middleware/index.js +8 -0
- package/lib/middleware/require-aal.js +107 -0
- package/lib/middleware/security-headers.js +29 -1
- package/lib/network-dns.js +131 -12
- package/lib/network-smtp-policy.js +7 -3
- package/lib/vendor/MANIFEST.json +21 -5
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/lib/middleware/index.js
CHANGED
|
@@ -27,14 +27,17 @@ var cors = require("./cors");
|
|
|
27
27
|
var cspNonce = require("./csp-nonce");
|
|
28
28
|
var csrfProtect = require("./csrf-protect");
|
|
29
29
|
var dbRoleFor = require("./db-role-for");
|
|
30
|
+
var dpop = require("./dpop");
|
|
30
31
|
var errorHandler = require("./error-handler");
|
|
31
32
|
var fetchMetadata = require("./fetch-metadata");
|
|
33
|
+
var gpc = require("./gpc");
|
|
32
34
|
var headers = require("./headers");
|
|
33
35
|
var health = require("./health");
|
|
34
36
|
var networkAllowlist = require("./network-allowlist");
|
|
35
37
|
var rateLimit = require("./rate-limit");
|
|
36
38
|
var requestId = require("./request-id");
|
|
37
39
|
var requestLog = require("./request-log");
|
|
40
|
+
var requireAal = require("./require-aal");
|
|
38
41
|
var requireAuth = require("./require-auth");
|
|
39
42
|
var securityHeaders = require("./security-headers");
|
|
40
43
|
var sse = require("./sse");
|
|
@@ -48,9 +51,11 @@ module.exports = {
|
|
|
48
51
|
rateLimit: rateLimit.create,
|
|
49
52
|
attachUser: attachUser.create,
|
|
50
53
|
bearerAuth: bearerAuth.create,
|
|
54
|
+
requireAal: requireAal.create,
|
|
51
55
|
requireAuth: requireAuth.create,
|
|
52
56
|
csrfProtect: csrfProtect.create,
|
|
53
57
|
fetchMetadata: fetchMetadata.create,
|
|
58
|
+
gpc: gpc.create,
|
|
54
59
|
headers: headers.create,
|
|
55
60
|
bodyParser: bodyParser.create,
|
|
56
61
|
health: health.create,
|
|
@@ -61,6 +66,7 @@ module.exports = {
|
|
|
61
66
|
requestLog: requestLog.create,
|
|
62
67
|
apiEncrypt: apiEncrypt,
|
|
63
68
|
dbRoleFor: dbRoleFor.create,
|
|
69
|
+
dpop: dpop.create,
|
|
64
70
|
networkAllowlist: networkAllowlist.create,
|
|
65
71
|
|
|
66
72
|
// Module exports for advanced use (constants, raw factory access)
|
|
@@ -73,6 +79,7 @@ module.exports = {
|
|
|
73
79
|
rateLimit: rateLimit,
|
|
74
80
|
attachUser: attachUser,
|
|
75
81
|
bearerAuth: bearerAuth,
|
|
82
|
+
requireAal: requireAal,
|
|
76
83
|
requireAuth: requireAuth,
|
|
77
84
|
csrfProtect: csrfProtect,
|
|
78
85
|
fetchMetadata: fetchMetadata,
|
|
@@ -84,6 +91,7 @@ module.exports = {
|
|
|
84
91
|
requestLog: requestLog,
|
|
85
92
|
apiEncrypt: apiEncrypt,
|
|
86
93
|
dbRoleFor: dbRoleFor,
|
|
94
|
+
dpop: dpop,
|
|
87
95
|
networkAllowlist: networkAllowlist,
|
|
88
96
|
},
|
|
89
97
|
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* require-aal middleware — gate routes by NIST SP 800-63-4 AAL band.
|
|
4
|
+
*
|
|
5
|
+
* var stepUp = b.middleware.requireAal({ minimum: "AAL2" });
|
|
6
|
+
* router.use("/admin", stepUp);
|
|
7
|
+
*
|
|
8
|
+
* Reads the AAL band from `req.user.aal` by default. Operators with a
|
|
9
|
+
* different shape pass `getAal(req)` returning the band string.
|
|
10
|
+
*
|
|
11
|
+
* On failure the middleware writes 401 with
|
|
12
|
+
* `WWW-Authenticate: AAL-StepUp realm="<X>", required="<minimum>"`
|
|
13
|
+
* — the bespoke scheme name signals to the operator's frontend that a
|
|
14
|
+
* step-up flow should be triggered (re-prompt for TOTP / passkey).
|
|
15
|
+
*
|
|
16
|
+
* Audit:
|
|
17
|
+
* auth.aal.granted — request passed (carries the actual band)
|
|
18
|
+
* auth.aal.denied — request below the required minimum
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
var lazyRequire = require("../lazy-require");
|
|
22
|
+
var requestHelpers = require("../request-helpers");
|
|
23
|
+
var validateOpts = require("../validate-opts");
|
|
24
|
+
var { AuthError } = require("../framework-error");
|
|
25
|
+
|
|
26
|
+
var aal = lazyRequire(function () { return require("../auth/aal"); });
|
|
27
|
+
var audit = lazyRequire(function () { return require("../audit"); });
|
|
28
|
+
|
|
29
|
+
function _writeUnauthorized(res, requiredBand, actualBand, realm) {
|
|
30
|
+
if (res.headersSent) return;
|
|
31
|
+
var body = JSON.stringify({
|
|
32
|
+
error: "step_up_required",
|
|
33
|
+
error_description: "AAL " + requiredBand + " is required for this resource",
|
|
34
|
+
required_aal: requiredBand,
|
|
35
|
+
actual_aal: actualBand || null,
|
|
36
|
+
});
|
|
37
|
+
var realmStr = realm ? ' realm="' + realm + '"' : "";
|
|
38
|
+
var challenge = "AAL-StepUp" + realmStr + ', required="' + requiredBand + '"';
|
|
39
|
+
res.writeHead(401, { // allow:raw-byte-literal — HTTP 401 status
|
|
40
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
41
|
+
"Content-Length": Buffer.byteLength(body),
|
|
42
|
+
"WWW-Authenticate": challenge,
|
|
43
|
+
});
|
|
44
|
+
res.end(body);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function create(opts) {
|
|
48
|
+
opts = opts || {};
|
|
49
|
+
validateOpts(opts, [
|
|
50
|
+
"minimum", "getAal", "audit", "realm",
|
|
51
|
+
], "middleware.requireAal");
|
|
52
|
+
|
|
53
|
+
var minimum = opts.minimum;
|
|
54
|
+
if (!aal().isValidBand(minimum)) {
|
|
55
|
+
throw new AuthError("auth-aal/bad-minimum",
|
|
56
|
+
"middleware.requireAal: opts.minimum must be one of " +
|
|
57
|
+
aal().BANDS.join(", ") + " (got " + JSON.stringify(minimum) + ")");
|
|
58
|
+
}
|
|
59
|
+
validateOpts.optionalFunction(opts.getAal,
|
|
60
|
+
"middleware.requireAal: getAal", AuthError, "auth-aal/bad-opt");
|
|
61
|
+
|
|
62
|
+
var auditOn = opts.audit !== false;
|
|
63
|
+
var realm = (typeof opts.realm === "string" && opts.realm.length > 0) ? opts.realm : null;
|
|
64
|
+
|
|
65
|
+
return function requireAalMiddleware(req, res, next) {
|
|
66
|
+
var actual = null;
|
|
67
|
+
if (typeof opts.getAal === "function") {
|
|
68
|
+
try { actual = opts.getAal(req); } catch (_e) { actual = null; }
|
|
69
|
+
} else if (req.user && typeof req.user.aal === "string") {
|
|
70
|
+
actual = req.user.aal;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!aal().meets(actual, minimum)) {
|
|
74
|
+
if (auditOn) {
|
|
75
|
+
try {
|
|
76
|
+
audit().safeEmit({
|
|
77
|
+
action: "auth.aal.denied",
|
|
78
|
+
actor: { clientIp: requestHelpers.clientIp(req), userId: req.user && req.user.id },
|
|
79
|
+
outcome: "fail",
|
|
80
|
+
metadata: {
|
|
81
|
+
required: minimum,
|
|
82
|
+
actual: actual || null,
|
|
83
|
+
route: req.url,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
} catch (_ignored) { /* drop-silent */ }
|
|
87
|
+
}
|
|
88
|
+
return _writeUnauthorized(res, minimum, actual, realm);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (auditOn) {
|
|
92
|
+
try {
|
|
93
|
+
audit().safeEmit({
|
|
94
|
+
action: "auth.aal.granted",
|
|
95
|
+
actor: { clientIp: requestHelpers.clientIp(req), userId: req.user && req.user.id },
|
|
96
|
+
outcome: "ok",
|
|
97
|
+
metadata: { aal: actual, required: minimum, route: req.url },
|
|
98
|
+
});
|
|
99
|
+
} catch (_ignored) { /* drop-silent */ }
|
|
100
|
+
}
|
|
101
|
+
return next();
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
create: create,
|
|
107
|
+
};
|
|
@@ -75,6 +75,7 @@ function create(opts) {
|
|
|
75
75
|
"hsts", "contentTypeOptions", "frameOptions", "referrerPolicy",
|
|
76
76
|
"permissionsPolicy", "coop", "coep", "corp",
|
|
77
77
|
"originAgentCluster", "dnsPrefetchControl", "csp", "trustProxy",
|
|
78
|
+
"reportingEndpoints",
|
|
78
79
|
], "middleware.securityHeaders");
|
|
79
80
|
var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
|
|
80
81
|
? opts.trustProxy : false;
|
|
@@ -89,6 +90,32 @@ function create(opts) {
|
|
|
89
90
|
var oac = opts.originAgentCluster === undefined ? "?1" : opts.originAgentCluster;
|
|
90
91
|
var dpc = opts.dnsPrefetchControl === undefined ? "off" : opts.dnsPrefetchControl;
|
|
91
92
|
var csp = opts.csp === undefined ? DEFAULT_CSP : opts.csp;
|
|
93
|
+
// Reporting-Endpoints (W3C Reporting API) — when operator passes a
|
|
94
|
+
// map of endpoint-name → URL, we emit `Reporting-Endpoints: name="url",
|
|
95
|
+
// name2="url2", ...` and (when default CSP is in force) append
|
|
96
|
+
// `report-to default` to the CSP so violations route to the named
|
|
97
|
+
// endpoint. Operators using a custom CSP add `report-to` to it
|
|
98
|
+
// themselves.
|
|
99
|
+
var reportingEndpoints = null;
|
|
100
|
+
if (opts.reportingEndpoints && typeof opts.reportingEndpoints === "object") {
|
|
101
|
+
var pairs = [];
|
|
102
|
+
var keys = Object.keys(opts.reportingEndpoints);
|
|
103
|
+
for (var i = 0; i < keys.length; i += 1) {
|
|
104
|
+
var k = keys[i];
|
|
105
|
+
var v = opts.reportingEndpoints[k];
|
|
106
|
+
if (typeof v !== "string" || v.length === 0) continue;
|
|
107
|
+
// Defensive — refuse CR/LF/NUL in either side (header injection).
|
|
108
|
+
if (/[\r\n\0]/.test(k) || /[\r\n\0]/.test(v)) continue; // allow:duplicate-regex — CR/LF/NUL header-injection rejection appears in cookies / mail / security-headers; each is the boundary primitive — extracting forces a shared module that hides the boundary check from each domain
|
|
109
|
+
pairs.push(k + '="' + v + '"');
|
|
110
|
+
}
|
|
111
|
+
if (pairs.length > 0) reportingEndpoints = pairs.join(", ");
|
|
112
|
+
}
|
|
113
|
+
// Auto-append `report-to default` to the default CSP when operator
|
|
114
|
+
// wires a `default` reporting endpoint and didn't override `csp`.
|
|
115
|
+
if (csp === DEFAULT_CSP && reportingEndpoints &&
|
|
116
|
+
opts.reportingEndpoints && opts.reportingEndpoints["default"]) {
|
|
117
|
+
csp = csp.replace(/;\s*$/, "") + "; report-to default;";
|
|
118
|
+
}
|
|
92
119
|
|
|
93
120
|
return function securityHeaders(req, res, next) {
|
|
94
121
|
if (typeof res.setHeader !== "function") return next();
|
|
@@ -109,7 +136,8 @@ function create(opts) {
|
|
|
109
136
|
if (corp) res.setHeader("Cross-Origin-Resource-Policy", corp);
|
|
110
137
|
if (oac) res.setHeader("Origin-Agent-Cluster", oac);
|
|
111
138
|
if (dpc) res.setHeader("X-DNS-Prefetch-Control", dpc);
|
|
112
|
-
if (csp)
|
|
139
|
+
if (csp) res.setHeader("Content-Security-Policy", csp);
|
|
140
|
+
if (reportingEndpoints) res.setHeader("Reporting-Endpoints", reportingEndpoints);
|
|
113
141
|
next();
|
|
114
142
|
};
|
|
115
143
|
}
|
package/lib/network-dns.js
CHANGED
|
@@ -291,28 +291,44 @@ function _encodeDnsQuery(host, qtype) {
|
|
|
291
291
|
return { buf: buf, id: id };
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// Walk a DNS-message name in-place and advance `state.off`. RFC 1035
|
|
295
|
+
// §3.1 names terminate either with a single 0x00 byte OR with a
|
|
296
|
+
// 2-byte compression pointer (high two bits 11). The pre-0.7.68
|
|
297
|
+
// parser unconditionally executed `if (buf[off] === 0) off++`
|
|
298
|
+
// after the loop, which consumed the high byte of the next field
|
|
299
|
+
// when the loop had exited via the compression pointer — silently
|
|
300
|
+
// breaking every DNS response that used name compression in the
|
|
301
|
+
// answer section (which is most of them).
|
|
302
|
+
function _skipDnsName(buf, state) {
|
|
303
|
+
var endedViaPointer = false;
|
|
304
|
+
while (state.off < buf.length && buf[state.off] !== 0) {
|
|
305
|
+
if ((buf[state.off] & 0xc0) === 0xc0) { // allow:raw-byte-literal — RFC 1035 name-compression pointer mask
|
|
306
|
+
state.off += 2;
|
|
307
|
+
endedViaPointer = true;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
state.off += buf[state.off] + 1;
|
|
311
|
+
}
|
|
312
|
+
if (!endedViaPointer && state.off < buf.length && buf[state.off] === 0) {
|
|
313
|
+
state.off += 1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
294
317
|
function _decodeDnsAnswer(buf, qtype) {
|
|
295
318
|
if (!Buffer.isBuffer(buf) || buf.length < 12) throw new DnsError("dns/bad-reply", "dns reply truncated");
|
|
296
319
|
var rcode = buf.readUInt8(3) & 0x0f;
|
|
297
320
|
if (rcode !== 0) throw new DnsError("dns/no-result", "dns reply rcode " + rcode);
|
|
298
321
|
var qdcount = buf.readUInt16BE(4);
|
|
299
322
|
var ancount = buf.readUInt16BE(6);
|
|
300
|
-
var
|
|
323
|
+
var state = { off: 12 };
|
|
301
324
|
for (var q = 0; q < qdcount; q++) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
off += buf[off] + 1;
|
|
305
|
-
}
|
|
306
|
-
if (buf[off] === 0) off++;
|
|
307
|
-
off += 4;
|
|
325
|
+
_skipDnsName(buf, state);
|
|
326
|
+
state.off += 4;
|
|
308
327
|
}
|
|
309
328
|
var addrs = [];
|
|
310
329
|
for (var a = 0; a < ancount; a++) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
off += buf[off] + 1;
|
|
314
|
-
}
|
|
315
|
-
if (buf[off] === 0) off++;
|
|
330
|
+
_skipDnsName(buf, state);
|
|
331
|
+
var off = state.off;
|
|
316
332
|
var rtype = buf.readUInt16BE(off); off += 2;
|
|
317
333
|
off += 2;
|
|
318
334
|
off += 4;
|
|
@@ -327,10 +343,20 @@ function _decodeDnsAnswer(buf, qtype) {
|
|
|
327
343
|
addrs.push(groups.join(":"));
|
|
328
344
|
}
|
|
329
345
|
off += rdlen;
|
|
346
|
+
state.off = off;
|
|
330
347
|
}
|
|
331
348
|
return addrs;
|
|
332
349
|
}
|
|
333
350
|
|
|
351
|
+
// Read the AD bit (Authenticated Data, RFC 4035) from a DNS reply
|
|
352
|
+
// header. Byte 3 holds RA, Z, AD, CD, and rcode bits; AD is bit 5
|
|
353
|
+
// (mask 0x20). Set when the upstream recursive resolver has validated
|
|
354
|
+
// the chain.
|
|
355
|
+
function _readAdBit(buf) {
|
|
356
|
+
if (!Buffer.isBuffer(buf) || buf.length < 12) return false;
|
|
357
|
+
return (buf.readUInt8(3) & 0x20) !== 0; // allow:raw-byte-literal — RFC 4035 AD-bit mask
|
|
358
|
+
}
|
|
359
|
+
|
|
334
360
|
// DoH GET URL length cap. RFC 8484 §4.1 says clients MAY use POST when
|
|
335
361
|
// the GET URL would exceed implementation limits. We pick 2048 bytes
|
|
336
362
|
// (a conservative ceiling well below RFC 7230's recommended 8 KB) so
|
|
@@ -404,6 +430,98 @@ async function _dohLookup(host, family) {
|
|
|
404
430
|
});
|
|
405
431
|
}
|
|
406
432
|
|
|
433
|
+
// _dohLookupSecure — DNSSEC-aware DoH lookup. Returns `{ rrs, ad }`
|
|
434
|
+
// where `ad` is the AD bit (RFC 4035) set by the upstream resolver
|
|
435
|
+
// after chain validation. Internal — operators reach for
|
|
436
|
+
// `resolveSecure` instead.
|
|
437
|
+
async function _dohLookupSecure(host, family) {
|
|
438
|
+
var qtype = family === 6 ? 28 : 1; // allow:raw-byte-literal — DNS QTYPE values for A / AAAA
|
|
439
|
+
var enc = _encodeDnsQuery(host, qtype);
|
|
440
|
+
var b64 = enc.buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
441
|
+
var getUrl = STATE.doh.url + (STATE.doh.url.indexOf("?") === -1 ? "?" : "&") + "dns=" + b64;
|
|
442
|
+
var forcedMethod = STATE.doh.method;
|
|
443
|
+
var usePost = forcedMethod === "POST" || (!forcedMethod && getUrl.length > DOH_GET_URL_MAX_BYTES);
|
|
444
|
+
var u = safeUrl.parse(STATE.doh.url, { allowedProtocols: safeUrl.ALLOW_HTTP_TLS });
|
|
445
|
+
return new Promise(function (resolve, reject) {
|
|
446
|
+
var reqOpts = {
|
|
447
|
+
hostname: u.hostname,
|
|
448
|
+
port: u.port || 443, // allow:raw-byte-literal — HTTPS default port
|
|
449
|
+
path: u.pathname + u.search,
|
|
450
|
+
method: usePost ? "POST" : "GET",
|
|
451
|
+
headers: { "accept": "application/dns-message" },
|
|
452
|
+
minVersion: "TLSv1.3",
|
|
453
|
+
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
454
|
+
};
|
|
455
|
+
if (STATE.doh.ca) reqOpts.ca = STATE.doh.ca;
|
|
456
|
+
if (usePost) {
|
|
457
|
+
reqOpts.headers["content-type"] = "application/dns-message";
|
|
458
|
+
reqOpts.headers["content-length"] = enc.buf.length;
|
|
459
|
+
} else {
|
|
460
|
+
var parsedGet = safeUrl.parse(getUrl, { allowedProtocols: safeUrl.ALLOW_HTTP_TLS });
|
|
461
|
+
reqOpts.path = parsedGet.pathname + parsedGet.search;
|
|
462
|
+
}
|
|
463
|
+
var req = https.request(reqOpts, function (res) {
|
|
464
|
+
var collector = safeBuffer.boundedChunkCollector({
|
|
465
|
+
maxBytes: C.BYTES.kib(256),
|
|
466
|
+
errorClass: DnsError,
|
|
467
|
+
sizeCode: "dns/doh-too-large",
|
|
468
|
+
sizeMessage: "DoH response exceeds 256 KiB",
|
|
469
|
+
});
|
|
470
|
+
var pushFailed = null;
|
|
471
|
+
res.on("data", function (c) {
|
|
472
|
+
if (pushFailed) return;
|
|
473
|
+
try { collector.push(c); }
|
|
474
|
+
catch (e) { pushFailed = e; }
|
|
475
|
+
});
|
|
476
|
+
res.on("end", function () {
|
|
477
|
+
try {
|
|
478
|
+
if (pushFailed) { reject(pushFailed); return; }
|
|
479
|
+
var body = collector.result();
|
|
480
|
+
if (res.statusCode !== 200) { // allow:raw-byte-literal — HTTP 200 OK
|
|
481
|
+
reject(new DnsError("dns/doh-http", "DoH HTTP " + res.statusCode + " for " + host));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
resolve({ rrs: _decodeDnsAnswer(body, qtype), ad: _readAdBit(body) });
|
|
485
|
+
} catch (e) { reject(e); }
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
req.on("error", function (e) { reject(new DnsError("dns/doh-failed", "DoH request failed: " + e.message)); });
|
|
489
|
+
if (usePost) req.write(enc.buf);
|
|
490
|
+
req.end();
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// resolveSecure — DNSSEC-aware resolution. Returns `{ rrs, ad }`
|
|
495
|
+
// where `ad` is the AD bit (RFC 4035) set by the upstream DoH
|
|
496
|
+
// resolver after chain validation, and `rrs` is the answer-record
|
|
497
|
+
// list (IPv4 / IPv6 string addresses for A / AAAA queries).
|
|
498
|
+
//
|
|
499
|
+
// Operators wiring DANE / TLSA validation (RFC 7672 SMTP DANE)
|
|
500
|
+
// require `ad === true` to honor the DANE security claim per RFC
|
|
501
|
+
// 7672 §1.3 — without DNSSEC validation the TLSA records can't be
|
|
502
|
+
// authenticated and the chain check is meaningless.
|
|
503
|
+
//
|
|
504
|
+
// Only available over DoH transport. The system resolver and DoT
|
|
505
|
+
// transports don't surface the AD bit through Node's API today.
|
|
506
|
+
async function resolveSecure(host, type) {
|
|
507
|
+
type = type || "A";
|
|
508
|
+
if (!STATE.doh) {
|
|
509
|
+
throw new DnsError("dns/secure-requires-doh",
|
|
510
|
+
"resolveSecure requires DoH transport (call useDnsOverHttps " +
|
|
511
|
+
"or rely on the default-on DoH posture)");
|
|
512
|
+
}
|
|
513
|
+
if (typeof host !== "string" || host.length === 0 || host.length > 253) { // allow:raw-byte-literal — RFC 1035 hostname octet ceiling
|
|
514
|
+
throw new DnsError("dns/bad-host",
|
|
515
|
+
"resolveSecure host is malformed");
|
|
516
|
+
}
|
|
517
|
+
var family;
|
|
518
|
+
if (type === "A") family = 4;
|
|
519
|
+
else if (type === "AAAA") family = 6;
|
|
520
|
+
else throw new DnsError("dns/secure-unsupported-type",
|
|
521
|
+
"resolveSecure currently supports A and AAAA; got " + type);
|
|
522
|
+
return _dohLookupSecure(host, family);
|
|
523
|
+
}
|
|
524
|
+
|
|
407
525
|
// DoT connection pool. Per-(host:port) cached TLS socket so successive
|
|
408
526
|
// lookups amortize the handshake. Sockets idle past the timeout are
|
|
409
527
|
// closed and removed; first lookup after expiry rebuilds. Each socket
|
|
@@ -695,6 +813,7 @@ module.exports = {
|
|
|
695
813
|
resolve4: resolve4,
|
|
696
814
|
resolve6: resolve6,
|
|
697
815
|
resolveAaaa: resolveAaaa,
|
|
816
|
+
resolveSecure: resolveSecure,
|
|
698
817
|
nodeLookup: nodeLookup,
|
|
699
818
|
clearCache: _clearCache,
|
|
700
819
|
DnsError: DnsError,
|
|
@@ -42,9 +42,13 @@
|
|
|
42
42
|
* - Full DANE certificate-chain verification per RFC 6698 (needs
|
|
43
43
|
* ASN.1 cert parsing). Operators today verify policy presence +
|
|
44
44
|
* match the leaf SHA-256 themselves.
|
|
45
|
-
* - DNSSEC-validated DANE lookups
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* - DNSSEC-validated DANE lookups: the framework now exposes the
|
|
46
|
+
* AD bit via `b.network.dns.resolveSecure(name, type) → { rrs,
|
|
47
|
+
* ad }`. Operators wiring strict RFC 7672 §1.3 compliance pass
|
|
48
|
+
* a DNSSEC-aware resolver via opts and refuse the chain when
|
|
49
|
+
* ad === false. The default `tlsa()` lookup path stays on
|
|
50
|
+
* `node:dns` for compatibility with operators on system
|
|
51
|
+
* resolvers.
|
|
48
52
|
*/
|
|
49
53
|
|
|
50
54
|
var dns = require("node:dns");
|
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
"server": "lib/vendor/noble-ciphers.cjs"
|
|
14
14
|
},
|
|
15
15
|
"bundler": "esbuild --format=cjs --minify --platform=node",
|
|
16
|
-
"bundledAt": "2026-04-25"
|
|
16
|
+
"bundledAt": "2026-04-25",
|
|
17
|
+
"hashes": {
|
|
18
|
+
"server": "sha256:5d539dfc9ef47121d4c09bd7256d76448a1f5ac47ee09ac44c78ff6a062af9ab"
|
|
19
|
+
}
|
|
17
20
|
},
|
|
18
21
|
"argon2": {
|
|
19
22
|
"version": "0.44.0",
|
|
@@ -39,7 +42,11 @@
|
|
|
39
42
|
"win32-x64"
|
|
40
43
|
],
|
|
41
44
|
"bundler": "esbuild --format=cjs --platform=node (inlines @phc/format + node-gyp-build)",
|
|
42
|
-
"bundledAt": "2026-04-25"
|
|
45
|
+
"bundledAt": "2026-04-25",
|
|
46
|
+
"hashes": {
|
|
47
|
+
"server": "sha256:93b8d2fb7f24dc1b3304dc9420844d5e1afc199c41ab1f9a90c8de48cc7c2359",
|
|
48
|
+
"prebuilds": "sha256-tree:65921b7cf331e0a9430a1b52440da8f26cdf9d215a4cd490edbc4804dd713df3"
|
|
49
|
+
}
|
|
43
50
|
},
|
|
44
51
|
"@simplewebauthn/server": {
|
|
45
52
|
"version": "13.3.0",
|
|
@@ -57,7 +64,10 @@
|
|
|
57
64
|
"server": "lib/vendor/simplewebauthn-server.cjs"
|
|
58
65
|
},
|
|
59
66
|
"bundler": "esbuild --format=cjs --minify --platform=node --external:crypto --external:node:crypto",
|
|
60
|
-
"bundledAt": "2026-04-26"
|
|
67
|
+
"bundledAt": "2026-04-26",
|
|
68
|
+
"hashes": {
|
|
69
|
+
"server": "sha256:a9777dca582095d67f17ca24e19a0791de29928555b6b779c2233429175eb3f0"
|
|
70
|
+
}
|
|
61
71
|
},
|
|
62
72
|
"SecLists-common-passwords-top-10000": {
|
|
63
73
|
"version": "10k-most-common (master)",
|
|
@@ -69,7 +79,10 @@
|
|
|
69
79
|
"server": "lib/vendor/common-passwords-top-10000.txt"
|
|
70
80
|
},
|
|
71
81
|
"bundler": "curl https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10k-most-common.txt",
|
|
72
|
-
"bundledAt": "2026-05-02"
|
|
82
|
+
"bundledAt": "2026-05-02",
|
|
83
|
+
"hashes": {
|
|
84
|
+
"server": "sha256:4adb3f0afb4a10cf19ebe48d8c69a46f934bbc8d77c694c210564f9583e7f4ba"
|
|
85
|
+
}
|
|
73
86
|
},
|
|
74
87
|
"peculiar-pki": {
|
|
75
88
|
"version": "2.0.0+pkijs-3.4.0",
|
|
@@ -90,7 +103,10 @@
|
|
|
90
103
|
"server": "lib/vendor/pki.cjs"
|
|
91
104
|
},
|
|
92
105
|
"bundler": "esbuild --format=cjs --minify --platform=node --external:crypto --external:node:crypto",
|
|
93
|
-
"bundledAt": "2026-04-29"
|
|
106
|
+
"bundledAt": "2026-04-29",
|
|
107
|
+
"hashes": {
|
|
108
|
+
"server": "sha256:9bbc191afaaa2b1e5757f00480457c08134cdc2c55d541df18d9155bba9cbf77"
|
|
109
|
+
}
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
}
|
package/package.json
CHANGED
package/sbom.cyclonedx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:55a0114a-f249-481f-b122-6997247485a1",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-06T03:31:31.641Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.7.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.7.73",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.73",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.7.73",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.7.
|
|
57
|
+
"ref": "@blamejs/core@0.7.73",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|