@blamejs/core 0.7.64 → 0.7.74
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 +20 -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-auth.js +356 -2
- package/lib/mail-unsubscribe.js +160 -0
- package/lib/mail.js +135 -9
- 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 +118 -3
- package/lib/vendor/MANIFEST.json +21 -5
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
|
@@ -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");
|
|
@@ -56,6 +60,7 @@ var lazyRequire = require("./lazy-require");
|
|
|
56
60
|
var validateOpts = require("./validate-opts");
|
|
57
61
|
var crypto = require("./crypto");
|
|
58
62
|
var safeUrl = require("./safe-url");
|
|
63
|
+
var safeJson = require("./safe-json");
|
|
59
64
|
var C = require("./constants");
|
|
60
65
|
var { SmtpPolicyError } = require("./framework-error");
|
|
61
66
|
|
|
@@ -531,6 +536,115 @@ async function tlsRptSubmit(report, opts) {
|
|
|
531
536
|
return { submitted: results.length, results: results };
|
|
532
537
|
}
|
|
533
538
|
|
|
539
|
+
// ---- TLS-RPT receive-side report parsing (RFC 8460 §4) ----
|
|
540
|
+
//
|
|
541
|
+
// MTAs that publish a TLS-RPT rua endpoint receive `application/
|
|
542
|
+
// tlsrpt+gzip` (or `application/json`) HTTPS POSTs from peers. The
|
|
543
|
+
// receiver parses the report, attributes failures to the right policy/
|
|
544
|
+
// MX-host pair, and feeds the data into the operator's observability
|
|
545
|
+
// stack. This primitive is the receive-side counterpart to
|
|
546
|
+
// tlsRpt.recordShape / tlsRpt.submit on the send side.
|
|
547
|
+
|
|
548
|
+
var TLS_RPT_MAX_REPORT_BYTES = C.BYTES.mib(8);
|
|
549
|
+
var TLS_RPT_MAX_POLICIES_PER_REPORT = 1024; // allow:raw-byte-literal allow:raw-time-literal — count cap, not seconds
|
|
550
|
+
|
|
551
|
+
function tlsRptParseReport(body, opts) {
|
|
552
|
+
opts = opts || {};
|
|
553
|
+
if (body === null || body === undefined) {
|
|
554
|
+
throw new SmtpPolicyError("smtp/tls-rpt-bad-input",
|
|
555
|
+
"tlsRpt.parseReport: body is required (Buffer | string)");
|
|
556
|
+
}
|
|
557
|
+
var bodyBuf;
|
|
558
|
+
if (Buffer.isBuffer(body)) bodyBuf = body;
|
|
559
|
+
else if (typeof body === "string") bodyBuf = Buffer.from(body, "utf8");
|
|
560
|
+
else {
|
|
561
|
+
throw new SmtpPolicyError("smtp/tls-rpt-bad-input",
|
|
562
|
+
"tlsRpt.parseReport: body must be a Buffer or string");
|
|
563
|
+
}
|
|
564
|
+
if (bodyBuf.length > TLS_RPT_MAX_REPORT_BYTES) {
|
|
565
|
+
throw new SmtpPolicyError("smtp/tls-rpt-too-large",
|
|
566
|
+
"tlsRpt.parseReport: report exceeds " + TLS_RPT_MAX_REPORT_BYTES + " bytes");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Decompress if the operator passes contentType: "application/tlsrpt+gzip"
|
|
570
|
+
// OR if the body sniffs as gzip (magic 0x1f 0x8b).
|
|
571
|
+
var contentType = (opts.contentType || "").toLowerCase();
|
|
572
|
+
var looksGzip = bodyBuf.length >= 2 && bodyBuf[0] === 0x1f && bodyBuf[1] === 0x8b;
|
|
573
|
+
if (contentType.indexOf("gzip") !== -1 || looksGzip) {
|
|
574
|
+
try { bodyBuf = zlib.gunzipSync(bodyBuf, { maxOutputLength: TLS_RPT_MAX_REPORT_BYTES }); }
|
|
575
|
+
catch (e) {
|
|
576
|
+
throw new SmtpPolicyError("smtp/tls-rpt-gunzip-failed",
|
|
577
|
+
"tlsRpt.parseReport: gunzip failed: " + ((e && e.message) || String(e)));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
var report;
|
|
582
|
+
try { report = safeJson.parse(bodyBuf.toString("utf8"), { maxBytes: TLS_RPT_MAX_REPORT_BYTES }); }
|
|
583
|
+
catch (e) {
|
|
584
|
+
throw new SmtpPolicyError("smtp/tls-rpt-bad-json",
|
|
585
|
+
"tlsRpt.parseReport: JSON parse failed: " + ((e && e.message) || String(e)));
|
|
586
|
+
}
|
|
587
|
+
if (!report || typeof report !== "object") {
|
|
588
|
+
throw new SmtpPolicyError("smtp/tls-rpt-bad-shape",
|
|
589
|
+
"tlsRpt.parseReport: report must be an object");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Validate the RFC 8460 §4.4 required fields. Optional fields are
|
|
593
|
+
// surfaced as-is when present (some operators ship `contact-info`,
|
|
594
|
+
// some don't).
|
|
595
|
+
var requiredKeys = ["organization-name", "date-range", "report-id", "policies"];
|
|
596
|
+
for (var ri = 0; ri < requiredKeys.length; ri += 1) {
|
|
597
|
+
if (!Object.prototype.hasOwnProperty.call(report, requiredKeys[ri])) {
|
|
598
|
+
throw new SmtpPolicyError("smtp/tls-rpt-missing-field",
|
|
599
|
+
"tlsRpt.parseReport: report missing required field '" + requiredKeys[ri] + "' (RFC 8460 §4.4)");
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (!report["date-range"] ||
|
|
603
|
+
typeof report["date-range"]["start-datetime"] !== "string" ||
|
|
604
|
+
typeof report["date-range"]["end-datetime"] !== "string") {
|
|
605
|
+
throw new SmtpPolicyError("smtp/tls-rpt-bad-date-range",
|
|
606
|
+
"tlsRpt.parseReport: date-range must have start-datetime + end-datetime");
|
|
607
|
+
}
|
|
608
|
+
if (!Array.isArray(report.policies)) {
|
|
609
|
+
throw new SmtpPolicyError("smtp/tls-rpt-bad-policies",
|
|
610
|
+
"tlsRpt.parseReport: policies must be an array");
|
|
611
|
+
}
|
|
612
|
+
if (report.policies.length > TLS_RPT_MAX_POLICIES_PER_REPORT) {
|
|
613
|
+
throw new SmtpPolicyError("smtp/tls-rpt-too-many-policies",
|
|
614
|
+
"tlsRpt.parseReport: report has " + report.policies.length +
|
|
615
|
+
" policies (cap " + TLS_RPT_MAX_POLICIES_PER_REPORT + ")");
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Aggregate counters operators most commonly want surfaced.
|
|
619
|
+
var totalSuccess = 0;
|
|
620
|
+
var totalFailure = 0;
|
|
621
|
+
for (var pi = 0; pi < report.policies.length; pi += 1) {
|
|
622
|
+
var entry = report.policies[pi];
|
|
623
|
+
if (entry && entry.summary) {
|
|
624
|
+
var s = entry.summary["total-successful-session-count"];
|
|
625
|
+
var f = entry.summary["total-failure-session-count"];
|
|
626
|
+
if (typeof s === "number" && isFinite(s)) totalSuccess += s;
|
|
627
|
+
if (typeof f === "number" && isFinite(f)) totalFailure += f;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
organization: report["organization-name"],
|
|
633
|
+
contact: report["contact-info"] || null,
|
|
634
|
+
reportId: report["report-id"],
|
|
635
|
+
dateRange: {
|
|
636
|
+
start: report["date-range"]["start-datetime"],
|
|
637
|
+
end: report["date-range"]["end-datetime"],
|
|
638
|
+
},
|
|
639
|
+
policies: report.policies,
|
|
640
|
+
totals: {
|
|
641
|
+
successful: totalSuccess,
|
|
642
|
+
failure: totalFailure,
|
|
643
|
+
},
|
|
644
|
+
raw: report,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
534
648
|
module.exports = {
|
|
535
649
|
mtaSts: Object.freeze({
|
|
536
650
|
fetch: mtaStsFetch,
|
|
@@ -546,6 +660,7 @@ module.exports = {
|
|
|
546
660
|
recordShape: tlsRptRecordShape,
|
|
547
661
|
fetchPolicy: tlsRptFetchPolicy,
|
|
548
662
|
submit: tlsRptSubmit,
|
|
663
|
+
parseReport: tlsRptParseReport,
|
|
549
664
|
}),
|
|
550
665
|
SmtpPolicyError: SmtpPolicyError,
|
|
551
666
|
};
|
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:a82c1893-13c0-448e-a951-71bc3d182da3",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-06T03:56:40.534Z",
|
|
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.74",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.74",
|
|
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.74",
|
|
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.74",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|