@blamejs/blamejs-shop 0.1.32 → 0.1.34

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +2 -0
  3. package/lib/admin.js +18 -10
  4. package/lib/asset-manifest.json +17 -5
  5. package/lib/storefront.js +326 -44
  6. package/lib/vendor/MANIFEST.json +2 -2
  7. package/lib/vendor/blamejs/CHANGELOG.md +10 -0
  8. package/lib/vendor/blamejs/README.md +4 -2
  9. package/lib/vendor/blamejs/api-snapshot.json +107 -2
  10. package/lib/vendor/blamejs/index.js +3 -0
  11. package/lib/vendor/blamejs/lib/crypto-oprf.js +110 -0
  12. package/lib/vendor/blamejs/lib/iab-tcf.js +277 -2
  13. package/lib/vendor/blamejs/lib/network-tsig.js +404 -0
  14. package/lib/vendor/blamejs/lib/network.js +1 -0
  15. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +44 -9
  16. package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +19 -0
  17. package/lib/vendor/blamejs/lib/worm.js +246 -0
  18. package/lib/vendor/blamejs/package.json +1 -1
  19. package/lib/vendor/blamejs/release-notes/v0.12.x.json +1844 -0
  20. package/lib/vendor/blamejs/release-notes/v0.13.0.json +22 -0
  21. package/lib/vendor/blamejs/release-notes/v0.13.1.json +18 -0
  22. package/lib/vendor/blamejs/release-notes/v0.13.2.json +27 -0
  23. package/lib/vendor/blamejs/scripts/vendor-update.sh +11 -1
  24. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +3 -1
  25. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +101 -0
  26. package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +61 -1
  27. package/lib/vendor/blamejs/test/layer-0-primitives/network-tsig.test.js +149 -0
  28. package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +2 -2
  29. package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +3 -3
  30. package/lib/vendor/blamejs/test/layer-0-primitives/worm.test.js +126 -0
  31. package/package.json +2 -2
  32. package/lib/vendor/blamejs/release-notes/v0.12.0.json +0 -64
  33. package/lib/vendor/blamejs/release-notes/v0.12.1.json +0 -32
  34. package/lib/vendor/blamejs/release-notes/v0.12.10.json +0 -65
  35. package/lib/vendor/blamejs/release-notes/v0.12.11.json +0 -39
  36. package/lib/vendor/blamejs/release-notes/v0.12.12.json +0 -48
  37. package/lib/vendor/blamejs/release-notes/v0.12.13.json +0 -31
  38. package/lib/vendor/blamejs/release-notes/v0.12.14.json +0 -18
  39. package/lib/vendor/blamejs/release-notes/v0.12.15.json +0 -27
  40. package/lib/vendor/blamejs/release-notes/v0.12.16.json +0 -18
  41. package/lib/vendor/blamejs/release-notes/v0.12.17.json +0 -22
  42. package/lib/vendor/blamejs/release-notes/v0.12.18.json +0 -22
  43. package/lib/vendor/blamejs/release-notes/v0.12.19.json +0 -22
  44. package/lib/vendor/blamejs/release-notes/v0.12.2.json +0 -45
  45. package/lib/vendor/blamejs/release-notes/v0.12.20.json +0 -18
  46. package/lib/vendor/blamejs/release-notes/v0.12.21.json +0 -27
  47. package/lib/vendor/blamejs/release-notes/v0.12.22.json +0 -18
  48. package/lib/vendor/blamejs/release-notes/v0.12.23.json +0 -18
  49. package/lib/vendor/blamejs/release-notes/v0.12.24.json +0 -18
  50. package/lib/vendor/blamejs/release-notes/v0.12.25.json +0 -18
  51. package/lib/vendor/blamejs/release-notes/v0.12.26.json +0 -30
  52. package/lib/vendor/blamejs/release-notes/v0.12.27.json +0 -26
  53. package/lib/vendor/blamejs/release-notes/v0.12.28.json +0 -26
  54. package/lib/vendor/blamejs/release-notes/v0.12.29.json +0 -31
  55. package/lib/vendor/blamejs/release-notes/v0.12.3.json +0 -23
  56. package/lib/vendor/blamejs/release-notes/v0.12.30.json +0 -18
  57. package/lib/vendor/blamejs/release-notes/v0.12.31.json +0 -18
  58. package/lib/vendor/blamejs/release-notes/v0.12.32.json +0 -27
  59. package/lib/vendor/blamejs/release-notes/v0.12.33.json +0 -31
  60. package/lib/vendor/blamejs/release-notes/v0.12.34.json +0 -18
  61. package/lib/vendor/blamejs/release-notes/v0.12.35.json +0 -22
  62. package/lib/vendor/blamejs/release-notes/v0.12.36.json +0 -18
  63. package/lib/vendor/blamejs/release-notes/v0.12.37.json +0 -27
  64. package/lib/vendor/blamejs/release-notes/v0.12.38.json +0 -18
  65. package/lib/vendor/blamejs/release-notes/v0.12.39.json +0 -18
  66. package/lib/vendor/blamejs/release-notes/v0.12.4.json +0 -19
  67. package/lib/vendor/blamejs/release-notes/v0.12.40.json +0 -18
  68. package/lib/vendor/blamejs/release-notes/v0.12.41.json +0 -18
  69. package/lib/vendor/blamejs/release-notes/v0.12.42.json +0 -18
  70. package/lib/vendor/blamejs/release-notes/v0.12.43.json +0 -18
  71. package/lib/vendor/blamejs/release-notes/v0.12.44.json +0 -18
  72. package/lib/vendor/blamejs/release-notes/v0.12.45.json +0 -18
  73. package/lib/vendor/blamejs/release-notes/v0.12.46.json +0 -18
  74. package/lib/vendor/blamejs/release-notes/v0.12.47.json +0 -18
  75. package/lib/vendor/blamejs/release-notes/v0.12.48.json +0 -22
  76. package/lib/vendor/blamejs/release-notes/v0.12.49.json +0 -31
  77. package/lib/vendor/blamejs/release-notes/v0.12.5.json +0 -40
  78. package/lib/vendor/blamejs/release-notes/v0.12.50.json +0 -18
  79. package/lib/vendor/blamejs/release-notes/v0.12.51.json +0 -18
  80. package/lib/vendor/blamejs/release-notes/v0.12.52.json +0 -18
  81. package/lib/vendor/blamejs/release-notes/v0.12.53.json +0 -18
  82. package/lib/vendor/blamejs/release-notes/v0.12.54.json +0 -18
  83. package/lib/vendor/blamejs/release-notes/v0.12.55.json +0 -18
  84. package/lib/vendor/blamejs/release-notes/v0.12.56.json +0 -27
  85. package/lib/vendor/blamejs/release-notes/v0.12.57.json +0 -18
  86. package/lib/vendor/blamejs/release-notes/v0.12.58.json +0 -22
  87. package/lib/vendor/blamejs/release-notes/v0.12.6.json +0 -45
  88. package/lib/vendor/blamejs/release-notes/v0.12.60.json +0 -18
  89. package/lib/vendor/blamejs/release-notes/v0.12.61.json +0 -18
  90. package/lib/vendor/blamejs/release-notes/v0.12.62.json +0 -18
  91. package/lib/vendor/blamejs/release-notes/v0.12.63.json +0 -27
  92. package/lib/vendor/blamejs/release-notes/v0.12.64.json +0 -18
  93. package/lib/vendor/blamejs/release-notes/v0.12.65.json +0 -27
  94. package/lib/vendor/blamejs/release-notes/v0.12.66.json +0 -18
  95. package/lib/vendor/blamejs/release-notes/v0.12.68.json +0 -27
  96. package/lib/vendor/blamejs/release-notes/v0.12.69.json +0 -27
  97. package/lib/vendor/blamejs/release-notes/v0.12.7.json +0 -86
  98. package/lib/vendor/blamejs/release-notes/v0.12.8.json +0 -81
  99. package/lib/vendor/blamejs/release-notes/v0.12.9.json +0 -61
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.45",
4
- "date": "2026-05-25",
5
- "headline": "`b.cose` adds detached-payload sign/verify + `b.cose.importKey` (COSE_Key)",
6
- "summary": "Two RFC 9052 / 9053 completions to the COSE substrate, both useable today and the prerequisites for mdoc device authentication and C2PA claim verification. Detached payloads (RFC 9052 §4.1): b.cose.sign with detached:true emits a COSE_Sign1 whose payload slot is nil — the signature still covers the payload, and the caller transmits it out of band; b.cose.verify takes the payload back as opts.externalPayload and binds it into the Sig_structure. A detached token verified without externalPayload is refused, and supplying externalPayload for an attached token is refused as ambiguous. COSE_Key import (RFC 9052 §7): b.cose.importKey turns a COSE_Key CBOR map into a node:crypto public KeyObject for b.cose.verify, accepting EC2 (P-256 / P-384 / P-521) and OKP (Ed25519) with the curve allowlisted so an unexpected key type is refused. No new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "Detached COSE_Sign1 payloads + `b.cose.importKey(coseKey)`",
13
- "body": "`b.cose.sign(payload, { detached: true })` emits a nil-payload COSE_Sign1 (the signature covers the payload regardless); `b.cose.verify(coseSign1, { externalPayload })` reconstructs the Sig_structure from the supplied payload, refusing a detached token with no `externalPayload` (`cose/detached-no-payload`) and refusing `externalPayload` on an attached token (`cose/payload-ambiguous`). `b.cose.importKey(coseKey)` maps a COSE_Key map (`kty` 2/EC2 with `crv` P-256/384/521, or `kty` 1/OKP with Ed25519) to a public KeyObject, allowlisting `kty`/`crv` and refusing anything else with `cose/unsupported-key` — the verification key embedded in an mdoc MSO or COSE_Key header is consumed this way."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.46",
4
- "date": "2026-05-25",
5
- "headline": "`b.mdoc.verifyDeviceAuth` — ISO 18013-5 mdoc device authentication",
6
- "summary": "Completes mdoc verification with the holder-binding half (ISO 18013-5 §9.1.3, signature variant). verifyIssuerSigned proves the data is issuer-signed; verifyDeviceAuth proves the presenter controls the device key the issuer bound into the MSO, so a captured issuer-signed document cannot be replayed by anyone else. The device's COSE_Sign1 (deviceSigned.deviceAuth.deviceSignature) is verified over the detached DeviceAuthentication structure [\"DeviceAuthentication\", SessionTranscript, DocType, DeviceNameSpacesBytes] using the device key from verifyIssuerSigned().deviceKey (now surfaced) and the operator-supplied SessionTranscript that binds the proof to this exact exchange (the presentation protocol — e.g. OpenID4VP — defines the transcript). Composes the v0.12.45 b.cose detached-payload verify + importKey. The MAC variant (deviceMac / COSE_Mac0, used in proximity flows with a reader ephemeral key) is deferred and refused with mdoc/device-mac-unsupported. No new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.mdoc.verifyDeviceAuth(opts)` + `deviceKey` on the verifyIssuerSigned result",
13
- "body": "`verifyDeviceAuth({ deviceKey, deviceSigned, docType, sessionTranscript, algorithms })` imports the device key (a COSE_Key via `b.cose.importKey`, or a KeyObject), reconstructs the detached `DeviceAuthentication` payload, and verifies the `deviceSignature` COSE_Sign1 against the mandatory algorithm allowlist — a mismatched `sessionTranscript` or `docType` fails the signature. `verifyIssuerSigned` now returns `deviceKey` (the MSO `deviceKeyInfo.deviceKey`) so the two checks chain. The MAC variant (`deviceMac`) is refused with `mdoc/device-mac-unsupported` pending COSE_Mac0 + reader-key support."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.47",
4
- "date": "2026-05-25",
5
- "headline": "`b.cose.mac0` / `b.cose.macVerify0` — COSE_Mac0 (RFC 9052 §6.2)",
6
- "summary": "Completes the COSE message-type set (COSE_Sign1 / COSE_Encrypt0 / COSE_Mac0) with single shared-key MACs. b.cose.mac0 produces a tagged COSE_Mac0 over a payload using HMAC-SHA-256/384/512 (the COSE-standard MAC algorithms; HMAC is symmetric, so its post-quantum strength is preserved). b.cose.macVerify0 recomputes the tag over the MAC_structure and compares it in constant time, with a mandatory algorithm allowlist. Use when both parties hold a shared key — e.g. an ECDH-derived key — and a non-repudiable signature is not wanted; detached payloads are supported (the proximity mdoc device-MAC variant and MACed CWTs are the consumers). Composes b.cbor + the framework's constant-time compare; no new runtime dependency.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.cose.mac0(payload, opts)` / `b.cose.macVerify0(coseMac0, opts)`",
13
- "body": "`mac0` emits a tagged COSE_Mac0 (tag 17) with `alg` (`HMAC-256/256` | `HMAC-384/384` | `HMAC-512/512`) in the protected header and the HMAC tag computed over the MAC_structure `[\"MAC0\", protected, external_aad, payload]`; `detached: true` emits a nil payload. `macVerify0` reads the algorithm from the protected header (must be in the required `opts.algorithms` allowlist), recomputes the tag, and compares it constant-time — a wrong key, tampered tag, or `external_aad` mismatch is refused with `cose/bad-tag`; a detached payload is supplied via `opts.externalPayload`. `external_aad` binds context into the tag."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,22 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.48",
4
- "date": "2026-05-25",
5
- "headline": "`b.network.dns.dnssec` — local DNSSEC signature verification (RFC 4035)",
6
- "summary": "Verify a DNS answer's RRSIG signature yourself instead of trusting the upstream resolver's AD bit. b.network.dns.dnssec.verifyRrset reconstructs the RFC 4034 §3.1.8.1 signed data — the RRSIG RDATA without the signature, followed by the RRset in canonical form (owner names lowercased, RRs ordered by canonical RDATA, the RRSIG's Original TTL) — and checks the signature against the DNSKEY, enforcing the inception / expiration window. Supports RSA/SHA-256 (alg 8), ECDSA P-256/SHA-256 (13), ECDSA P-384/SHA-384 (14), and Ed25519 (15) — the modern deployed set. verifyDs checks a delegation-signer digest against a DNSKEY (SHA-256 / SHA-384) and keyTag computes the RFC 4034 Appendix B key tag. The verification core is what a chain-walker composes; it defends against a compromised or on-path resolver that lies about authentication.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.network.dns.dnssec.verifyRrset(opts)`",
13
- "body": "Verifies an RRSIG over a canonicalised RRset against a DNSKEY. `opts` carries the owner `name`, the RR `type`, the wire-format `rdatas`, the parsed `rrsig` (algorithm / labels / originalTtl / inception / expiration / keyTag / signerName / signature), and the `dnskey` (algorithm + raw public key). The signed data is rebuilt per RFC 4034 §3.1.8.1: the RRSIG prefix (type covered | algorithm | labels | original TTL | expiration | inception | key tag | canonical signer name) followed by each RR in canonical form (lowercased owner | type | class | original TTL | rdlen | rdata), sorted by `Buffer.compare` on the RDATA. The validity window is enforced against `opts.at` (defaults to now; an invalid Date is refused, not treated as now). An RRSIG whose algorithm disagrees with the DNSKEY is refused before any key is built. RR types that embed domain names in their RDATA (NS, CNAME, SOA, MX, SRV, …) need RDATA-internal name-lowercasing this version does not perform, so they are refused with `dnssec/uncanonicalizable-type` rather than mis-validated; the security-critical DNSKEY / DS and the name-free address / text types (A, AAAA, TXT, CAA, TLSA, …) are fully supported."
14
- },
15
- {
16
- "title": "`b.network.dns.dnssec.verifyDs(opts)` / `b.network.dns.dnssec.keyTag(dnskeyRdata)`",
17
- "body": "`verifyDs` confirms a delegation-signer record matches a DNSKEY: it checks the key tag, then compares the DS digest (SHA-256 type 2 / SHA-384 type 4) against the digest computed over the canonical owner name and the DNSKEY RDATA, constant-time. `keyTag` computes the RFC 4034 Appendix B 16-bit key tag from a DNSKEY's full RDATA — the identifier an RRSIG or DS uses to select the signing key. Together with `verifyRrset` these are the per-RRset building blocks a recursive chain-walk (root → TLD → zone) composes; the chain-walk itself, NSEC / NSEC3 denial-of-existence, and the bundled IANA root trust anchor are not part of this core."
18
- }
19
- ]
20
- }
21
- ]
22
- }
@@ -1,31 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.49",
4
- "date": "2026-05-25",
5
- "headline": "`b.network.dns.dnssec.verifyDenial` — NSEC / NSEC3 denial-of-existence",
6
- "summary": "Prove a DNS name does not exist, or has no records of a given type, from the signed NSEC (RFC 4034 §4) or NSEC3 (RFC 5155) records a server returns. This is the other half of local DNSSEC verification: verifyRrset proves a positive answer, verifyDenial proves a negative — so a resolver client can confirm an NXDOMAIN / NODATA itself instead of trusting the upstream resolver. NSEC3 proofs run the closest-encloser / next-closer / covering-range logic over iterated-SHA-1 hashes, with the iteration count capped (default 500) to bound the work an attacker can force, and an Opt-Out NXDOMAIN refused unless explicitly accepted (opt-out only proves 'no signed records', not non-existence). The companion b.network.dns.dnssec.nsec3Hash computes the RFC 5155 §5 hash directly. NSEC verifyRrset support is also enabled: per RFC 6840 §5.1 the NSEC Next Domain Name is not downcased, so its RDATA is verbatim-canonical.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.network.dns.dnssec.verifyDenial(opts)`",
13
- "body": "Proves NXDOMAIN or NODATA from already-verified NSEC / NSEC3 records (supply one of `opts.nsec3` or `opts.nsec`). Like `verifyDs`, it checks the denial RELATION — closest-encloser matching, covering ranges, and type-bitmap absence — not the record signatures, which the caller verifies with `verifyRrset` first. NSEC3 supports name-error proofs (matching closest encloser + covered next-closer + covered wildcard), NODATA (matching record with the type and CNAME absent from the bitmap), Opt-Out DS NODATA, and wildcard NODATA. The iterated-SHA-1 count is capped by `opts.maxIterations` (default 500); an NXDOMAIN proof that depends on an Opt-Out NSEC3 is refused unless `opts.allowOptOut` is set. NSEC supports covering-name NXDOMAIN (with the source-of-synthesis wildcard) and matching-name NODATA. Verified end-to-end against a live iana.org NXDOMAIN proof."
14
- },
15
- {
16
- "title": "`b.network.dns.dnssec.nsec3Hash(name, opts)`",
17
- "body": "Computes the RFC 5155 §5 NSEC3 hash of a name — iterated SHA-1 over the canonical (lowercased, root-terminated) wire form with the zone salt. The base32hex encoding of the result is the NSEC3 owner label. SHA-1 is the only hash IANA registers for NSEC3, so this is a wire-protocol constant rather than a cryptographic default. Useful for checking an owner label or analyzing a zone's hashing parameters."
18
- }
19
- ]
20
- },
21
- {
22
- "heading": "Changed",
23
- "items": [
24
- {
25
- "title": "`verifyRrset` now accepts NSEC and NSEC3 RRsets",
26
- "body": "NSEC (type 47) and NSEC3 (type 50) are no longer refused as uncanonicalizable: NSEC3's next-owner is a hash, and per RFC 6840 §5.1 the NSEC Next Domain Name field is not downcased for DNSSEC canonical form, so both RDATAs are verbatim-canonical. This lets a caller verify the signatures on the records that `verifyDenial` then reasons over."
27
- }
28
- ]
29
- }
30
- ]
31
- }
@@ -1,40 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.5",
4
- "date": "2026-05-22",
5
- "headline": "`b.metrics` content-negotiates OpenMetrics 1.0 + auto-attaches trace exemplars on request histograms",
6
- "summary": "The `/metrics` scrape endpoint now serves `application/openmetrics-text; version=1.0.0; charset=utf-8` when the scraper requests it via the `Accept` header (Prometheus 2.x strict mode, OpenObservability tooling). Legacy scrapers still get `text/plain; version=0.0.4` — no operator with the default Prometheus client sees a content-type change. Separately, the framework's request-duration histogram middleware now auto-attaches the active sampled trace's `trace_id` + `span_id` as the OpenMetrics §6.2 exemplar on every bucket sample, so Grafana / Tempo / Jaeger scrapers can pivot from a slow-bucket histogram to the exact trace that produced the sample. The wiring is composition-only — `b.middleware.tracePropagate` populates `req.trace.{traceId,parentId,sampled}`, the metrics middleware reads it, no operator opt-in needed.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`Accept` content-negotiation on `b.metrics.expositionHandler()`",
13
- "body": "When the scraper's `Accept` header includes `application/openmetrics-text`, the handler renders the OpenMetrics 1.0 wire format (`# UNIT` lines, `_total` suffix on counters, `# EOF` terminator, exemplar shape) and serves `application/openmetrics-text; version=1.0.0; charset=utf-8`. Otherwise serves Prometheus 0.0.4 `text/plain` as before. Operators relying on the legacy Prometheus content-type see no change."
14
- },
15
- {
16
- "title": "Auto-attached trace exemplars on request-duration histograms",
17
- "body": "When `b.middleware.spanHttpServer` populates `req.span.{traceId, spanId, sampled}` on the inbound request and the span is sampled, the framework's built-in `requestDuration` histogram middleware attaches `{ labels: { trace_id, span_id }, value: <duration>, timestamp: <unix-sec> }` as the OpenMetrics §6.2 exemplar on the corresponding bucket. The exemplar's `span_id` is the server-handling span, not the upstream `traceparent`'s parent-id, so the metric-to-trace pivot in Grafana / Tempo / Jaeger lands on the work the metric measured. Operators wiring `tracePropagate` without `spanHttpServer` fall back to `req.trace.spanId` when populated; the framework never invents a span_id from the upstream parent."
18
- }
19
- ]
20
- },
21
- {
22
- "heading": "Fixed",
23
- "items": [
24
- {
25
- "title": "Accept-header weighted negotiation (Codex P1)",
26
- "body": "The first pass treated any `Accept` header containing `application/openmetrics-text` as an unconditional OpenMetrics request — clients sending `Accept: text/plain;q=1.0, application/openmetrics-text;q=0.5` got OpenMetrics back instead of their preferred Prometheus 0.0.4. Fix: parse Accept via `b.requestHelpers.parseQualityList` and compare q-values for `application/openmetrics-text` vs `text/plain` (wildcards `*/*`, `application/*`, `text/*` honored). Defaults to Prometheus when both q-values are equal or zero (backward compatibility with the legacy default content-type)."
27
- },
28
- {
29
- "title": "Exemplar span_id sources the active server span, not the upstream parent (Codex P2)",
30
- "body": "The first pass used `req.trace.parentId` for the exemplar's `span_id` label — but `parentId` is the upstream caller's span (or empty for root requests), not the server-handling span. Fix: prefer `req.span.spanId` (set by `b.middleware.spanHttpServer`), falling back to `req.trace.spanId` for operators wiring `tracePropagate` without `spanHttpServer`. Never synthesises a span_id from `parentId`."
31
- }
32
- ]
33
- }
34
- ],
35
- "references": [
36
- { "label": "OpenMetrics 1.0 §1.2 (content negotiation)", "url": "https://prometheus.io/docs/specs/om/open_metrics_spec/" },
37
- { "label": "OpenMetrics 1.0 §6.2 (exemplars)", "url": "https://prometheus.io/docs/specs/om/open_metrics_spec/" },
38
- { "label": "W3C Trace Context (traceparent header)", "url": "https://www.w3.org/TR/trace-context/" }
39
- ]
40
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.50",
4
- "date": "2026-05-25",
5
- "headline": "`b.network.dns.dnssec.verifyChain` — validate a DNSSEC delegation chain to a pinned root anchor",
6
- "summary": "Completes local DNSSEC verification: validate a full delegation chain from the root down to a zone against a pinned trust anchor (RFC 4035 §5), instead of trusting any single resolver. For each link, the zone's DNSKEY RRset must be self-signed by one of its keys, and that key must be vouched for either by a pinned anchor (at the root) or by a DS record served + signed by the already-trusted parent — so trust flows root → TLD → zone with no gap. The IANA root KSKs (KSK-2017 tag 20326, KSK-2024 tag 38696) ship as the default anchors; override with opts.trustAnchors for a private root. verifyChain returns the leaf zone's trusted DNSKEY set, which you then hand to verifyRrset / verifyDenial for the actual answer. Composes verifyRrset + verifyDs + the key tag; verified end-to-end against a live root→org chain.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.network.dns.dnssec.verifyChain(opts)`",
13
- "body": "Walks an ordered, root-first list of `links` ({ zone, dnskeys, dnskeyRrsig, dsRdatas?, dsRrsig? }). At each link it verifies the DNSKEY RRset's self-signature (composing `verifyRrset`), then establishes trust in the signing key: at the root by matching a pinned anchor's DS digest (`verifyDs`), at every delegation by verifying the parent-served DS RRset's signature with the already-trusted parent key and confirming the signing KSK matches one of those DS records. Returns `{ ok, zone, keys, path }` with the leaf zone's trusted DNSKEY set. Refuses a root key that matches no anchor (`dnssec/chain-anchor-mismatch`), a KSK that matches no parent DS (`dnssec/chain-ds-mismatch`), and a missing parent key (`dnssec/chain-no-parent-key`). The default `DEFAULT_ROOT_ANCHORS` are the published IANA root KSK DS records; `opts.trustAnchors` overrides them for a private or test root."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.51",
4
- "date": "2026-05-25",
5
- "headline": "`b.network.dns.dane.matchCertificate` — DANE / TLSA certificate matching (RFC 6698 / 7671)",
6
- "summary": "Pin a service's certificate through DNS instead of a public CA. matchCertificate checks a server certificate against a set of TLSA records: the selected data — the full certificate (selector 0) or its subjectPublicKeyInfo (selector 1) — is hashed per the matching type (exact / SHA-256 / SHA-512) and compared in constant time to the record's association data. For a DANE-EE (usage 3) record a match is self-authenticating — the TLSA pins the key, so no public-CA path is needed (the common SMTP-DANE case, RFC 7672); for the PKIX usages a match is reported as necessary-but-not-sufficient so the caller still runs PKIX. This is the payoff of the DNSSEC verifier: verify the TLSA RRset with b.network.dns.dnssec, then match the certificate. Verified against a live DNSSEC-signed TLSA record and the matching server certificate.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.network.dns.dane.matchCertificate(opts)`",
13
- "body": "Matches a leaf certificate (and optional `chain`) against a TLSA RRset (`{ usage, selector, matchingType, data }`). Selector 0 hashes the full certificate DER, selector 1 the subjectPublicKeyInfo; matching type 0 is an exact comparison, 1 SHA-256, 2 SHA-512 (SHA-1 and any other type are refused, not guessed). End-entity usages (PKIX-EE 1, DANE-EE 3) match the leaf; trust-anchor usages (PKIX-TA 0, DANE-TA 2) match the leaf or any supplied chain certificate. Returns `{ ok, matched, daneAuthenticated, trustAnchorMatch, pkixRequired, matchedCertIndex }` — `daneAuthenticated` is true only for a DANE-EE match (the key is pinned, no CA needed); `pkixRequired` flags the PKIX usages. Throws `dane/no-match` when nothing matches, and refuses unknown usage / selector / matching values and unparseable certificates. Verify the TLSA RRset with `b.network.dns.dnssec` first — an unauthenticated TLSA record proves nothing."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.52",
4
- "date": "2026-05-25",
5
- "headline": "`b.privacyPass` — Privacy Pass origin-side token verification (RFC 9577 / 9578)",
6
- "summary": "Anonymous, publicly verifiable authorization: an origin issues a WWW-Authenticate: PrivateToken challenge and verifies a presented token cryptographically, without learning who the client is and without a callback to the issuer. b.privacyPass implements the publicly verifiable token type 0x0002 (Blind RSA, 2048-bit): the token's authenticator is an RSA Blind Signature (RFC 9474) checked as RSASSA-PSS (SHA-384, 48-byte salt) over token_input = token_type ‖ nonce ‖ challenge_digest ‖ token_key_id, using only the issuer's public key. The token is bound to that key (token_key_id) and, optionally, to the challenge it answers, so a token minted for another origin is refused. Blind RSA is the algorithm Privacy Pass defines on the wire — like the DNSSEC / DANE verifiers it validates an external protocol's signatures rather than introducing classical crypto as a framework default. Verified against the RFC 9578 §8.2 test vector.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.privacyPass.verifyToken(opts)` / `parseToken` / `buildChallenge`",
13
- "body": "`buildChallenge` builds a TokenChallenge (RFC 9577 §2.1) and the matching `WWW-Authenticate: PrivateToken challenge=…, token-key=…` header an origin returns to request a token, scoped to an issuer (and optionally an origin and a 32-byte redemption context). `parseToken` splits a token into its fields (type / nonce / challenge_digest / token_key_id / authenticator). `verifyToken` verifies a type 0x0002 (Blind RSA) token: it confirms the token's `token_key_id` is the SHA-256 of the supplied issuer public key, optionally that its `challenge_digest` matches `opts.challenge`, and that the authenticator is a valid RSASSA-PSS signature over the token input. Refuses unknown / privately verifiable token types (the VOPRF type 0x0001 needs the issuer secret and is an issuer-side operation), key-id and challenge mismatches, and tampered authenticators. Marked experimental while the issuance protocols see deployment."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.53",
4
- "date": "2026-05-25",
5
- "headline": "`b.contentDigest` — HTTP Content-Digest / Repr-Digest fields (RFC 9530)",
6
- "summary": "Emit and verify the Content-Digest / Repr-Digest HTTP fields so a recipient can detect a corrupted or tampered message body. b.contentDigest.create builds the RFC 8941 dictionary value (sha-256=:base64:, sha-512=:base64:) over a body; b.contentDigest.verify recomputes each modern digest over the body and compares it in constant time. Only SHA-256 and SHA-512 are computed — the legacy algorithms RFC 9530 §6 marks insecure (MD5, SHA-1, the unix checksums) are ignored on verify, and a field carrying no modern digest is refused, so an attacker cannot downgrade integrity to an MD5-only digest. Content-Digest is the integrity companion to HTTP Message Signatures (b.httpSig, RFC 9421): sign the digest rather than the whole body. Verified against the RFC 9530 Appendix D worked examples.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.contentDigest.create(body, opts?)` / `b.contentDigest.verify(fieldValue, body, opts?)`",
13
- "body": "`create` returns a Content-Digest / Repr-Digest field value over the body — SHA-256 by default, or any subset of `[\"sha-256\",\"sha-512\"]` via `opts.algorithms` — and refuses insecure or unknown algorithms. `verify` parses the field, recomputes each SHA-256 / SHA-512 entry over the body, and compares constant-time; it throws `content-digest/mismatch` on any mismatch, ignores legacy / unknown entries, throws `content-digest/no-modern-digest` if the field has no SHA-256 / SHA-512 entry at all, and honours `opts.required` to force specific algorithms to be present and match. Composes the framework's structured-field helpers and constant-time compare; Repr-Digest is the same machinery over the selected representation (RFC 9110)."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.54",
4
- "date": "2026-05-25",
5
- "headline": "`b.structuredFields.parse` / `serialize` — full RFC 8941 Structured Fields codec",
6
- "summary": "The structured-fields module gains a complete RFC 8941 parser and serializer alongside its existing quote-aware helpers. b.structuredFields.parse reads an Item, List, or Dictionary into a typed value model — items are { value, params }, lists are arrays of items / inner lists, dictionaries are Maps — with Tokens and byte sequences returned as distinct SfToken / SfByteSequence instances. It enforces the grammar strictly: integer and decimal digit caps, printable-ASCII strings, canonical base64 byte sequences, valid token and key grammar, and no trailing characters. b.structuredFields.serialize is the exact inverse. This is the real parser the framework's Content-Digest, Client Hints, Web Push, and HTTP Message Signature surfaces can build on instead of open-coding each field. Validated against the official httpwg structured-field-tests conformance vectors.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.structuredFields.parse(input, type, opts?)` / `serialize(value, type, opts?)` / `Token` / `ByteSequence`",
13
- "body": "`parse` accepts `type` of `\"item\"`, `\"list\"`, or `\"dictionary\"` and returns the value model (items as `{ value, params }` with a `Map` of parameters; lists as arrays of items or inner lists; dictionaries as `Map`s). Bare items are JS numbers (Integer / Decimal), strings, booleans, `SfToken`, or `SfByteSequence`. Malformed input is rejected — out-of-range integers, over-long decimals, non-printable string bytes, non-canonical base64, invalid tokens / keys, and any trailing characters — and `opts.ErrorClass` yields a typed error. `serialize` is the inverse, rounding decimals to three fractional digits and refusing values outside the RFC's ranges or grammar. `b.structuredFields.Token` and `b.structuredFields.ByteSequence` wrap those bare-item types for serialization. The existing `splitTopLevel` / `refuseControlBytes` / `unquoteSfString` helpers are unchanged."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.55",
4
- "date": "2026-05-25",
5
- "headline": "`b.structuredFields` — RFC 9651 Date and Display String types",
6
- "summary": "Brings the Structured Fields codec up to RFC 9651, which obsoletes RFC 8941 by adding two bare-item types. A Date (`@1659578233`) is an Integer number of seconds since the Unix epoch; a Display String (`%\"f%c3%bc%c3%bc\"`) is a Unicode string conveyed as percent-escaped UTF-8. parse returns them as distinct SfDate / SfDisplayString values, and serialize emits them canonically — a Date as `@` + integer, a Display String as `%\"`-wrapped lowercase-percent-escaped UTF-8 that escapes only what RFC 9651 requires. Parsing is strict: a Date rejects a decimal / out-of-range value, and a Display String rejects uppercase escapes, raw non-ASCII, bad hex, and invalid UTF-8. Validated against the official httpwg structured-field-tests date and display-string vectors.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "RFC 9651 Date (`@…`) and Display String (`%\"…\"`) in `b.structuredFields`",
13
- "body": "`parse` now reads the two RFC 9651 types: `@` + an Integer yields an `SfDate` (rejecting a decimal `@1.5`, an empty `@`, a sign-only `@-`, and out-of-range values), and `%\"…\"` yields an `SfDisplayString` (decoding lowercase `%XX` escapes as UTF-8, rejecting uppercase escapes, raw non-ASCII or control characters, malformed hex, and invalid UTF-8). `serialize` is the inverse — a Date as `@` + the integer, a Display String percent-escaping only non-printable / non-ASCII bytes plus `%` and `\"`. The new `b.structuredFields.Date` and `b.structuredFields.DisplayString` wrappers construct these values. The module now tracks RFC 9651 (which obsoletes RFC 8941); the existing Item / List / Dictionary parsing is unchanged."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.56",
4
- "date": "2026-05-25",
5
- "headline": "`b.canonicalJson` — RFC 8785 JSON Canonicalization Scheme, now a public primitive",
6
- "summary": "The deterministic JSON serializer the framework uses internally for audit-chain and config-drift fingerprints is now an operator-facing primitive, for signing your own JSON (custom credentials, receipts, deterministic request signing). b.canonicalJson.stringifyJcs is strict RFC 8785: keys sorted in UTF-16 code-unit order at every depth, numbers in the ECMAScript format JCS references, and types JCS does not define (BigInt / Buffer / Date / Map / Set / circular references) refused rather than silently lost. b.canonicalJson.stringify is a lenient variant that also serializes Buffers (hex), Dates (ISO-8601), and BigInts. Exposing it surfaced and fixed a latent ordering bug: the serializer built a sorted-key object and let JSON.stringify emit it, but V8 hoists integer-like keys (\"1\", \"10\") to the front — so canonical output was wrong for objects with integer-like string keys. Members are now written in sorted order directly. Validated against the official cyberphone/json-canonicalization conformance vectors.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.canonicalJson.stringifyJcs(value)` / `stringify(value, opts?)` / `sortKeys(obj)`",
13
- "body": "`stringifyJcs` produces strict RFC 8785 canonical JSON — the byte-for-byte stable form to hash or sign — with UTF-16 code-unit key sorting and ECMAScript number formatting, refusing BigInt / Buffer / Date / Map / Set / RegExp / Symbol / function / circular references. `stringify` is the lenient framework variant (Buffers → hex, Dates → ISO-8601, BigInts → decimal; `opts.bufferAs: \"reject\"` to forbid binary). `sortKeys` returns an object's own keys in the canonical UTF-16 ordering. These were framework-internal; they are now documented public API."
14
- }
15
- ]
16
- },
17
- {
18
- "heading": "Fixed",
19
- "items": [
20
- {
21
- "title": "Canonical JSON now emits integer-like keys in sorted order",
22
- "body": "The canonical serializer built a sorted-key object and serialized it with JSON.stringify, which hoists integer-like string keys (\"1\", \"10\") to the front per V8 own-property ordering — producing non-canonical output for objects containing such keys (a violation of RFC 8785 §3.2.3). Members are now written in sorted-key order directly. Real-world consumers (audit-chain, config-drift) use named fields and are unaffected; only objects with integer-like string keys change, and the new output is the correct canonical form."
23
- }
24
- ]
25
- }
26
- ]
27
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.57",
4
- "date": "2026-05-25",
5
- "headline": "`b.linkHeader` — RFC 8288 Web Linking (HTTP Link header) codec",
6
- "summary": "Parse and build the HTTP Link header (RFC 8288) — the standard way to convey resource relations, most visibly REST pagination (Link: <…?page=2>; rel=\"next\"). b.linkHeader.parse returns one { uri, rel, params } per link, splitting multiple links on top-level commas and unwrapping quoted parameter values via the framework's RFC 8941 structured-field helpers, so a comma inside a quoted title never fake-splits the list; rel is exposed as its array of space-separated relation types. b.linkHeader.serialize is the inverse, angle-bracketing the URI, emitting rel first, and double-quoting parameter values. Verified against the RFC 8288 §3.5 examples and GitHub-style pagination links.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.linkHeader.parse(headerValue)` / `b.linkHeader.serialize(links)`",
13
- "body": "`parse` reads an HTTP Link header into `[{ uri, rel, params }]` — `uri` is the angle-bracketed target, `rel` the array of space-separated relation types, `params` the remaining parameters with quoted values unwrapped (a repeated parameter keeps the first occurrence per RFC 8288 §3.4). It refuses a link without a bracketed URI, an unterminated URI or quoted parameter, control bytes, and oversized headers. `serialize` builds the header value from `{ uri, rel, params? }` objects (or a single one), angle-bracketing the URI and double-quoting parameter values. Composes the framework's structured-field splitter so quoting is handled consistently; pair with `b.pagination` to emit standard `rel=\"next\"` / `rel=\"prev\"` navigation."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,22 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.58",
4
- "date": "2026-05-25",
5
- "headline": "`b.jsonPointer` (RFC 6901) + `b.jsonPatch` (RFC 6902) — JSON Pointer + Patch",
6
- "summary": "Two related JSON primitives. b.jsonPointer.get references a value within a JSON document by RFC 6901 path (/foo/0/bar), handling the ~1 / ~0 escapes and array indices, and refusing pointers that do not resolve. b.jsonPatch.apply applies an RFC 6902 patch — add / remove / replace / move / copy / test — the standard HTTP PATCH payload (application/json-patch+json). It is atomic: operations run against a deep copy, so a failure at any step (an out-of-range index, a missing source, or a failed test) throws and leaves the input document untouched, and test compares values structurally. Verified against the official json-patch/json-patch-tests conformance suite (every enabled result and error case) plus the RFC 6901 §5 pointer examples.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.jsonPointer.get(doc, pointer)` / `b.jsonPointer.parse(pointer)`",
13
- "body": "Resolve an RFC 6901 JSON Pointer against a document — walking object keys and array indices, decoding `~1` → `/` and `~0` → `~`, and returning the whole document for the empty pointer. Throws `json-pointer/not-found` for a missing key, an out-of-range or non-numeric (leading-zero) array index, or descent into a primitive, and `json-pointer/bad-pointer` for a non-`/`-prefixed pointer. `parse` exposes the decoded reference tokens."
14
- },
15
- {
16
- "title": "`b.jsonPatch.apply(doc, operations)`",
17
- "body": "Apply an RFC 6902 patch and return the result. Supports `add` (insert / append with `-` for arrays, set for objects, whole-document for `\"\"`), `remove`, `replace` (overwrite an existing location), `move`, `copy`, and `test` (structural equality). Atomic — the patch runs on a deep copy, so the input `doc` is never mutated and any failure (unknown op, missing `path` / `value` / `from`, bad index, failed `test`, or moving a location into its own child) throws a typed error. Paths are RFC 6901 pointers resolved through `b.jsonPointer`; suitable for HTTP PATCH endpoints."
18
- }
19
- ]
20
- }
21
- ]
22
- }
@@ -1,45 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.6",
4
- "date": "2026-05-22",
5
- "headline": "`b.observability.otlpExporter` adds OTLP/protobuf-HTTP encoding (`opts.encoding: \"protobuf\"`)",
6
- "summary": "The OTLP trace exporter now speaks `application/x-protobuf` end-to-end. Operators with high-volume telemetry opt into binary encoding via `opts.encoding: \"protobuf\"` (`\"http/protobuf\"` is accepted as a spec-name alias). The protobuf wire format encodes the same `ExportTraceServiceRequest` envelope as the existing JSON path — `ResourceSpans` → `ScopeSpans` → `Span` → `Status` / `Event` / `KeyValue` / `AnyValue` per the opentelemetry-proto repo — but emits 30-50% smaller bodies than the JSON shape on real-world workloads and avoids the JSON-parse cost on the collector side. Default stays `\"json\"`; collectors that don't speak protobuf keep working unchanged. Composes the existing `lib/protobuf-encoder.js` infrastructure.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`opts.encoding: \"json\" | \"protobuf\"` on `b.observability.otlpExporter.create`",
13
- "body": "When `\"protobuf\"` (or the spec-name alias `\"http/protobuf\"`), the exporter encodes batches as binary OTLP `ExportTraceServiceRequest` bytes and POSTs with `Content-Type: application/x-protobuf`. The retry / queue / drop-counter / audit machinery is shared with the JSON path so operators get the same operational primitives across both encodings. Default stays `\"json\"` — existing collectors keep working without configuration changes."
14
- },
15
- {
16
- "title": "Full OTLP trace schema encoded via `lib/protobuf-encoder.js`",
17
- "body": "ResourceSpans / ScopeSpans / Span / Event / Status / KeyValue / AnyValue / ArrayValue are emitted per the opentelemetry-proto repo's field numbers + wire types. `trace_id` and `span_id` round-trip as fixed-length bytes (16 + 8 octets respectively). `start_time_unix_nano` / `end_time_unix_nano` use `fixed64` for the nanosecond precision the JSON path's number type lossily encoded. SpanKind enum mapping covers unspecified / internal / server / client / producer / consumer."
18
- },
19
- {
20
- "title": "`pb.int64` / `pb.sint64` — signed-integer varint shapes on `lib/protobuf-encoder.js`",
21
- "body": "Negative integer attribute values in OTLP `AnyValue` (e.g. retry-after offsets, signed metric deltas) emit as proto3 `int64` — wire-type 0 varint, 10-byte two's-complement reinterpret per the spec. `pb.sint64` adds ZigZag-encoded varint for cases where small negatives dominate. Both accept Number / BigInt / digit-string inputs with explicit `[-2^63, 2^63 - 1]` range validation."
22
- },
23
- {
24
- "title": "`pb.fixed64` accepts string-form uint64 values",
25
- "body": "OTLP/JSON encodes uint64 as a JSON string (per the proto3 JSON mapping) — the framework's tracer emits `start_time_unix_nano` / `end_time_unix_nano` as digit-string BigInt-to-string conversions so the JSON path stays lossless. `pb.fixed64` now accepts that same digit-string shape on the protobuf path so a single timestamp representation flows through both encodings without a separate coercion step. Refuses non-digit strings and silently-rounded Numbers above `Number.MAX_SAFE_INTEGER`."
26
- }
27
- ]
28
- },
29
- {
30
- "heading": "Security",
31
- "items": [
32
- {
33
- "title": "AnyValue recursion capped at 100 levels (CVE-2024-7254 / CVE-2025-4565 class)",
34
- "body": "Both protobufjs (CVE-2024-7254) and protobuf-python (CVE-2025-4565) shipped DoS-via-unbounded-nested-group decoding. The OTLP `AnyValue` type permits a nested `ArrayValue { repeated AnyValue values = 1 }` that an adversarial collector-response could exploit during a future receive path. The encoder caps `_anyValueToProto` recursion at 100 levels — beyond which it emits an empty AnyValue rather than continuing to descend. Today's framework only EMITS (never receives) OTLP — but the cap is in the right place when the receive path lands."
35
- }
36
- ]
37
- }
38
- ],
39
- "references": [
40
- { "label": "OTLP §3 — Body encodings (JSON + protobuf)", "url": "https://opentelemetry.io/docs/specs/otlp/" },
41
- { "label": "opentelemetry-proto repo — trace/v1/trace.proto", "url": "https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto" },
42
- { "label": "CVE-2024-7254 (protobufjs unbounded nesting DoS)", "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-7254" },
43
- { "label": "CVE-2025-4565 (protobuf-python unbounded nesting DoS)", "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-4565" }
44
- ]
45
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.60",
4
- "date": "2026-05-25",
5
- "headline": "`b.jsonMergePatch` — JSON Merge Patch (RFC 7396)",
6
- "summary": "The simpler companion to JSON Patch: b.jsonMergePatch.merge applies an RFC 7396 merge patch (application/merge-patch+json), the partial-document PATCH body. A member present in the patch replaces or — for nested objects — merges into the target, a member whose value is null removes that key, and a patch that is itself an array, scalar, or null replaces the target wholesale. The inputs are never mutated (the merge runs on a deep copy) and member keys are written as literal own properties, so a \"__proto__\" member cannot reach any prototype. Verified against every RFC 7396 Appendix A test case.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.jsonMergePatch.merge(target, patch)`",
13
- "body": "Applies a JSON Merge Patch to a target document and returns the result without mutating either input. When the patch is an object it overlays the target (a null member deletes the key, a nested object merges recursively, any other value replaces); when the patch is an array, scalar, or null it replaces the whole target. Member keys are set via `Object.defineProperty`, so a `\"__proto__\"` member becomes a literal own key rather than altering a prototype. Pairs with `b.jsonPatch` — merge patch reads like the resource you want, JSON Patch expresses precise operations (array reordering, literal-null values)."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.61",
4
- "date": "2026-05-26",
5
- "headline": "`b.jsonPath` — JSONPath query (RFC 9535)",
6
- "summary": "A full RFC 9535 JSONPath query evaluator, complementing the framework's JSONPath guards (which screen path strings). b.jsonPath.query(doc, path) compiles a path and returns the matched node values; b.jsonPath.paths returns their normalized locations. The complete surface is implemented: name / wildcard / index / slice selectors, descendant segments (..), filter selectors (?) with comparison and logical operators, relative (@) and absolute ($) embedded queries, and the five standard functions length / count / match / search / value — with the spec's well-typedness rules enforced at compile time so a malformed or ill-typed query is rejected rather than silently mis-evaluated. Descendant walks are node-capped to bound work on hostile input. Verified against all 703 cases of the official jsonpath-compliance-test-suite.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.jsonPath.query(doc, path)` / `b.jsonPath.paths(doc, path)`",
13
- "body": "`query` returns the array of node values selected by an RFC 9535 JSONPath; `paths` returns the normalized-path string of each match (e.g. `$['a'][1]['p']`). Supports every selector (name, wildcard `*`, index incl. negative, slice `start:end:step` incl. negative step, comma-separated selections), child and descendant (`..`) segments, and filter expressions with `==` / `!=` / `<` / `<=` / `>` / `>=`, `&&` / `||` / `!`, existence tests, and the standard functions. The well-typedness rules are checked when the path is compiled — a non-singular query used as a comparison operand, an ill-typed function argument, or a value-typed function used as a test all throw `json-path/invalid`. Pairs with `b.guardJsonPath`, which screens operator-supplied path strings before they reach the evaluator."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.62",
4
- "date": "2026-05-26",
5
- "headline": "`b.jtd` — JSON Type Definition validation (RFC 8927)",
6
- "summary": "Validate JSON against a JSON Type Definition schema (RFC 8927) — a small, portable, cross-implementation schema language, the interop-friendly companion to the framework's fluent b.safeSchema builder. b.jtd.validate(schema, instance) returns an array of { instancePath, schemaPath } errors (empty = valid); b.jtd.isValid is the boolean form. All eight schema forms are supported — empty, type, enum, elements, properties (with optional / additional properties and nullable), values, discriminator (with mapping), and ref (with definitions) — including the integer-range and RFC 3339 timestamp types. A malformed schema is rejected at compile time with jtd/bad-schema rather than silently mis-validating. Verified against the official json-typedef-spec suites: all 316 validation cases and all 49 invalid-schema cases.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.jtd.validate(schema, instance)` / `b.jtd.isValid(schema, instance)`",
13
- "body": "`validate` returns the RFC 8927 error list — each `{ instancePath, schemaPath }` naming the offending value and the broken schema rule — and `isValid` is the boolean convenience form. Supports every JTD form and type: the numeric types enforce their exact ranges (int8 … uint32, float32 / float64), `timestamp` requires an RFC 3339 date-time, `properties` honours `optionalProperties` / `additionalProperties` / `nullable`, and `discriminator` selects a `mapping` schema by a tag property. The schema is checked for well-formedness before validation, so unknown keywords, multiple forms, bad refs, or a discriminator over a non-properties mapping all throw `jtd/bad-schema`. Use JTD for schemas you share across implementations or generate code from; use `b.safeSchema` for in-process fluent validation."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.63",
4
- "date": "2026-05-25",
5
- "headline": "`b.cloudEvents` gains the JSON event format, batch, and the HTTP binding",
6
- "summary": "b.cloudEvents grows beyond wrap / parse into a full CloudEvents 1.0.2 surface. b.cloudEvents.validate / isValid check an envelope against the spec without throwing (the non-throwing companion to parse). toJSON / fromJSON serialize and parse the JSON event format, and toJSONBatch / fromJSONBatch handle the JSON batch format; untrusted bodies parse through the framework's bounded, prototype-pollution-safe JSON reader. The new http.* binding speaks both content modes the spec defines — binary mode spreads context attributes across percent-encoded ce-* headers with the data in the body, structured mode carries the whole event as application/cloudevents+json — plus the batch mode, and http.decode auto-detects the incoming mode from Content-Type exactly as a conformant receiver does. Verified against the spec's normative example events.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.cloudEvents` JSON event format, batch, and HTTP binding",
13
- "body": "`validate` / `isValid` report spec violations without throwing (the non-throwing companion to `parse`). `toJSON` / `fromJSON` and `toJSONBatch` / `fromJSONBatch` serialize and parse the JSON event and batch formats over the existing envelope shape. `http.encodeBinary` / `http.encodeStructured` / `http.encodeBatch` render the three HTTP content modes — binary spreads attributes across percent-encoded `ce-*` headers, structured and batched carry the event(s) as `application/cloudevents+json` / `application/cloudevents-batch+json` — and `http.decode` parses a request back into an envelope (or array) by auto-detecting the mode from `Content-Type`. `b.jtd` or `b.safeSchema` still validate the event's `data` payload."
14
- }
15
- ]
16
- },
17
- {
18
- "heading": "Fixed",
19
- "items": [
20
- {
21
- "title": "`b.csp.build` accepts `fenced-frame-src` and `webrtc`",
22
- "body": "The CSP3 `fenced-frame-src` directive — which the default security-headers policy emits to block `<fencedframe>` embeds — was missing from the builder's recognized-directive set, so the default policy could not round-trip through `b.csp.build` (it threw `csp/unknown-directive`). Both `fenced-frame-src` and the CSP3 `webrtc` directive are now recognized."
23
- }
24
- ]
25
- }
26
- ]
27
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.64",
4
- "date": "2026-05-25",
5
- "headline": "`b.jsonSchema` — JSON Schema 2020-12 validation",
6
- "summary": "Validate JSON against a JSON Schema 2020-12 document — the dialect OpenAPI 3.1 adopted and the most widely implemented schema language. b.jsonSchema.compile(schema) returns a reusable validator; b.jsonSchema.validate(schema, instance) compiles and runs in one call, returning { valid, errors } where each error names the failing instance location, keyword, and schema path. The full 2020-12 vocabulary is supported: every applicator (allOf / anyOf / oneOf / not / if-then-else, properties / patternProperties / additionalProperties / prefixItems / items / contains), the annotation-aware unevaluatedProperties / unevaluatedItems, every assertion keyword, and reference resolution ($ref / $anchor / $dynamicRef / $dynamicAnchor / $defs / $id base URIs). format is an annotation by default (opt in to assertion with assertFormat). External references resolve through an operator-supplied schema map — never a network fetch. Verified against the official JSON-Schema-Test-Suite (1292 of 1295 draft2020-12 cases; the remainder need the bundled dialect metaschema or $vocabulary selection, both opt-in). This is the standards-track counterpart to the fluent b.safeSchema builder and the portable b.jtd.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.jsonSchema` — JSON Schema 2020-12",
13
- "body": "`compile(schema, opts)` returns `{ validate, isValid }`; `validate(schema, instance, opts)` and `isValid(schema, instance, opts)` compile and run in one call. `validate` returns `{ valid, errors }`, each error a `{ instancePath, keyword, schemaPath, message }`. The full 2020-12 vocabulary is implemented — applicators, annotation-aware `unevaluatedProperties` / `unevaluatedItems`, every assertion keyword, and `$ref` / `$anchor` / `$dynamicRef` / `$dynamicAnchor` / `$defs` / `$id` resolution. `format` is an annotation by default (`assertFormat: true` to assert). External references resolve through `opts.schemas` (a URI→schema map), never a network fetch. Reach for it when the schema is an existing JSON Schema document (an API contract, OpenAPI component, or config schema); `b.safeSchema` remains the fluent in-process builder and `b.jtd` the portable codegen-friendly option. Validating a schema document against the dialect metaschema requires supplying that metaschema via `opts.schemas`, and `$vocabulary`-based keyword selection is not honored (every standard keyword always asserts)."
14
- }
15
- ]
16
- }
17
- ]
18
- }
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.65",
4
- "date": "2026-05-26",
5
- "headline": "`b.base32` — RFC 4648 Base32 encode / decode",
6
- "summary": "Encode and decode RFC 4648 Base32 — the case-insensitive alphabet behind TOTP / 2FA secrets, DNSSEC NSEC3 hashes, and human-transcribable identifiers. Both RFC 4648 variants are supported: the standard alphabet (the default) and the extended-hex alphabet. b.base32.encode pads to an 8-character boundary by default (pass padding: false for the bare form TOTP key URIs use); b.base32.decode is strict by default but accepts the real-world shapes humans produce — lower-case, embedded spaces and dashes, missing padding — under loose: true. Verified against the RFC 4648 §10 test vectors for both alphabets. The TOTP primitive now composes this codec instead of carrying its own Base32 implementation.",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.base32.encode` / `b.base32.decode`",
13
- "body": "RFC 4648 Base32 codec. `encode(buf, opts)` takes a Buffer or Uint8Array and returns a Base32 string, padded to an 8-character boundary unless `padding: false`; `decode(str, opts)` returns a Buffer. The `variant` option selects the standard (`\"rfc4648\"`, default) or extended-hex (`\"rfc4648-hex\"`) alphabet. Decoding is strict by default — any character outside the alphabet throws `Base32Error` — and `loose: true` up-cases the input and ignores embedded spaces, dashes, and missing padding, which is how copied TOTP keys and hand-typed codes arrive."
14
- }
15
- ]
16
- },
17
- {
18
- "heading": "Changed",
19
- "items": [
20
- {
21
- "title": "TOTP composes `b.base32`",
22
- "body": "`b.auth.totp` now encodes and decodes its secrets through `b.base32` rather than a private Base32 implementation. Behavior is unchanged — secrets are still emitted unpadded and parsed leniently (case-insensitive, ignoring spaces and dashes)."
23
- }
24
- ]
25
- }
26
- ]
27
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "../scripts/release-notes-schema.json",
3
- "version": "0.12.66",
4
- "date": "2026-05-26",
5
- "headline": "`b.uriTemplate` — RFC 6570 URI Template expansion",
6
- "summary": "Expand RFC 6570 URI Templates — the {var} syntax that OpenAPI links, HAL _links, and hypermedia API clients use to turn a template plus a set of variables into a concrete URI. The full Level 4 grammar is supported: every operator ({+var} reserved, {#var} fragment, {.var} label, {/var} path, {;var} path-style parameters, {?var} query, {&var} query continuation), the {var:3} prefix modifier, and the {var*} explode modifier for lists and associative arrays. b.uriTemplate.expand(template, vars) returns the expanded string; b.uriTemplate.compile(template) parses once for templates applied to many variable sets. A malformed template (unclosed expression, reserved operator, non-numeric prefix, unmatched brace) throws UriTemplateError. Verified against the official uritemplate-test conformance suite (all 135 spec, extended, and negative cases).",
7
- "sections": [
8
- {
9
- "heading": "Added",
10
- "items": [
11
- {
12
- "title": "`b.uriTemplate.expand` / `b.uriTemplate.compile`",
13
- "body": "RFC 6570 URI Template expansion, full Level 4. `expand(template, vars)` substitutes variables into a template and returns the URI; `compile(template)` returns a reusable `{ expand }` for repeated use. Variable values may be strings, numbers, booleans, arrays (lists), or plain objects (associative arrays); undefined, null, and empty list/map variables are omitted. All eight operators, the `:N` prefix modifier, and the `*` explode modifier follow §3.2, including reserved-set encoding for `{+var}` / `{#var}`. Composes naturally with `b.hal`, `b.linkHeader`, and `b.openapi` link objects. A malformed template throws `UriTemplateError`."
14
- }
15
- ]
16
- }
17
- ]
18
- }