@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.
@@ -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) res.setHeader("Content-Security-Policy", 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
  }
@@ -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 off = 12;
323
+ var state = { off: 12 };
301
324
  for (var q = 0; q < qdcount; q++) {
302
- while (off < buf.length && buf[off] !== 0) {
303
- if ((buf[off] & 0xc0) === 0xc0) { off += 2; break; }
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
- while (off < buf.length && buf[off] !== 0) {
312
- if ((buf[off] & 0xc0) === 0xc0) { off += 2; break; }
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 (node:dns doesn't expose
46
- * DNSSEC ad-bit; operators pin to a DNSSEC-validating resolver
47
- * externally).
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
  };
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.7.64",
3
+ "version": "0.7.74",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -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:186ac1cf-f222-4ea2-81b9-ad8a89f1849e",
5
+ "serialNumber": "urn:uuid:a82c1893-13c0-448e-a951-71bc3d182da3",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-06T00:58:07.750Z",
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.64",
22
+ "bom-ref": "@blamejs/core@0.7.74",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.7.64",
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.64",
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.64",
57
+ "ref": "@blamejs/core@0.7.74",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]