@blamejs/blamejs-shop 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +33 -29
- package/lib/vendor/blamejs/CHANGELOG.md +4 -0
- package/lib/vendor/blamejs/api-snapshot.json +2 -2
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +1 -1
- package/lib/vendor/blamejs/lib/audit-chain.js +5 -1
- package/lib/vendor/blamejs/lib/calendar.js +6 -6
- package/lib/vendor/blamejs/lib/cwt.js +6 -1
- package/lib/vendor/blamejs/lib/external-db.js +4 -3
- package/lib/vendor/blamejs/lib/flag-cache.js +8 -8
- package/lib/vendor/blamejs/lib/http-client-cache.js +2 -2
- package/lib/vendor/blamejs/lib/http-message-signature.js +3 -3
- package/lib/vendor/blamejs/lib/inbox.js +18 -7
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +6 -2
- package/lib/vendor/blamejs/lib/mail-auth.js +6 -1
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +2 -2
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +5 -1
- package/lib/vendor/blamejs/lib/ws-client.js +22 -15
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.15.36.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.15.37.json +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +8 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/cwt.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +6 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +16 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-freshness.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.5.x
|
|
10
10
|
|
|
11
|
+
- v0.5.3 (2026-06-27) — **Vendored framework refreshed to 0.15.37 — non-finite numeric options can no longer silently disable a security check.** Updates the vendored blamejs framework to 0.15.37. The upstream release closes a class of fail-open configuration bugs: several numeric options validated a value only as `typeof === number && >= 0`, which accepts `Infinity`. Where the value is a clock-skew tolerance or a resource cap, an `Infinity` (or `NaN`) silently turned off the very protection it tunes — an `Infinity` CWT / OCSP-staple / ARC clock-skew made the expiry and freshness comparisons unsatisfiable (so an expired token, a replayed pre-revocation OCSP `good` response, or an expired ARC seal would be accepted), and an `Infinity` WebSocket message/frame cap, inbox/flag-cache size cap, or audit-chain fan-out cap disabled the bound. These options now route through the framework's finite-bounds validator, refusing a non-finite value at the entry point (or falling back to the safe default), while options where unbounded is deliberate (reconnect-indefinitely, retain-indefinitely) still accept it. This storefront configures these options with finite values, so there is no runtime or behavior change for it; the hardened framework rides along and no operator action is required. **Changed:** *Vendored framework refreshed to 0.15.37 (non-finite option values rejected)* — Refreshes the vendored blamejs framework to 0.15.37. The upstream fix makes a clock-skew tolerance or resource cap of `Infinity` / `NaN` a rejected value rather than one that silently disables the check it configures — covering the CWT / OCSP-staple / ARC time checks, the WebSocket-client inbound size and handshake caps, and the inbox / flag-cache / audit-chain size and count caps, with deliberately-unbounded options (reconnect/retain indefinitely) unaffected. The shipped storefront passes finite values to these options, so its runtime, public API, and wire format are unchanged; the hardened framework simply rides along.
|
|
12
|
+
|
|
13
|
+
- v0.5.2 (2026-06-27) — **Vendored framework refreshed to 0.15.36.** Updates the vendored blamejs framework to 0.15.36. The upstream release is internal test-suite tooling only: it re-verifies the guard-suite class that allows a bare comma/semicolon split on token-only RFC header grammars (RRULE, RFC 9421 signature components, TLS-RPT rua, SCIM attribute paths), renames its marker to a descriptive token, and prunes five inert marker comments. No runtime code, public API, or wire format changed upstream, so there is no runtime or behavior change for this storefront and no operator action is required. **Changed:** *Vendored framework refreshed to 0.15.36* — Refreshes the vendored blamejs framework to 0.15.36, an upstream release scoped to test-suite tooling — a guard-suite class re-verified and its marker renamed, plus five inert marker comments removed. The shipped framework runtime, public API, and wire format are unchanged, so no operator action is required.
|
|
14
|
+
|
|
11
15
|
- v0.5.1 (2026-06-27) — **Vendored framework refreshed to 0.15.35 — Prometheus label-injection fix in the metrics shadow registry.** Updates the vendored blamejs framework to 0.15.35. The upstream release fixes a label-injection in the framework's metrics shadow registry: it built a metric's cardinality key by joining label pairs with commas and re-split that string when rendering the Prometheus exposition, so a comma (or `=`) inside a label VALUE forged additional label pairs in the scrape output — which downstream tenant-scoping filters, authorization selectors, recording rules, and alerting trust as a boundary — and two distinct label sets could collide into one cardinality bucket. The key now uses canonical-JSON of the label set and the render emits the structured labels instead of re-splitting, so a value's commas/equals stay inside the value and a label named `constructor` / `prototype` / `__proto__` is preserved. This storefront does not compose the metrics shadow registry, so there is no runtime change for it; the fix ships in the vendored framework regardless and no operator action is required. **Changed:** *Vendored framework refreshed to 0.15.35 (Prometheus label-injection fix)* — Refreshes the vendored blamejs framework to 0.15.35. The upstream fix stops a comma or `=` in a metric label value from forging extra Prometheus label pairs in the shadow registry's scrape output — the cardinality key is now canonical-JSON of the label set and the render emits the structured labels rather than splitting a joined string, so distinct label sets stay distinct and reserved-name labels survive. The shipped storefront does not expose the metrics shadow registry, so its runtime, public API, and wire format are unchanged; the hardened framework simply rides along.
|
|
12
16
|
|
|
13
17
|
- v0.5.0 (2026-06-27) — **Affiliate commissions gain a tamper-evident hash chain, joining the gift-card, store-credit, and loyalty ledgers.** The affiliate-commission ledger was the last money ledger without tamper-evidence. Each commission is now linked into a per-affiliate SHA3-512 hash chain: a row names its predecessor, a uniqueness fence allows exactly one child per chain tip (so concurrent commissions for the same affiliate serialize instead of forking), and editing a commission's money fields or deleting a row from the middle breaks the linkage. Unlike the append-only gift-card / store-credit / loyalty ledgers, an affiliate commission walks a pending → paid → voided payout lifecycle in place, so the chain hashes only the immutable money facts — order, affiliate, order total, commission, currency, and time — and leaves the lifecycle columns out, meaning a legitimate payout or void never breaks the chain while an inflated amount or a removed row still does. A new `affiliates.verifyChain(affiliateId)` recomputes an affiliate's chain end to end and reports the first divergence, and accepts a trusted anchor (row count + head hash from an earlier snapshot) to also catch a truncation that deletes the most-recent commissions. Migration 0238 adds the chain columns + fence; commissions recorded before it are tolerated as an unverifiable legacy prefix, and the chain anchors fresh from the first commission recorded afterward. **Added:** *Affiliate commissions are a verifiable, fork-proof per-affiliate hash chain* — Recording a commission now links it into the affiliate's SHA3-512 hash chain — `row_hash = SHA3-512(prev_hash || canonical-json(immutable fields))` — under a `UNIQUE(affiliate_id, prev_hash)` parent fence, so a commission derived from a stale tip collides and retries instead of forking the chain, and concurrent commissions for one affiliate serialize. Because a commission's status, paid-at, payout reference, and void reason are stamped in place over its lifetime, only the immutable money facts are hashed, so a `pending → paid → voided` transition leaves the chain valid while tampering with the order total or commission amount, or deleting a commission, is detected. `affiliates.verifyChain(affiliateId)` walks the chain and reports the first break; pass `{ anchor: { count, head } }` to also rule out a tail truncation. Commissions written before this release are tolerated as an unverifiable legacy prefix and the chain anchors fresh from the next one — no backfill required. Migration 0238 adds the columns and the fence.
|
package/lib/asset-manifest.json
CHANGED
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
|
|
4
4
|
"packages": {
|
|
5
5
|
"blamejs": {
|
|
6
|
-
"version": "0.15.
|
|
7
|
-
"tag": "v0.15.
|
|
6
|
+
"version": "0.15.37",
|
|
7
|
+
"tag": "v0.15.37",
|
|
8
8
|
"license": "Apache-2.0",
|
|
9
9
|
"author": "blamejs contributors",
|
|
10
10
|
"source": "https://github.com/blamejs/blamejs",
|
|
@@ -858,6 +858,8 @@
|
|
|
858
858
|
"release-notes/v0.15.33.json": "lib/vendor/blamejs/release-notes/v0.15.33.json",
|
|
859
859
|
"release-notes/v0.15.34.json": "lib/vendor/blamejs/release-notes/v0.15.34.json",
|
|
860
860
|
"release-notes/v0.15.35.json": "lib/vendor/blamejs/release-notes/v0.15.35.json",
|
|
861
|
+
"release-notes/v0.15.36.json": "lib/vendor/blamejs/release-notes/v0.15.36.json",
|
|
862
|
+
"release-notes/v0.15.37.json": "lib/vendor/blamejs/release-notes/v0.15.37.json",
|
|
861
863
|
"release-notes/v0.15.4.json": "lib/vendor/blamejs/release-notes/v0.15.4.json",
|
|
862
864
|
"release-notes/v0.15.5.json": "lib/vendor/blamejs/release-notes/v0.15.5.json",
|
|
863
865
|
"release-notes/v0.15.6.json": "lib/vendor/blamejs/release-notes/v0.15.6.json",
|
|
@@ -1566,7 +1568,7 @@
|
|
|
1566
1568
|
".npmrc": "sha256:66f104e7d07c496d2d0409988225e8c0e4ceb8d247dbcac3be75b2128d20ce66",
|
|
1567
1569
|
".pinact.yaml": "sha256:0213ffda55961dc49b64c0a5dfa3c0567419633b1499d57eaf7c8d842d7da6c7",
|
|
1568
1570
|
"ARCHITECTURE.md": "sha256:9b1c8d2b1b7a41838eb348b0a008e4b4369718fd72bfe2974b37155f7536d35b",
|
|
1569
|
-
"CHANGELOG.md": "sha256:
|
|
1571
|
+
"CHANGELOG.md": "sha256:e14e4572e0a057f9633365e55eb176592f766006a4392009a25c9060d9b17cc0",
|
|
1570
1572
|
"CODE_OF_CONDUCT.md": "sha256:148a281960fff7c2fe6554dab66da572c72245ddeb00b0d14811558397bff386",
|
|
1571
1573
|
"CONTRIBUTING.md": "sha256:bb4dbdbc8598da31dbce653a8ed322e08ff46560173f2eb67a4d684653948332",
|
|
1572
1574
|
"GOVERNANCE.md": "sha256:906df6afb1f552b27b9acb50f7f96c47b917a2f1021cd4e987dbf4ee0e0a821b",
|
|
@@ -1576,7 +1578,7 @@
|
|
|
1576
1578
|
"NOTICE": "sha256:f487fa47a11aca0f89e2615cdd3c713e9842abf7a30d8d328eeeae1c864aa774",
|
|
1577
1579
|
"README.md": "sha256:3ddcc197b003da0b02db8bdd1aef1e943c94f7eab613c633d6a45bb11d0a80e9",
|
|
1578
1580
|
"SECURITY.md": "sha256:77d8c2bcc04b425a08ef30e51204cebddbd48954b028a259c7a8412dbfeab40a",
|
|
1579
|
-
"api-snapshot.json": "sha256:
|
|
1581
|
+
"api-snapshot.json": "sha256:8665f75a6befdc47255199601827a9642aa9f1662f271c35325068482edb11ad",
|
|
1580
1582
|
"assets/BlameJS_Logo.png": "sha256:3c65699753c771b48ef9ac7f45bb40815ec19a23afcdd0cd30ef4601bbbe293e",
|
|
1581
1583
|
"assets/BlameJS_Logo.svg": "sha256:dda44f3fb1343d5de9db6b1fcdb75fc649c57e7a99a8e8239fcf852e3841e1a8",
|
|
1582
1584
|
"bench/README.md": "sha256:74202f2507fd840bfc1ac6c681975d9273cf36cca6e0f72655f138337304033c",
|
|
@@ -1784,7 +1786,7 @@
|
|
|
1784
1786
|
"lib/agent-event-bus.js": "sha256:c8b21e11f0b6596ddfc36cd87a7ee26185e41ff59e3bb65c0fb4b02e525ee1e4",
|
|
1785
1787
|
"lib/agent-idempotency.js": "sha256:dc3f9f82d129a844812a042e6730f167df471fc0792d60b0dc2bb174604d896e",
|
|
1786
1788
|
"lib/agent-orchestrator.js": "sha256:517f672eefbfc430cabf06f3162ab5fd2212996d17366909d9eff7aa514363b2",
|
|
1787
|
-
"lib/agent-posture-chain.js": "sha256:
|
|
1789
|
+
"lib/agent-posture-chain.js": "sha256:450f0ebe438b4e99d7017a498ae1f705b54c472145111d145348be2ea78490d7",
|
|
1788
1790
|
"lib/agent-saga.js": "sha256:1a239892fe534d16e5567b731730bebc073057fe7d3bae63e0c49d989648c501",
|
|
1789
1791
|
"lib/agent-snapshot.js": "sha256:eac77c6c771b31ced5ce01937ee0cab301bc97a5b96d9db949471296029da769",
|
|
1790
1792
|
"lib/agent-stream.js": "sha256:8e519cba5505ad8e89bfe827483c16fe577ad39742e17f8f01d30b430a4bbf0b",
|
|
@@ -1822,7 +1824,7 @@
|
|
|
1822
1824
|
"lib/asyncapi-traits.js": "sha256:82ec58b77f0a65ef1b5e8b67ee9555e9cb984de769c3393fe5c6d4d0640546f0",
|
|
1823
1825
|
"lib/asyncapi.js": "sha256:cd48847ed363023e248cf0fbe275b545b0002b85e93ed91315990ef8a6abe483",
|
|
1824
1826
|
"lib/atomic-file.js": "sha256:b93b008c121f785d149d4450681467b18785781c3a82b9b001bb2a7d636c3ad1",
|
|
1825
|
-
"lib/audit-chain.js": "sha256:
|
|
1827
|
+
"lib/audit-chain.js": "sha256:c8c4bfb7abf0a4f2288b56404f30c1d8311c22f3c71f6c9c3a606a142744f3ea",
|
|
1826
1828
|
"lib/audit-daily-review.js": "sha256:61cebe47d6e5eeeedcab45515473acfaab623217810a7ab3629b3aeca5028519",
|
|
1827
1829
|
"lib/audit-emit.js": "sha256:1171699027269e154fd247141a549e65bcdb566e52ed00b993c7dfb9a293ba0f",
|
|
1828
1830
|
"lib/audit-sign.js": "sha256:b8ddef053fca28c1e6cf265f73ecd254290a9c2e0759250342e926280205342f",
|
|
@@ -1873,7 +1875,7 @@
|
|
|
1873
1875
|
"lib/cache-redis.js": "sha256:661ba39417085682cee1e5524685c9cb1e1277c6f1cef9ca4ae808e457d5499e",
|
|
1874
1876
|
"lib/cache-status.js": "sha256:f82bf22698c9b4fa10b7dc581fd39b83709267f105195908eee29612a14df15d",
|
|
1875
1877
|
"lib/cache.js": "sha256:e38929b261a6c9ea2dd822f39f2669c2ee5bbc6729517ced49471227d1d5ddb9",
|
|
1876
|
-
"lib/calendar.js": "sha256:
|
|
1878
|
+
"lib/calendar.js": "sha256:040bcf1385ffea3eb5779f1192f7f01889e498a3773de4c81d3cd37fc5b98307",
|
|
1877
1879
|
"lib/canonical-json.js": "sha256:3546e2a4b7692c4f8c77602e8804a8aaebff29b2183329588578c1d6f0df3eee",
|
|
1878
1880
|
"lib/cbor.js": "sha256:9b6a6799b10646e50705f9e0839fbac2f3b8df6e41943170c15e308215936feb",
|
|
1879
1881
|
"lib/cdn-cache-control.js": "sha256:adc1e5bdc59492de9e2f688dbee279a0ab09166adba609c4b5355028c4d3e0d1",
|
|
@@ -1919,7 +1921,7 @@
|
|
|
1919
1921
|
"lib/crypto.js": "sha256:0884094cdfc52e577c10b51622b940e9acc1ce2ef8dd8996d790e51e80b2f9b5",
|
|
1920
1922
|
"lib/csp.js": "sha256:b7a4d24f721767edecb7bc0c7a28c7824b7dda596c2edb3be387304684a8698a",
|
|
1921
1923
|
"lib/csv.js": "sha256:eca5d01409e72f5f73c5a954f3019826ed5da06f72103a5117016aeb0654f17c",
|
|
1922
|
-
"lib/cwt.js": "sha256:
|
|
1924
|
+
"lib/cwt.js": "sha256:b0156f9098c3eb336221949c838b25f2eb9d4f765af6e293df225493f64c12a4",
|
|
1923
1925
|
"lib/daemon.js": "sha256:9441d845c9a2d4b2808cdd6bf18915d89338a3dd75fa304c4b38c047b9f47a54",
|
|
1924
1926
|
"lib/dark-patterns.js": "sha256:e54cc6fb591748b937627560ccdfd84da0e84974da5189e15b911e00b3c7e51b",
|
|
1925
1927
|
"lib/data-act.js": "sha256:9d8f276393bef93e90ddf471f6c424cf95728a9c67fc8416fc5e3ccc99f690ee",
|
|
@@ -1946,14 +1948,14 @@
|
|
|
1946
1948
|
"lib/error-page.js": "sha256:4ed2e8a118b35d3dc76a072b233713ae1fdaedb54334df68ad081e19f765cb88",
|
|
1947
1949
|
"lib/events.js": "sha256:b5f0034fda33285f571b9b247db50bff08f632762d9bf69b83784b435eeea75c",
|
|
1948
1950
|
"lib/external-db-migrate.js": "sha256:990014016e71a818672b3f2c32eaa2ff6815553feb52efafb88ec765df9a570a",
|
|
1949
|
-
"lib/external-db.js": "sha256:
|
|
1951
|
+
"lib/external-db.js": "sha256:309a6bf0156f609dd3f143eaae1f41eec5a22a49f9d83eaf7c8f6f28d82b2d76",
|
|
1950
1952
|
"lib/fapi2.js": "sha256:6ae4679931675a629fa3f9e9efba71172fe69251dec2dd5d8c995cfb9dcad8f7",
|
|
1951
1953
|
"lib/fda-21cfr11.js": "sha256:9256bcea7d526a7280d5d3b1beed5be1daf2402ded2d5a18d602466d01a7884c",
|
|
1952
1954
|
"lib/fdx.js": "sha256:67a29b140b6d07a767da31d6b55d1db0c2219cdaa780531fdec0fec63a5c551c",
|
|
1953
1955
|
"lib/fedcm.js": "sha256:4b0f1e3d8e762353a28b2d42e00678c120d6e8ad574ecb9d94d4aa8dead0f7f7",
|
|
1954
1956
|
"lib/file-type.js": "sha256:158d798f8204cc96038b4fda2a215bb4ba79232476e31195f15d4fee8f790a00",
|
|
1955
1957
|
"lib/file-upload.js": "sha256:436b763fd928f286fb7faba8e2e9373cd37dcf677cb19e12afee61f00413e420",
|
|
1956
|
-
"lib/flag-cache.js": "sha256:
|
|
1958
|
+
"lib/flag-cache.js": "sha256:4333ce5105e2a2970b7277fa77f6664d1c6d2e3c6e7c3fa6094c228d305df4dc",
|
|
1957
1959
|
"lib/flag-evaluation-context.js": "sha256:fa8846a9a97a9d773dc08dd0da664d1d6d3fead4e12dc9982b33fa45b59d47a8",
|
|
1958
1960
|
"lib/flag-providers.js": "sha256:d85ff62c2d599634c3d2dc9d4e4a8cfcecec8178ce63a2800438434cc365f84a",
|
|
1959
1961
|
"lib/flag-targeting.js": "sha256:c2b5096e04dcaa98435c66cfa6b1aae02d0da708ca7e96f810627ef38e669309",
|
|
@@ -2029,17 +2031,17 @@
|
|
|
2029
2031
|
"lib/handlers.js": "sha256:860d07682bc6199c5d06f2d91f4ba9ab26ff5e2d9a97c72af6a700505d0e44e2",
|
|
2030
2032
|
"lib/honeytoken.js": "sha256:288a69a6c22107d41185c88ec86ca1bf424b3c10d349d90d5fc5037749db0568",
|
|
2031
2033
|
"lib/html-balance.js": "sha256:da49a83a2b17e9e5feb3f061e59bb595ceb37d8dfb6a7c1da7ab230705b7259f",
|
|
2032
|
-
"lib/http-client-cache.js": "sha256:
|
|
2034
|
+
"lib/http-client-cache.js": "sha256:52a0ef4ce9bfa99886f0e26c7fd4649b6cb6294c0f7e46279f23c76437a8fc64",
|
|
2033
2035
|
"lib/http-client-cookie-jar.js": "sha256:50aee1674d2c2ac73fc153fffd6be6d2628326cf85ac15e7d19b9bcd1e727826",
|
|
2034
2036
|
"lib/http-client.js": "sha256:99f1a4faf97e858794d4422a8cc9a813154d251413ffce328635a7e93e85d62b",
|
|
2035
|
-
"lib/http-message-signature.js": "sha256:
|
|
2037
|
+
"lib/http-message-signature.js": "sha256:3d5faf7637ceea9dfa1d281848d11e790100e113f5ef3366fc4c733d5f29b4b9",
|
|
2036
2038
|
"lib/http2-teardown.js": "sha256:61d291c34e321e18b64d60a4c0253e638550fff7dc32568b980d3aa13bb178e2",
|
|
2037
2039
|
"lib/i18n-messageformat.js": "sha256:2c5012ac3fbc6f56191765bda090889ab58206d130cf05c81963d965744d63d6",
|
|
2038
2040
|
"lib/i18n.js": "sha256:e2663046590798f2dde5cb73f14973ca509070841dff7c9697546e5dc0ec195a",
|
|
2039
2041
|
"lib/iab-mspa.js": "sha256:64d45d2f8b8373efe4daa0584f72b1a50940c9118aeb4f0925c38d3fdb3d64f3",
|
|
2040
2042
|
"lib/iab-tcf.js": "sha256:c21467eced77bce0bc28ca08a7ef5ea5f7a99b4f3f5fad36c1cb70b773e0f7c3",
|
|
2041
2043
|
"lib/importmap-integrity.js": "sha256:addabb8fb49c9ce94e768d8f6d32ac05dc096b2c036eff3ad3528ba65b84292b",
|
|
2042
|
-
"lib/inbox.js": "sha256:
|
|
2044
|
+
"lib/inbox.js": "sha256:40e662386d56d4f2b3fbad02f1fed8a33f75e88596e12f5f88a7acfff94de588",
|
|
2043
2045
|
"lib/incident-report.js": "sha256:03729af0dcaba8aadd709c7c3036776c6155b012bd7b80ffc2a13a42609d2a0f",
|
|
2044
2046
|
"lib/ip-utils.js": "sha256:cc7a40523a25f3732f8f9ce3b52d725a981ca831a973888abd059ef8e5dfa459",
|
|
2045
2047
|
"lib/jobs.js": "sha256:f8e273bdd25b172cff189a8b5b56748828216fdb5d5f445634477c7b2686e7c2",
|
|
@@ -2067,9 +2069,9 @@
|
|
|
2067
2069
|
"lib/log.js": "sha256:8268967e7f45bf59b131ba7e651b48a15656850f5585efe98001af4406c7c495",
|
|
2068
2070
|
"lib/lro.js": "sha256:da9baf47f27c422c32d51495b2896c887ec3ac283875712efcd7528fd396868b",
|
|
2069
2071
|
"lib/mail-agent.js": "sha256:4ca93408c03873796d285d002067ffa54fa70859ad5437215d6001d90c0f76ea",
|
|
2070
|
-
"lib/mail-arc-sign.js": "sha256:
|
|
2072
|
+
"lib/mail-arc-sign.js": "sha256:68e71b313e7f44924c60ad668a101897db4c9966222793eed69bc1c868788d59",
|
|
2071
2073
|
"lib/mail-arf.js": "sha256:47d02905b58c6c0733114e58594034adedd73799de39b93420459dbb04d22050",
|
|
2072
|
-
"lib/mail-auth.js": "sha256:
|
|
2074
|
+
"lib/mail-auth.js": "sha256:2e7e72ca6dcc5598cb5a27ffb20b2fa43869d5a27aaf95fcf41b170dbafdea8a",
|
|
2073
2075
|
"lib/mail-bimi.js": "sha256:b22afc5e8ad04503fee7e41f3b34c05c9f0759579436bfbe5f7706180c466155",
|
|
2074
2076
|
"lib/mail-bounce.js": "sha256:461e885dd2ea0664e9a4af39ae5c36f3a5d9c7dc914dad4c40641b705952fd04",
|
|
2075
2077
|
"lib/mail-crypto-pgp.js": "sha256:2e94f51d2cb13b92c612e84f73cf96e83eb17843d57b9dc9aef688ce55a1f9b4",
|
|
@@ -2109,14 +2111,14 @@
|
|
|
2109
2111
|
"lib/mcp.js": "sha256:c65af74fdb3cf30436699aba5051c4d6e8cb972887cbaaecc46250d557599953",
|
|
2110
2112
|
"lib/mdoc.js": "sha256:bc31d38d6fdfac8db8f040dfa4d33ab40405f56340bb4ec978f3ba98e93f6433",
|
|
2111
2113
|
"lib/metrics.js": "sha256:55d445b738b2cfc8ceb1bfa7a1e69b306497e8065ea86d61e4f4087873acce1b",
|
|
2112
|
-
"lib/middleware/age-gate.js": "sha256:
|
|
2114
|
+
"lib/middleware/age-gate.js": "sha256:4d4967beef100e2e58f1565de770e31fb8dd50dab2f04fa8251972fea5607826",
|
|
2113
2115
|
"lib/middleware/ai-act-disclosure.js": "sha256:6fc62940d85813019b31c586fc86853570329a939565a6947f81052d50977c42",
|
|
2114
2116
|
"lib/middleware/api-encrypt.js": "sha256:f90210bfc4fe04b50f6d72e14676a140c5759e255ed359567066a1e80ab5fb63",
|
|
2115
2117
|
"lib/middleware/assetlinks.js": "sha256:bb36f8d53991668b4ae1557d0cab3ec8b479a16f9133c43f9adc07963c8b4750",
|
|
2116
2118
|
"lib/middleware/asyncapi-serve.js": "sha256:a0f0b62b264a5cd8a24e3cfddae722a84b5e2206729f77403906a1d1cfcd7c7b",
|
|
2117
2119
|
"lib/middleware/attach-user.js": "sha256:776424918bf302378da99a85ff20c91a42f8c9f528ab435cd7a06b95ec523d6d",
|
|
2118
2120
|
"lib/middleware/bearer-auth.js": "sha256:d0cb9d65a724a0badf9a5164397d12bf6a3afa9a2ae5e75922799b832741ee59",
|
|
2119
|
-
"lib/middleware/body-parser.js": "sha256:
|
|
2121
|
+
"lib/middleware/body-parser.js": "sha256:a2a0af8ac8cecd9e6c842680029d6b409122d0639e33cc7204089e9c0daf6b73",
|
|
2120
2122
|
"lib/middleware/bot-disclose.js": "sha256:2b98da494ee51ba7c8efc0d5fccbf11ba335649e71726d83847116c340720bf0",
|
|
2121
2123
|
"lib/middleware/bot-guard.js": "sha256:ba724a897f646fcab7c384eb6e5c076e77e83b8e1c8d82a5aab79e8df616af8c",
|
|
2122
2124
|
"lib/middleware/clear-site-data.js": "sha256:9b489c42aa6d4ee9f4b1e6e410b211f5d28134fc1d3933502f7504d64bc68c26",
|
|
@@ -2155,7 +2157,7 @@
|
|
|
2155
2157
|
"lib/middleware/require-methods.js": "sha256:3b1b7ee5844969e813a46f26e78ed116ccfa28d2c6a9b821bb5d8c998ae95ea9",
|
|
2156
2158
|
"lib/middleware/require-mtls.js": "sha256:f823a6d26af0969f28bced4ab166533dc6a5547df2f9120392d77acdbff3ba97",
|
|
2157
2159
|
"lib/middleware/require-step-up.js": "sha256:dd8967633dc9133e54b1c4965247c77be087d6d34c395746d319b3af8e81e2eb",
|
|
2158
|
-
"lib/middleware/scim-server.js": "sha256:
|
|
2160
|
+
"lib/middleware/scim-server.js": "sha256:edb2b85326310247c19e2b9005df6b9af92d689d611759a824649f67b8283b15",
|
|
2159
2161
|
"lib/middleware/security-headers.js": "sha256:87506e572454033b33955d58d9f0cbfdc14edc63c1cbe7e01f0dc4f07753c64d",
|
|
2160
2162
|
"lib/middleware/security-txt.js": "sha256:3140d6537aa09a7052d83088840358949dd36393697fa2215f8f915d31aaa112",
|
|
2161
2163
|
"lib/middleware/span-http-server.js": "sha256:8f22bf4feb5f05a201da20b89425f0e904ed6368c91b2fcd1be9a0f5cb067696",
|
|
@@ -2180,8 +2182,8 @@
|
|
|
2180
2182
|
"lib/network-heartbeat.js": "sha256:f14ec7fa4667cbb04477d91ae59d6253b6c9ccc43071bc558e737cc672f43975",
|
|
2181
2183
|
"lib/network-nts.js": "sha256:90ce47584ad58c171e8029eb1d5df8ba4ba130c4038d1fe260109685c34c8f75",
|
|
2182
2184
|
"lib/network-proxy.js": "sha256:9342c534863f905000d2432b67c26dd7a5ea02be25aaf9ebe05976079e61d4c9",
|
|
2183
|
-
"lib/network-smtp-policy.js": "sha256:
|
|
2184
|
-
"lib/network-tls.js": "sha256:
|
|
2185
|
+
"lib/network-smtp-policy.js": "sha256:4d10a339d5c471cdff217c5514038eb2e30f4ecb97a38cb4405165ee0f9dcf7e",
|
|
2186
|
+
"lib/network-tls.js": "sha256:bfd58cf11c307151c98044d8b7ac43f7fefc8f8d36fee61789e88e7435aafbe0",
|
|
2185
2187
|
"lib/network-tsig.js": "sha256:41dc85a6e816cce1c58c36891dfc6bafc612b68978936107a09a5a553563e0d7",
|
|
2186
2188
|
"lib/network.js": "sha256:40cb01627bf45741cac22ef79f5985f69323e47732d54cf3ce28c06882830c25",
|
|
2187
2189
|
"lib/nis2-report.js": "sha256:fa106a4f95dd9301d7da966c5792197bdbebac477a4f1d2fd2ac2481fbd818fd",
|
|
@@ -2340,14 +2342,14 @@
|
|
|
2340
2342
|
"lib/wiki-concepts.js": "sha256:54789790b950d926cbd0657dd51b32435c4988012b520819615df93673680de5",
|
|
2341
2343
|
"lib/worker-pool.js": "sha256:fc35b3c9f83a4c72be0981400f5294b566472bfe187df0500775e37dd71afa4c",
|
|
2342
2344
|
"lib/worm.js": "sha256:f17e24f8ca39295f396f40bd0c4ad1c946f5e41ff646ef1b33e8d50dc8b9435e",
|
|
2343
|
-
"lib/ws-client.js": "sha256:
|
|
2345
|
+
"lib/ws-client.js": "sha256:3ac479017e1199f1b69300b81e473f8303f6782161b73e95b17e47cf494ee066",
|
|
2344
2346
|
"lib/x509-chain.js": "sha256:fdc0ecd264d9de0e039f348e819675fdf3838d61097a97a0e3e119def338722a",
|
|
2345
2347
|
"lib/xml-c14n.js": "sha256:01a27d20df99ebd6eaefbff0aad933a2231049f65a54ae994236694d4146ce5d",
|
|
2346
2348
|
"oss-fuzz/projects/blamejs/Dockerfile": "sha256:6c48ec4d81f4b1836a6b43701a2497cf737002413dd0da6a10892be1498b0766",
|
|
2347
2349
|
"oss-fuzz/projects/blamejs/README.md": "sha256:ae13b7bb79ed8d69b1b3276e5562807a0349fb6e6b7d11cf1f683aad1eafdb4b",
|
|
2348
2350
|
"oss-fuzz/projects/blamejs/build.sh": "sha256:0ced1cf21782c97be7f8d74faf5e27a308b60b2f858836fb5ca3b8c4e939a8f7",
|
|
2349
2351
|
"oss-fuzz/projects/blamejs/project.yaml": "sha256:59f2cb83aa622325a175b77416fe155be15b70a9c798bd1a78bba05763b1b03d",
|
|
2350
|
-
"package.json": "sha256:
|
|
2352
|
+
"package.json": "sha256:ffb1cef6e0b19b328852007de2c92144fdb0a08f4627a4b3bf048eeb3857bee4",
|
|
2351
2353
|
"release-notes/v0.0.x.json": "sha256:7a49819f30068ee119000cad7010194882bb8bfaa12acbdab4dfc066efb7982f",
|
|
2352
2354
|
"release-notes/v0.1.x.json": "sha256:6742a8c17f947c5cb76f69dead7eea86b942d80621d914b774ba5488e09937e5",
|
|
2353
2355
|
"release-notes/v0.10.x.json": "sha256:fe498045daf88337bd3d987e5964aa42c99a50e1685b6f09e51f698b8687726f",
|
|
@@ -2385,6 +2387,8 @@
|
|
|
2385
2387
|
"release-notes/v0.15.33.json": "sha256:295ea9939b74498c7f0be6afdc8bc511f97cb2383a436c3040c58b0600598b19",
|
|
2386
2388
|
"release-notes/v0.15.34.json": "sha256:2a1b36d897d8e9f2cece051c098030f08d9c6ee6bef136800bd19453ad65f188",
|
|
2387
2389
|
"release-notes/v0.15.35.json": "sha256:e317b2252734aace7a0f734e3ab40c3a0c4ac208d678c0e9834be34eb424d15b",
|
|
2390
|
+
"release-notes/v0.15.36.json": "sha256:392c15724efc02fad5825aaf93b3f5dcef90a0cb9909d61f86e777f3061045ec",
|
|
2391
|
+
"release-notes/v0.15.37.json": "sha256:b696debed024bed09c59eaeb467d6dde93d7f30ad33f46af8a37d07534df09d8",
|
|
2388
2392
|
"release-notes/v0.15.4.json": "sha256:6ac7fa0ef1728c27e71b2050d1b07a810f9b4b1440ccddbf28ad56e2f54d8585",
|
|
2389
2393
|
"release-notes/v0.15.5.json": "sha256:cca1d0edd5d6fc41b512d19d98be224b990dcab41478622c11962f0fcb1bb09a",
|
|
2390
2394
|
"release-notes/v0.15.6.json": "sha256:0e3b9e5e43b70b61dd258c3003d1b8729cd3c26c62a34dedcca81bbec5d31077",
|
|
@@ -2631,7 +2635,7 @@
|
|
|
2631
2635
|
"test/layer-0-primitives/cluster-storage.test.js": "sha256:238b3b3db0eba3e6312a863710533178f566347b90e161e564481aa826707647",
|
|
2632
2636
|
"test/layer-0-primitives/cluster-vault-rotation.test.js": "sha256:3514e9e71d6c39e805248f58ad2f41528d091e196c0f3766a032675677161b2d",
|
|
2633
2637
|
"test/layer-0-primitives/cms-codec.test.js": "sha256:7e46078ed82be5b69d22c48f22dba37ea5015371c2a8cf5f94fb1a792fb7bb78",
|
|
2634
|
-
"test/layer-0-primitives/codebase-patterns.test.js": "sha256:
|
|
2638
|
+
"test/layer-0-primitives/codebase-patterns.test.js": "sha256:ae47993e4fb7be4bb52b4c71a178cd47928b853dec9935c6445d8966da04ed4f",
|
|
2635
2639
|
"test/layer-0-primitives/codepoint-class.test.js": "sha256:19d1b69efa7e0e9f7ef2392ca264167767a5ec5890ee339b2cfd8a7818035d27",
|
|
2636
2640
|
"test/layer-0-primitives/compliance-ai-act.test.js": "sha256:5ee4ad05d12233cb3c5457ef10a727833710bbc1ce1318838f9f9ef5d2cb8d4b",
|
|
2637
2641
|
"test/layer-0-primitives/compliance-cascade.test.js": "sha256:ee02cf14541a837a9d7977c6ea6bf7f9210bed293925d93c976e31f270aebec4",
|
|
@@ -2674,7 +2678,7 @@
|
|
|
2674
2678
|
"test/layer-0-primitives/csp-report.test.js": "sha256:8bb2e54a31fd4317a6a39a4dc3358147a0102dbfa58b71d85a5743008289a2ff",
|
|
2675
2679
|
"test/layer-0-primitives/csrf-protect.test.js": "sha256:802a8de2fee57c01a03b523cd6701780c181838006356c30a01452b30381225b",
|
|
2676
2680
|
"test/layer-0-primitives/csv.test.js": "sha256:6a9ed5a3fc6fd315261ad0bf7788047de5323fe09c36bb52d6e599a761b18085",
|
|
2677
|
-
"test/layer-0-primitives/cwt.test.js": "sha256:
|
|
2681
|
+
"test/layer-0-primitives/cwt.test.js": "sha256:74b06a3278eacbaed102ae9e966fac8d43e3fc609c288fde4f535566df78a5ed",
|
|
2678
2682
|
"test/layer-0-primitives/daemon.test.js": "sha256:a118c7a6d738115ea4590ef1d639e4c06f139732bcaf8545d649452dfdb3979e",
|
|
2679
2683
|
"test/layer-0-primitives/daily-byte-quota.test.js": "sha256:c4683ef74d205ba8ebabf3c9f0ce60d0e86824cf0c8a0f23c1e6b8c5b5b70f23",
|
|
2680
2684
|
"test/layer-0-primitives/dane.test.js": "sha256:59d01acff1a81f0d691ecb28920f5c22e30ef416bf84a52c853c1a00e64cf783",
|
|
@@ -2735,7 +2739,7 @@
|
|
|
2735
2739
|
"test/layer-0-primitives/fido-mds3.test.js": "sha256:91893e57258f91386ea3baedf8727dac08b5c75725a74b74ecdfbc5d99bb4adc",
|
|
2736
2740
|
"test/layer-0-primitives/file-type.test.js": "sha256:ba6bb725857cbf7b165f4391e3aa126c9fc4286c5afb92d1964ff1d0dfd2b57c",
|
|
2737
2741
|
"test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js": "sha256:ad13f7f84a33899b4f4e66b74c896f606e070bfe5df769b0acd109cfb7ab38c7",
|
|
2738
|
-
"test/layer-0-primitives/flag.test.js": "sha256:
|
|
2742
|
+
"test/layer-0-primitives/flag.test.js": "sha256:37f17c037220ef60bb0afb79cbddd359a9b5784ed6ce8efd3dc04063fbc09176",
|
|
2739
2743
|
"test/layer-0-primitives/forensic-snapshot.test.js": "sha256:629a8af1ccd0396e2b30f8e46d76fe19eab9f33c156c0db78e3e76649e21030c",
|
|
2740
2744
|
"test/layer-0-primitives/fsm.test.js": "sha256:7037831083426478ed4efd7d1c17d4b96c3609816a41819b5c9da71d2b6187e8",
|
|
2741
2745
|
"test/layer-0-primitives/gate-contract-content-gate.test.js": "sha256:d9f8b94231efb0e02a94b7934bf58a3f8e343829ce11346b7302e69e13efd551",
|
|
@@ -2796,7 +2800,7 @@
|
|
|
2796
2800
|
"test/layer-0-primitives/iab-tcf.test.js": "sha256:34e5f9b2740c0b96e3f84d003fbbf55a4d366f3d08f6baedca0109573eead9b6",
|
|
2797
2801
|
"test/layer-0-primitives/idempotency-key.test.js": "sha256:0214b68cbb3f50a0c7cbc7fcb92b1edf86a714dd6040c3a1a2be34b31eb9c631",
|
|
2798
2802
|
"test/layer-0-primitives/importmap-integrity.test.js": "sha256:babd21e2356c7d1d7916ae89fc228105290bf8c99371f0cde68512a62bea45dd",
|
|
2799
|
-
"test/layer-0-primitives/inbox.test.js": "sha256:
|
|
2803
|
+
"test/layer-0-primitives/inbox.test.js": "sha256:b8c16ba0cd5f2453ef9cc242dfc1af85e88f8d65a361c02ec2209225a0e67b99",
|
|
2800
2804
|
"test/layer-0-primitives/incident-report.test.js": "sha256:57bc8d037e6a3f84fcefe0a95205e5ac0f83c6dbb8b1f935c3c5184d2456c857",
|
|
2801
2805
|
"test/layer-0-primitives/ip-utils.test.js": "sha256:0ca865e21be6a8815a5c0dd772497b3e0ab4cedd67e7dbabf9518c82af789132",
|
|
2802
2806
|
"test/layer-0-primitives/jose-jwe-experimental.test.js": "sha256:fabd155817296fbac7cea4ccaaf5e1740471fe97b431fe46e13e6a30d13784ef",
|
|
@@ -2820,7 +2824,7 @@
|
|
|
2820
2824
|
"test/layer-0-primitives/mail-agent.test.js": "sha256:51725784bab6849cb02f1e94e0ba97c2b8e7e101780a26ae7ec19f9d894640ef",
|
|
2821
2825
|
"test/layer-0-primitives/mail-arf.test.js": "sha256:2279aebee547903a8c1415c9fc1c9c194757b3917e869bfddb43dd0375e68a93",
|
|
2822
2826
|
"test/layer-0-primitives/mail-auth-dmarc-policy-failclosed.test.js": "sha256:22066c238cdb9c4f84377f56f2d69e387bcb4a601ef384cc874fc55249fbfaa6",
|
|
2823
|
-
"test/layer-0-primitives/mail-auth.test.js": "sha256:
|
|
2827
|
+
"test/layer-0-primitives/mail-auth.test.js": "sha256:36f434e2ddb65b3f4cae76be48a917971254959d2312c1f87a6dfd5144d54560",
|
|
2824
2828
|
"test/layer-0-primitives/mail-bimi-cert-validity-unparseable.test.js": "sha256:d14d66d801b5767372ce7014617841f8710f7ad95107984da058ade794fd4d8f",
|
|
2825
2829
|
"test/layer-0-primitives/mail-bimi.test.js": "sha256:3e12e211e1f75f07d1ba43ada69c9adcd205902194f983d8ae24293b1acd1cd5",
|
|
2826
2830
|
"test/layer-0-primitives/mail-bounce.test.js": "sha256:d4ef09adc71e1c9e336a9fc5b059e6b264d9abcd0c032023bfa7b15a9173a4b7",
|
|
@@ -3023,7 +3027,7 @@
|
|
|
3023
3027
|
"test/layer-0-primitives/time.test.js": "sha256:59f6dcd5753b9bb97508044c30cfca948c4707dd8d107dab5eab53f0dcd91e84",
|
|
3024
3028
|
"test/layer-0-primitives/tls-exporter.test.js": "sha256:3224b12f18619fa805e933c8eef18356e42f8554d75d7c4e23ebd8b5b993ca6b",
|
|
3025
3029
|
"test/layer-0-primitives/tls-ocsp-ct.test.js": "sha256:7c98ddde33095683a3be527fee9e868501ca99eac74cc40fe1c56ca6018f1660",
|
|
3026
|
-
"test/layer-0-primitives/tls-ocsp-freshness.test.js": "sha256:
|
|
3030
|
+
"test/layer-0-primitives/tls-ocsp-freshness.test.js": "sha256:ddf2723551337d3859add255644e322c74d5e55df07be181795484be0835ee2d",
|
|
3027
3031
|
"test/layer-0-primitives/tls-ocsp-verify.test.js": "sha256:f726bb9387c570cee945a2b927070899bc7ae933ec09ba481c3aa5f0d11d2ba6",
|
|
3028
3032
|
"test/layer-0-primitives/tls-pinset-drift.test.js": "sha256:6b7c530326a1025819257daec21711d6f889a8523f2766595f7cc28563f5badc",
|
|
3029
3033
|
"test/layer-0-primitives/tls-preferred-groups.test.js": "sha256:7d3620cbde85289a98a385a3c3ba9d17f0bb2b8e5f5f9398b5641ff9d1e2b181",
|
|
@@ -3052,7 +3056,7 @@
|
|
|
3052
3056
|
"test/layer-0-primitives/worker-pool-recycle-race.test.js": "sha256:0e9f12256864334bf49de0b0d62b9d4f9cfeb34e9fe598f9299b811dc4ac4719",
|
|
3053
3057
|
"test/layer-0-primitives/worker-pool.test.js": "sha256:0a3698d862346f64f98eb1c954694ad6a6b4cedbfc569a1f4d93233b7bfa733a",
|
|
3054
3058
|
"test/layer-0-primitives/worm.test.js": "sha256:0908cfbf7228de39ab68796505c29071d3117571b8a6c07cef7d1fa27d0380b8",
|
|
3055
|
-
"test/layer-0-primitives/ws-client.test.js": "sha256:
|
|
3059
|
+
"test/layer-0-primitives/ws-client.test.js": "sha256:004f5f231e7396fd8b32c7f41e7cd36becaa8919584c740497a9b08c242414c8",
|
|
3056
3060
|
"test/layer-0-primitives/x509-chain-ca-enforcement.test.js": "sha256:8d68ffe4f8655af99682927524ae5a6c5336f32a57eb7c5c78fef5f2d1b2e6e3",
|
|
3057
3061
|
"test/layer-1-state/api-key.test.js": "sha256:48d641b673a181746adf80dfa995f0ca29187af17aa4735590557fda4350dcb8",
|
|
3058
3062
|
"test/layer-5-integration/bundler-output.test.js": "sha256:023f0504a5ae60fb39d347d05e8fe7e9d8fee685eb1bb89dd40406cf01691d22",
|
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.15.x
|
|
10
10
|
|
|
11
|
+
- v0.15.37 (2026-06-27) — **Several numeric options that silently accepted a non-finite value — disabling a clock-skew / freshness check or a resource cap — now reject it, so an `Infinity` skew or limit can no longer turn off the protection it configures.** A number of configuration options validated a numeric value with a bare `typeof === "number" && value >= 0` check, which accepts `Infinity`. Where the value is a clock-skew tolerance or a resource cap, an `Infinity` (or `NaN`) silently disabled the very check it tunes: a CWT / OCSP-staple / ARC clock-skew of `Infinity` made the expiry, freshness, and expiration comparisons unsatisfiable (an expired token / a replayed pre-revocation "good" response / an expired ARC seal would be accepted); a WebSocket-client `maxMessageBytes` / `maxFrameBytes` / `handshakeTimeoutMs` of `Infinity` disabled the inbound-OOM and stalled-handshake defenses; and inbox / flag-cache / audit-chain size and count caps of `Infinity` admitted unbounded data. These options now route through the finite-bounds validator: a present non-finite value is refused at the entry point (or falls back to the safe default on the result-returning paths). Options where an unbounded value is a deliberate intent — reconnect "retry indefinitely", inbox "retain indefinitely" — continue to accept `Infinity`. **Security:** *A non-finite clock-skew no longer disables CWT / OCSP / ARC time checks* — b.cwt.verify, the OCSP-staple freshness check in b.network.tls, and b.mail.arc.verify each took an operator clock-skew tolerance validated as `typeof === "number" && >= 0`, which accepts `Infinity`. Because each check is of the form `now > deadline + skew`, a skew of `Infinity` made it unsatisfiable and silently turned the check off: an expired CWT verified, a stale (post-nextUpdate) OCSP "good" response — the exact reply an attacker replays after the certificate is revoked — was accepted, and an expired ARC seal passed. A present clock-skew that is not a non-negative finite integer is now refused (b.cwt.verify throws cwt/bad-clock-skew; the OCSP and ARC paths fall back to their safe default). Regression tests assert an expired token / stale staple / expired ARC seal is still rejected when the skew is `Infinity`. · *WebSocket-client inbound caps can no longer be disabled by an Infinity value* — b.wsClient.connect validated maxMessageBytes, maxFrameBytes, and handshakeTimeoutMs (and the ping/pong keepalive intervals) with a bare numeric check that accepted `Infinity`, which disabled the inbound-message and frame size limits — the defenses against a malicious server sending an unbounded message — and the handshake timeout. A present non-finite value for these is now refused at connect time. The reconnect maxAttempts still accepts `Infinity` (a deliberate "reconnect indefinitely" intent). · *Inbox, flag-cache, and audit-chain caps reject a non-finite limit* — The inbox maxPayloadBytes / messageIdMaxLen / sourceMaxLen caps, the flag-cache ttlMs / maxEntries, and the audit-chain partition fan-out cap each accepted `Infinity`, disabling the cap (unbounded stored payloads, a never-expiring or unbounded cache, unbounded fan-out). These now require a positive finite integer — refused at create time, or clamped to the bounded default on the result-returning verify path. The inbox retentionDays still accepts `Infinity` (retain indefinitely).
|
|
12
|
+
|
|
13
|
+
- v0.15.36 (2026-06-27) — **Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside).** The codebase-patterns guard class that allows a bare comma/semicolon split on token-only RFC header grammars was re-verified from scratch and renamed to a descriptive token, with its old name recorded as retired. Each live marked site (RRULE, RFC 9421 component identifiers, TLS-RPT rua, SCIM attribute paths) was re-read and confirmed to split a grammar with no quoted-string members. Five marker comments that suppressed nothing were removed or turned into plain explanatory comments. No runtime code, public API, or wire format changed. **Detectors:** *Bare token-only header-split suppression class re-verified, renamed, and pruned of inert markers* — Each marked bare `.split(",")` / `.split(";")` on an RFC header value was re-read and confirmed to operate on a token-only grammar (no quoted-string members, so a quote-aware splitter is unnecessary): RFC 5545 RRULE, RFC 9421 signature component identifiers, RFC 8460 TLS-RPT rua, and RFC 7644 SCIM attribute paths. The guard class was renamed to a descriptive token and its old name added to the retired-token set. Five marker comments that the detector never actually evaluated (two sat on a date-normalizing `.replace`, three in header parsers the guard intentionally does not scan) were removed or converted to plain comments. This is test-suite tooling plus source-comment text; no shipped framework behavior changed.
|
|
14
|
+
|
|
11
15
|
- v0.15.35 (2026-06-26) — **`b.metrics` shadow registry no longer lets a comma in a label value forge extra Prometheus label pairs — a label-injection that downstream tenant-scoping or authorization selectors could be tricked by.** The shadow registry serialized a metric's label set into a `name=value,name=value` string key and re-split it on `,` to build the Prometheus exposition. A label VALUE containing a comma therefore split into multiple label pairs, so a single operator-set label could forge additional label pairs in the scrape output (for example turning one `route` label into a `route` plus an attacker-named label), which downstream tenant-scoping filters, authorization selectors, recording rules, and alerting trust as a boundary; two distinct label sets could also collide into one cardinality bucket. The label key now uses canonical-JSON of the string-coerced label set (the same approach the main registry already used) and the render path emits the structured label set kept alongside that key rather than splitting or re-parsing, so a `,` or `=` inside a label value stays inside the value and a label NAMED `constructor`, `prototype`, or `__proto__` (all valid Prometheus label names) is preserved instead of being dropped. Note: the cardinality keys exposed by `shadowRegistry().snapshot()` are now canonical-JSON strings (e.g. `{"route":"/api"}`) rather than `route=/api`. **Changed:** *shadowRegistry().snapshot() cardinality keys are canonical-JSON* — As a consequence of the label-injection fix, the per-series cardinality keys in a shadow-registry snapshot are now canonical-JSON strings (e.g. `{"tenant":"a"}`) instead of the previous `tenant=a` form. Code that reads those keys directly from snapshot() should parse them as JSON; the Prometheus render output is unchanged for conforming label values. **Security:** *Label values can no longer forge extra Prometheus label pairs in the shadow registry* — b.metrics shadowRegistry built a metric's cardinality key by joining `name=value` pairs with commas, then split that string on commas when rendering the Prometheus exposition. A comma (and `=`) in a label value therefore broke one value into several forged label pairs in the scrape output, and two different label sets could collide into the same cardinality bucket. The key is now canonical-JSON of the string-coerced label set and the render emits the structured label set kept alongside that key rather than splitting or re-parsing, so a label value is emitted as a single quoted value with its commas/equals intact, distinct label sets get distinct keys, and a label NAMED `constructor`, `prototype`, or `__proto__` (each a valid Prometheus label name) is preserved rather than dropped. Deterministic regression tests assert a comma-bearing label value produces exactly one label pair and that the reserved-name labels survive render.
|
|
12
16
|
|
|
13
17
|
- v0.15.34 (2026-06-26) — **`b.mail.crypto.pgp.verify` now accepts every valid RSA OpenPGP signature — one whose value happened to have a high zero byte (about 1 in 256) was being rejected.** An OpenPGP RSA signature is an integer modulo the key's modulus, and ~1 in 256 signatures have a value that begins with a zero byte. The MPI encoding strips those leading zero bytes (RFC 9580 §3.2), but b.mail.crypto.pgp.verify passed the stripped value straight to the RSA verification, which requires a signature exactly the modulus byte length — so a perfectly valid signature was intermittently reported invalid. The signature is now left-padded back to the modulus width before verification, exactly as the Ed25519 path already pads its components. Verification of every valid RSA signature is now reliable, including signatures produced by other OpenPGP implementations. Also includes internal test-tooling: two more guard-suite suppression classes were re-verified and their tokens retired. **Fixed:** *RSA OpenPGP signatures with a high zero byte now verify reliably* — b.mail.crypto.pgp.verify read the RSA signature MPI (whose leading zero bytes the OpenPGP wire format strips) and handed it to the RSA verification without restoring the stripped bytes. Node's RSA verify requires the signature to be exactly the modulus byte length, so a signature whose value had one or more high zero bytes — about 1 in 256 of all signatures, for any key — was rejected as invalid even though it was correct. The signature is now left-padded to the modulus width before verification (the same correction the Ed25519 verification path already applied to its R/S components). This affects signatures the framework produces and signatures from other OpenPGP implementations alike. A deterministic regression test searches for a short-MPI signature and asserts it verifies. **Detectors:** *Two more suppression-marker classes re-verified and their tokens retired* — The raw-hash-compare and seal-without-aad guard classes were re-read and confirmed (a data-residency region tag compared with === — not a secret; and two intentional non-AEAD-bound seals — a non-regulated plain-mode column whose AAD is enforced by the posture seal-envelope floor where it matters, and a throwaway vault-readiness probe), then renamed to descriptive tokens with their old names added to the retired-token set. Test-suite tooling only.
|
|
@@ -124,7 +124,7 @@ function create(opts) {
|
|
|
124
124
|
var auditImpl = opts.audit || audit();
|
|
125
125
|
var declaredRegimes = Object.create(null);
|
|
126
126
|
for (var i = 0; i < BUILTIN_REGIMES.length; i += 1) declaredRegimes[BUILTIN_REGIMES[i]] = true;
|
|
127
|
-
// allow:numeric-opt-Infinity — operator opt clamped to [1, DEFAULT_MAX_HOP_COUNT]; bad input falls back to default
|
|
127
|
+
// allow:numeric-opt-Infinity-intentional — operator opt clamped to [1, DEFAULT_MAX_HOP_COUNT] (the `<= DEFAULT_MAX_HOP_COUNT` upper bound rejects Infinity); bad input falls back to default
|
|
128
128
|
var maxHopCount = typeof opts.maxHopCount === "number" && opts.maxHopCount > 0 &&
|
|
129
129
|
opts.maxHopCount <= DEFAULT_MAX_HOP_COUNT
|
|
130
130
|
? Math.floor(opts.maxHopCount)
|
|
@@ -37,6 +37,7 @@ var canonicalJson = require("./canonical-json");
|
|
|
37
37
|
var C = require("./constants");
|
|
38
38
|
var clusterStorage = require("./cluster-storage");
|
|
39
39
|
var frameworkSchema = require("./framework-schema");
|
|
40
|
+
var numericBounds = require("./numeric-bounds");
|
|
40
41
|
var sql = require("./sql");
|
|
41
42
|
var safeSql = require("./safe-sql");
|
|
42
43
|
var safeBuffer = require("./safe-buffer");
|
|
@@ -305,7 +306,10 @@ async function verifyChain(queryAllAsync, tableName, opts) {
|
|
|
305
306
|
// coerce so a Postgres INTEGER/BIGINT chainKey is type-stable in the
|
|
306
307
|
// reported break-shape and the per-key WHERE bind, matching SQLite.
|
|
307
308
|
var keyRows = frameworkSchema.coerceRows(await queryAllAsync(keysBuilt.sql, keysBuilt.params));
|
|
308
|
-
|
|
309
|
+
// Partition fan-out cap; a non-finite / <= 0 / non-integer value (Infinity
|
|
310
|
+
// would make the `keyRows.length > maxChains` cap unsatisfiable) falls back
|
|
311
|
+
// to the bounded default rather than disabling the cap.
|
|
312
|
+
var maxChains = numericBounds.isPositiveFiniteInt(opts.maxChains) ? opts.maxChains : 100000;
|
|
309
313
|
if (keyRows.length > maxChains) {
|
|
310
314
|
return {
|
|
311
315
|
ok: false,
|
|
@@ -1119,7 +1119,7 @@ function _firstParamValue(prop, paramName) {
|
|
|
1119
1119
|
|
|
1120
1120
|
function _icalRruleToJscal(rrule) {
|
|
1121
1121
|
var out = { "@type": "RecurrenceRule", frequency: "daily" };
|
|
1122
|
-
var parts = String(rrule).split(";"); // allow:bare-split-on-quoted-header — RFC 5545 RRULE grammar has no quoted-string members; values are token-only
|
|
1122
|
+
var parts = String(rrule).split(";"); // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 RRULE grammar has no quoted-string members; values are token-only
|
|
1123
1123
|
for (var i = 0; i < parts.length; i += 1) {
|
|
1124
1124
|
var kv = parts[i].split("=");
|
|
1125
1125
|
if (kv.length !== 2) continue;
|
|
@@ -1129,11 +1129,11 @@ function _icalRruleToJscal(rrule) {
|
|
|
1129
1129
|
else if (key === "INTERVAL") out.interval = parseInt(val, 10);
|
|
1130
1130
|
else if (key === "COUNT") out.count = parseInt(val, 10);
|
|
1131
1131
|
else if (key === "UNTIL") out.until = _icalDateTimeToUtc(val);
|
|
1132
|
-
else if (key === "BYDAY") out.byDay = val.split(",").map(function (d) { // allow:bare-split-on-quoted-header — RFC 5545 BYDAY values are token-only
|
|
1132
|
+
else if (key === "BYDAY") out.byDay = val.split(",").map(function (d) { // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 BYDAY values are token-only
|
|
1133
1133
|
return { "@type": "NDay", day: d.slice(-2).toLowerCase() };
|
|
1134
1134
|
});
|
|
1135
|
-
else if (key === "BYMONTH") out.byMonth = val.split(","); // allow:bare-split-on-quoted-header — RFC 5545 BYMONTH values are integer-only
|
|
1136
|
-
else if (key === "BYMONTHDAY") out.byMonthDay = val.split(",").map(function (n) { return parseInt(n, 10); }); // allow:bare-split-on-quoted-header — RFC 5545 BYMONTHDAY values are integer-only
|
|
1135
|
+
else if (key === "BYMONTH") out.byMonth = val.split(","); // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 BYMONTH values are integer-only
|
|
1136
|
+
else if (key === "BYMONTHDAY") out.byMonthDay = val.split(",").map(function (n) { return parseInt(n, 10); }); // allow:bare-split-on-quoted-header-token-grammar — RFC 5545 BYMONTHDAY values are integer-only
|
|
1137
1137
|
}
|
|
1138
1138
|
return out;
|
|
1139
1139
|
}
|
|
@@ -1168,13 +1168,13 @@ function _utcDateTimeToIcal(s) {
|
|
|
1168
1168
|
// JSCalendar UTCDateTime "2026-05-22T10:00:00.123Z" →
|
|
1169
1169
|
// "20260522T100000Z" (RFC 5545 §3.3.5 form 2 has NO fractional
|
|
1170
1170
|
// seconds; strict ICS consumers reject `T100000.123Z`).
|
|
1171
|
-
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1171
|
+
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
1174
1174
|
function _localDateTimeToIcal(s) {
|
|
1175
1175
|
// JSCalendar LocalDateTime "2026-05-22T09:00:00.123" →
|
|
1176
1176
|
// "20260522T090000" (same fractional-second strip as the UTC form).
|
|
1177
|
-
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1177
|
+
return String(s).replace(/\.\d+/, "").replace(/[-:]/g, "");
|
|
1178
1178
|
}
|
|
1179
1179
|
|
|
1180
1180
|
function _isUtcDateTime(s) {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
var cose = require("./cose");
|
|
40
40
|
var cbor = require("./cbor");
|
|
41
41
|
var C = require("./constants");
|
|
42
|
+
var numericBounds = require("./numeric-bounds");
|
|
42
43
|
var validateOpts = require("./validate-opts");
|
|
43
44
|
var { defineClass } = require("./framework-error");
|
|
44
45
|
|
|
@@ -191,7 +192,11 @@ async function verify(cwt, opts) {
|
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
// Time claims (NumericDate, seconds). Skew tolerance both directions.
|
|
194
|
-
|
|
195
|
+
// A present clockSkewSec must be a non-negative finite integer — an
|
|
196
|
+
// Infinity / NaN / negative skew would otherwise make `now > exp + skew`
|
|
197
|
+
// unsatisfiable and silently disable the expiry / not-before checks.
|
|
198
|
+
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.clockSkewSec, "cwt.verify: opts.clockSkewSec", CwtError, "cwt/bad-clock-skew");
|
|
199
|
+
var skew = (typeof opts.clockSkewSec === "number") ? opts.clockSkewSec : 60; // allow:raw-time-literal — clock-skew in seconds (NumericDate units), not a ms duration
|
|
195
200
|
var now = _nowSec(opts);
|
|
196
201
|
// A present exp / nbf MUST be a well-formed NumericDate — a non-numeric
|
|
197
202
|
// value would otherwise bypass the time check entirely (a token could
|
|
@@ -1338,8 +1338,9 @@ async function transaction(fn, opts) {
|
|
|
1338
1338
|
// (serialization_failure) are transient — retry with capped attempts
|
|
1339
1339
|
// and a small jittered backoff. Operators tune retries via opts.deadlockRetries (default 3).
|
|
1340
1340
|
// numeric-bounds doesn't have a non-negative-int helper; use a
|
|
1341
|
-
//
|
|
1342
|
-
//
|
|
1341
|
+
// Explicit isFinite/integer guard (zero is permitted to disable retries
|
|
1342
|
+
// entirely); a non-finite deadlockRetries is refused here, so Math.floor
|
|
1343
|
+
// below only ever sees a validated finite integer.
|
|
1343
1344
|
if (opts.deadlockRetries !== undefined) {
|
|
1344
1345
|
if (typeof opts.deadlockRetries !== "number" || !isFinite(opts.deadlockRetries) ||
|
|
1345
1346
|
opts.deadlockRetries < 0 || (opts.deadlockRetries | 0) !== opts.deadlockRetries) {
|
|
@@ -1348,7 +1349,7 @@ async function transaction(fn, opts) {
|
|
|
1348
1349
|
}
|
|
1349
1350
|
}
|
|
1350
1351
|
var maxRetries = (typeof opts.deadlockRetries === "number")
|
|
1351
|
-
? Math.floor(opts.deadlockRetries) : 3;
|
|
1352
|
+
? Math.floor(opts.deadlockRetries) : 3;
|
|
1352
1353
|
// Validate the transaction-level residency tag shape at entry (the
|
|
1353
1354
|
// sessionGucs / deadlockRetries discipline) so an empty-string tag
|
|
1354
1355
|
// fails before BEGIN rather than at the first statement.
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
var validateOpts = require("./validate-opts");
|
|
25
25
|
var lazyRequire = require("./lazy-require");
|
|
26
26
|
var C = require("./constants");
|
|
27
|
+
var numericBounds = require("./numeric-bounds");
|
|
27
28
|
var { defineClass } = require("./framework-error");
|
|
28
29
|
var FlagError = defineClass("FlagError", { alwaysPermanent: true });
|
|
29
30
|
|
|
@@ -36,18 +37,17 @@ function cache(downstream, opts) {
|
|
|
36
37
|
throw new FlagError("flag/bad-cache",
|
|
37
38
|
"cache: downstream provider must implement .evaluate()");
|
|
38
39
|
}
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// A present ttlMs / maxEntries must be a positive finite integer — Infinity
|
|
41
|
+
// would pass a bare `typeof === "number" && > 0` check and give a
|
|
42
|
+
// never-expiring entry (ttlMs) or an unbounded cache (maxEntries).
|
|
43
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.ttlMs, "flag.cache: opts.ttlMs", FlagError, "flag/bad-cache");
|
|
44
|
+
var ttlMs = (typeof opts.ttlMs === "number") ? opts.ttlMs : C.TIME.seconds(30);
|
|
43
45
|
if (ttlMs < C.TIME.seconds(1)) {
|
|
44
46
|
throw new FlagError("flag/bad-cache",
|
|
45
47
|
"cache: ttlMs must be >= 1000ms - got " + ttlMs);
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
var maxEntries = (typeof opts.maxEntries === "number"
|
|
49
|
-
? Math.floor(opts.maxEntries)
|
|
50
|
-
: 10000; // entry-count default
|
|
49
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxEntries, "flag.cache: opts.maxEntries", FlagError, "flag/bad-cache");
|
|
50
|
+
var maxEntries = (typeof opts.maxEntries === "number") ? opts.maxEntries : 10000; // entry-count default
|
|
51
51
|
var auditOn = opts.audit === true; // off by default — too chatty
|
|
52
52
|
var entries = new Map();
|
|
53
53
|
var hits = 0;
|
|
@@ -212,7 +212,7 @@ function _buildCacheKey(method, url, varyHeaderValues) {
|
|
|
212
212
|
// uncacheable.
|
|
213
213
|
function _extractVaryValues(varyHeader, requestHeaders) {
|
|
214
214
|
if (typeof varyHeader !== "string" || varyHeader.length === 0) return [];
|
|
215
|
-
var names = varyHeader.split(",").map(function (s) { //
|
|
215
|
+
var names = varyHeader.split(",").map(function (s) { // RFC 9110 §12.5.5 Vary is a comma-list of field-names (token grammar); no quoted-string, so a bare split is correct
|
|
216
216
|
return s.trim().toLowerCase();
|
|
217
217
|
}).filter(function (s) { return s.length > 0; });
|
|
218
218
|
if (names.indexOf("*") !== -1) return null; // sentinel: "uncacheable"
|
|
@@ -262,7 +262,7 @@ function _evaluateStorage(method, statusCode, responseHeaders, sharedCache) {
|
|
|
262
262
|
|
|
263
263
|
// Vary: * is uncacheable per RFC 9110 §12.5.5.
|
|
264
264
|
if (typeof varyHeader === "string" && varyHeader.indexOf("*") !== -1) {
|
|
265
|
-
var trimmed = varyHeader.split(",").map(function (s) { return s.trim(); }); //
|
|
265
|
+
var trimmed = varyHeader.split(",").map(function (s) { return s.trim(); }); // RFC 9110 §12.5.5 Vary field-names; token grammar only, so a bare split is correct
|
|
266
266
|
if (trimmed.indexOf("*") !== -1) {
|
|
267
267
|
return { cacheable: false, reason: "vary-star", freshnessMs: -1, directives: directives, varyHeader: varyHeader };
|
|
268
268
|
}
|
|
@@ -319,7 +319,7 @@ function sign(msg, opts) {
|
|
|
319
319
|
// header isn't already supplied. Operators wanting to use the
|
|
320
320
|
// RFC 9530 "sha-512" identifier (SHA-512 instead of SHA3-512) supply
|
|
321
321
|
// the header themselves; the framework emits SHA3-512.
|
|
322
|
-
var coveredLower = opts.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header — opts.covered is operator-supplied component-id list (e.g. "content-digest;sf"); component identifiers are RFC 9421 §2.1 derived-field names with token-only grammar; no quoted-string
|
|
322
|
+
var coveredLower = opts.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header-token-grammar — opts.covered is operator-supplied component-id list (e.g. "content-digest;sf"); component identifiers are RFC 9421 §2.1 derived-field names with token-only grammar; no quoted-string
|
|
323
323
|
if (coveredLower.indexOf("content-digest") !== -1 &&
|
|
324
324
|
_resolveHeader(m.headers, "content-digest") === null) {
|
|
325
325
|
if (m.body == null) {
|
|
@@ -451,7 +451,7 @@ function _parseSignature(headerValue, label) {
|
|
|
451
451
|
if (headerValue.indexOf(prefix) !== 0) {
|
|
452
452
|
// Multiple signature labels can appear; comma-separated. Find the
|
|
453
453
|
// matching label.
|
|
454
|
-
var parts = headerValue.split(","); // allow:bare-split-on-quoted-header — RFC 9421 §2.4 Signature header values are `label=:b64:` form; base64 alphabet excludes `,` and the label tokens are RFC 8941 §3.3.4 sf-token (no DQUOTE in practice)
|
|
454
|
+
var parts = headerValue.split(","); // allow:bare-split-on-quoted-header-token-grammar — RFC 9421 §2.4 Signature header values are `label=:b64:` form; base64 alphabet excludes `,` and the label tokens are RFC 8941 §3.3.4 sf-token (no DQUOTE in practice)
|
|
455
455
|
for (var i = 0; i < parts.length; i++) {
|
|
456
456
|
var p = parts[i].trim();
|
|
457
457
|
if (p.indexOf(prefix) === 0) {
|
|
@@ -519,7 +519,7 @@ function verify(msg, opts) {
|
|
|
519
519
|
// If content-digest is covered, recompute and compare. RFC 9421 §B.2.5
|
|
520
520
|
// mandates that verifiers re-run the digest over the body — a stale
|
|
521
521
|
// header from a proxy would otherwise verify trivially.
|
|
522
|
-
var coveredLower = parsedInput.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header — same as sign() above: covered items are RFC 9421 §2.1 component-ids, token grammar
|
|
522
|
+
var coveredLower = parsedInput.covered.map(function (c) { return c.split(";")[0].toLowerCase(); }); // allow:bare-split-on-quoted-header-token-grammar — same as sign() above: covered items are RFC 9421 §2.1 component-ids, token grammar
|
|
523
523
|
if (coveredLower.indexOf("content-digest") !== -1) {
|
|
524
524
|
if (m.body == null) {
|
|
525
525
|
return { valid: false, reason: "content-digest-no-body" };
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
var C = require("./constants");
|
|
46
46
|
var codepointClass = require("./codepoint-class");
|
|
47
|
+
var numericBounds = require("./numeric-bounds");
|
|
47
48
|
var lazyRequire = require("./lazy-require");
|
|
48
49
|
var safeJson = require("./safe-json");
|
|
49
50
|
var safeSql = require("./safe-sql");
|
|
@@ -155,15 +156,17 @@ function create(opts) {
|
|
|
155
156
|
// The table identifier reaches SQL through b.sql, which validates +
|
|
156
157
|
// quotes it by construction on every emitted statement; _validateTableName
|
|
157
158
|
// above fails fast at create() time on a bad name.
|
|
158
|
-
var retentionDays = (typeof opts.retentionDays === "number" && opts.retentionDays > 0) // allow:numeric-opt-Infinity
|
|
159
|
+
var retentionDays = (typeof opts.retentionDays === "number" && opts.retentionDays > 0) // allow:numeric-opt-Infinity-intentional — Infinity = retain indefinitely, a supported intent
|
|
159
160
|
? opts.retentionDays : 30; // default retention days
|
|
160
161
|
var auditOn = opts.audit !== false;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
// The byte / length caps must be positive finite integers — Infinity would
|
|
163
|
+
// disable the cap and admit unbounded stored payloads / identifiers.
|
|
164
|
+
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
165
|
+
["maxPayloadBytes", "messageIdMaxLen", "sourceMaxLen"],
|
|
166
|
+
"inbox.create", InboxError, "inbox/bad-opt");
|
|
167
|
+
var maxPayloadBytes = (typeof opts.maxPayloadBytes === "number") ? opts.maxPayloadBytes : C.BYTES.kib(64);
|
|
168
|
+
var messageIdMaxLen = (typeof opts.messageIdMaxLen === "number") ? opts.messageIdMaxLen : 256; // message-id length cap
|
|
169
|
+
var sourceMaxLen = (typeof opts.sourceMaxLen === "number") ? opts.sourceMaxLen : 256; // source length cap
|
|
167
170
|
|
|
168
171
|
function _emitAudit(action, outcome, metadata) {
|
|
169
172
|
if (!auditOn) return;
|
|
@@ -346,6 +349,14 @@ function create(opts) {
|
|
|
346
349
|
}
|
|
347
350
|
|
|
348
351
|
async function sweep() {
|
|
352
|
+
// retentionDays: Infinity is the documented "retain indefinitely" intent —
|
|
353
|
+
// there is no horizon to age past, so sweep deletes nothing. (Computing a
|
|
354
|
+
// cutoff would otherwise throw: `new Date(Date.now() - Infinity)` is an
|
|
355
|
+
// Invalid Date, and "Infinity days" is not a valid Postgres interval.)
|
|
356
|
+
if (!isFinite(retentionDays)) {
|
|
357
|
+
_emitAudit("inbox.swept", "success", { deleted: 0, retentionDays: retentionDays });
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
349
360
|
var dialect = _sqlDialect(externalDb);
|
|
350
361
|
var deleted = 0;
|
|
351
362
|
await externalDb.transaction(async function (xdb) {
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
var nodeCrypto = require("node:crypto");
|
|
52
52
|
var lazyRequire = require("./lazy-require");
|
|
53
53
|
var validateOpts = require("./validate-opts");
|
|
54
|
+
var numericBounds = require("./numeric-bounds");
|
|
54
55
|
var safeBuffer = require("./safe-buffer");
|
|
55
56
|
var dkim = require("./mail-dkim");
|
|
56
57
|
var { defineClass } = require("./framework-error");
|
|
@@ -246,8 +247,11 @@ function sign(opts) {
|
|
|
246
247
|
"sign: headersToSign[" + hi + "] must be a non-empty string");
|
|
247
248
|
}
|
|
248
249
|
}
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
// A present t= timestamp must be a positive finite integer (NumericDate
|
|
251
|
+
// seconds) — an Infinity / NaN value would serialize into a malformed t= tag.
|
|
252
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.timestamp, "arc.sign: opts.timestamp", MailAuthError, "arc-sign/bad-timestamp");
|
|
253
|
+
var timestamp = (typeof opts.timestamp === "number")
|
|
254
|
+
? opts.timestamp : Math.floor(Date.now() / 1000); // Unix epoch seconds divisor
|
|
251
255
|
var auditOn = opts.audit !== false;
|
|
252
256
|
|
|
253
257
|
var keyObject;
|
|
@@ -52,6 +52,7 @@ var structuredFields = require("./structured-fields");
|
|
|
52
52
|
var markupEscape = require("./markup-escape").markupEscape;
|
|
53
53
|
var bCrypto = require("./crypto");
|
|
54
54
|
var C = require("./constants");
|
|
55
|
+
var numericBounds = require("./numeric-bounds");
|
|
55
56
|
var dkim = require("./mail-dkim");
|
|
56
57
|
var mimeParse = require("./mime-parse");
|
|
57
58
|
var safeXml = require("./parsers/safe-xml");
|
|
@@ -1575,7 +1576,11 @@ async function arcVerify(rfc822, opts) {
|
|
|
1575
1576
|
var anyFail = false;
|
|
1576
1577
|
// RFC 8617 §5.2 — operator-tunable clock skew on t= (signing
|
|
1577
1578
|
// timestamp) and x= (expiration) tags. Default 5 min.
|
|
1578
|
-
|
|
1579
|
+
// A present clockSkewMs must be a non-negative finite integer; an Infinity /
|
|
1580
|
+
// NaN / negative skew makes `amsX + skewSec < nowSec` (and the t= future
|
|
1581
|
+
// check) unsatisfiable and silently disables the RFC 8617 §5.2 expiry /
|
|
1582
|
+
// future-timestamp gate. Non-finite falls to the default.
|
|
1583
|
+
var arcClockSkewMs = numericBounds.isNonNegativeFiniteInt(opts.clockSkewMs)
|
|
1579
1584
|
? opts.clockSkewMs : C.TIME.minutes(5);
|
|
1580
1585
|
var nowSec = Math.floor(Date.now() / 1000); // Unix epoch seconds divisor
|
|
1581
1586
|
|
|
@@ -101,9 +101,9 @@ function create(opts) {
|
|
|
101
101
|
"middleware.ageGate: opts.getAge must be a function (req) -> number | null");
|
|
102
102
|
}
|
|
103
103
|
var getAge = opts.getAge;
|
|
104
|
-
var requireAge = (typeof opts.requireAge === "number" && opts.requireAge > 0) // allow:numeric-opt-Infinity — age is
|
|
104
|
+
var requireAge = (typeof opts.requireAge === "number" && opts.requireAge > 0) // allow:numeric-opt-Infinity-intentional — age threshold; an Infinity bound is fail-closed (denies everyone), never a bypass
|
|
105
105
|
? opts.requireAge : null;
|
|
106
|
-
var consentRequired = (typeof opts.consentRequired === "number" && opts.consentRequired > 0) // allow:numeric-opt-Infinity — age threshold,
|
|
106
|
+
var consentRequired = (typeof opts.consentRequired === "number" && opts.consentRequired > 0) // allow:numeric-opt-Infinity-intentional — age threshold; an Infinity bound is fail-closed (classifies everyone below-threshold), never a bypass
|
|
107
107
|
? opts.consentRequired : null;
|
|
108
108
|
var hasParentalConsent = typeof opts.hasParentalConsent === "function" ? opts.hasParentalConsent : null;
|
|
109
109
|
var skipPaths = Array.isArray(opts.skipPaths) ? opts.skipPaths.slice() : [];
|
|
@@ -351,7 +351,7 @@ function _detectSmuggling(req) {
|
|
|
351
351
|
// (RFC 9112 §6.1). Anything else is a smuggling vector or
|
|
352
352
|
// server-side decode error.
|
|
353
353
|
if (typeof te === "string" && te.length > 0) {
|
|
354
|
-
var tokens = te.toLowerCase().split(",").map(function (t) { return t.trim(); }); //
|
|
354
|
+
var tokens = te.toLowerCase().split(",").map(function (t) { return t.trim(); }); // RFC 9112 §6.1 Transfer-Encoding values (chunked / gzip / deflate / identity) are token-only; no quoted-string in the grammar, so a bare split is correct
|
|
355
355
|
var last = tokens[tokens.length - 1];
|
|
356
356
|
if (last !== "chunked") {
|
|
357
357
|
return {
|
|
@@ -237,8 +237,8 @@ async function _dispatch(req, res, basePath, bearer, opts, maxPageSize, bulkCfg)
|
|
|
237
237
|
count: pageSize,
|
|
238
238
|
sortBy: query.sortBy || null,
|
|
239
239
|
sortOrder: query.sortOrder || null,
|
|
240
|
-
attributes: query.attributes ? query.attributes.split(",") : null, // allow:bare-split-on-quoted-header — RFC 7644 §3.9 attributes/excludedAttributes are SCIM attribute paths (URN-ish identifiers); grammar excludes DQUOTE
|
|
241
|
-
excludedAttributes: query.excludedAttributes ? query.excludedAttributes.split(",") : null, // allow:bare-split-on-quoted-header — same SCIM attribute-name grammar
|
|
240
|
+
attributes: query.attributes ? query.attributes.split(",") : null, // allow:bare-split-on-quoted-header-token-grammar — RFC 7644 §3.9 attributes/excludedAttributes are SCIM attribute paths (URN-ish identifiers); grammar excludes DQUOTE
|
|
241
|
+
excludedAttributes: query.excludedAttributes ? query.excludedAttributes.split(",") : null, // allow:bare-split-on-quoted-header-token-grammar — same SCIM attribute-name grammar
|
|
242
242
|
}, ctx);
|
|
243
243
|
_writeJson(res, H.OK, {
|
|
244
244
|
schemas: [SCIM_MESSAGE_LIST],
|
|
@@ -613,7 +613,7 @@ async function tlsRptFetchPolicy(domain, opts) {
|
|
|
613
613
|
var rua = [];
|
|
614
614
|
for (var p = 0; p < pairs.length; p += 1) {
|
|
615
615
|
if (pairs[p][0] === "rua") {
|
|
616
|
-
var uris = pairs[p][1].split(","); // allow:bare-split-on-quoted-header — allow:raw-time-literal — TLS-RPT rua grammar (RFC 8460 §3): rua = tlsrpt-uri *("," tlsrpt-uri); URIs percent-encode reserved chars, no quoted-string
|
|
616
|
+
var uris = pairs[p][1].split(","); // allow:bare-split-on-quoted-header-token-grammar — allow:raw-time-literal — TLS-RPT rua grammar (RFC 8460 §3): rua = tlsrpt-uri *("," tlsrpt-uri); URIs percent-encode reserved chars, no quoted-string
|
|
617
617
|
for (var u = 0; u < uris.length; u += 1) {
|
|
618
618
|
var uri = uris[u].trim();
|
|
619
619
|
if (uri.length > 0) rua.push(uri);
|
|
@@ -1204,7 +1204,11 @@ function evaluateOcspResponse(ocspDer, opts) {
|
|
|
1204
1204
|
// gets revoked, the attacker keeps presenting the cached "good" and
|
|
1205
1205
|
// the framework keeps accepting it. requireGood postures depend on
|
|
1206
1206
|
// freshness — reject expired or future-dated responses outright.
|
|
1207
|
-
|
|
1207
|
+
// A present clockSkewMs must be a non-negative finite integer; an Infinity /
|
|
1208
|
+
// NaN / negative skew would make the staleness check `now > nextUpdate + skew`
|
|
1209
|
+
// unsatisfiable and silently disable the freshness window — accepting a
|
|
1210
|
+
// replayed pre-revocation "good" response. Non-finite falls to the default.
|
|
1211
|
+
var clockSkewMs = numericBounds.isNonNegativeFiniteInt(opts.clockSkewMs)
|
|
1208
1212
|
? opts.clockSkewMs : C.TIME.minutes(5);
|
|
1209
1213
|
var now = typeof opts.now === "number" ? opts.now : Date.now();
|
|
1210
1214
|
// thisUpdate / nextUpdate are already unix-ms NUMBERS (parseOcspResponse →
|
|
@@ -52,6 +52,7 @@ var { EventEmitter } = require("node:events");
|
|
|
52
52
|
|
|
53
53
|
var lazyRequire = require("./lazy-require");
|
|
54
54
|
var validateOpts = require("./validate-opts");
|
|
55
|
+
var numericBounds = require("./numeric-bounds");
|
|
55
56
|
var safeAsync = require("./safe-async");
|
|
56
57
|
var safeBuffer = require("./safe-buffer");
|
|
57
58
|
var bCrypto = lazyRequire(function () { return require("./crypto"); });
|
|
@@ -217,16 +218,18 @@ function connect(target, opts) {
|
|
|
217
218
|
"wsClient.connect: subprotocols[" + sp + "] must be a non-empty string");
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
var
|
|
229
|
-
|
|
221
|
+
// These are keepalive/timeout intervals and inbound-OOM caps; a present
|
|
222
|
+
// value must be a positive finite integer. A bare `typeof === "number" && > 0`
|
|
223
|
+
// check accepts Infinity, which silently disables the cap (a malicious server
|
|
224
|
+
// could then send an unbounded message/frame, or stall the handshake forever).
|
|
225
|
+
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
226
|
+
["pingMs", "pongMs", "maxMessageBytes", "maxFrameBytes", "handshakeTimeoutMs"],
|
|
227
|
+
"wsClient.connect", WsClientError, "ws-client/bad-opt");
|
|
228
|
+
var pingMs = (typeof opts.pingMs === "number") ? opts.pingMs : DEFAULT_PING_MS;
|
|
229
|
+
var pongMs = (typeof opts.pongMs === "number") ? opts.pongMs : DEFAULT_PONG_MS;
|
|
230
|
+
var maxMessageBytes = (typeof opts.maxMessageBytes === "number") ? opts.maxMessageBytes : DEFAULT_MAX_BYTES;
|
|
231
|
+
var maxFrameBytes = (typeof opts.maxFrameBytes === "number") ? opts.maxFrameBytes : DEFAULT_MAX_FRAME;
|
|
232
|
+
var handshakeTimeoutMs = (typeof opts.handshakeTimeoutMs === "number") ? opts.handshakeTimeoutMs : DEFAULT_HANDSHAKE_TIMEOUT_MS;
|
|
230
233
|
|
|
231
234
|
var reconnectOpts = _normaliseReconnect(opts.reconnect);
|
|
232
235
|
var permessageDeflate = opts.permessageDeflate !== false;
|
|
@@ -281,14 +284,18 @@ function _normaliseReconnect(input) {
|
|
|
281
284
|
"wsClient.connect: reconnect must be false / null / object");
|
|
282
285
|
}
|
|
283
286
|
validateOpts(input, ["maxAttempts", "baseMs", "maxMs", "enabled"], "wsClient.connect.reconnect");
|
|
287
|
+
// baseMs / maxMs are backoff durations; a present value must be a positive
|
|
288
|
+
// finite integer (an Infinity base would stall the first reconnect forever).
|
|
289
|
+
numericBounds.requireAllPositiveFiniteIntIfPresent(input, ["baseMs", "maxMs"],
|
|
290
|
+
"wsClient.connect.reconnect", WsClientError, "ws-client/bad-reconnect-opt");
|
|
284
291
|
return {
|
|
285
292
|
enabled: input.enabled !== false,
|
|
286
|
-
maxAttempts:
|
|
293
|
+
// maxAttempts: Infinity is a deliberate "reconnect indefinitely" intent, so
|
|
294
|
+
// a non-finite value is accepted here (unlike the bounded caps above).
|
|
295
|
+
maxAttempts: (typeof input.maxAttempts === "number" && input.maxAttempts >= 0) // allow:numeric-opt-Infinity-intentional — Infinity = reconnect indefinitely, a supported intent
|
|
287
296
|
? input.maxAttempts : DEFAULT_RECONNECT_MAX_ATTEMPTS,
|
|
288
|
-
baseMs: (typeof input.baseMs === "number"
|
|
289
|
-
|
|
290
|
-
maxMs: (typeof input.maxMs === "number" && input.maxMs > 0) // allow:numeric-opt-Infinity
|
|
291
|
-
? input.maxMs : DEFAULT_RECONNECT_MAX_MS,
|
|
297
|
+
baseMs: (typeof input.baseMs === "number") ? input.baseMs : DEFAULT_RECONNECT_BASE_MS,
|
|
298
|
+
maxMs: (typeof input.maxMs === "number") ? input.maxMs : DEFAULT_RECONNECT_MAX_MS,
|
|
292
299
|
};
|
|
293
300
|
}
|
|
294
301
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.36",
|
|
4
|
+
"date": "2026-06-27",
|
|
5
|
+
"headline": "Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside)",
|
|
6
|
+
"summary": "The codebase-patterns guard class that allows a bare comma/semicolon split on token-only RFC header grammars was re-verified from scratch and renamed to a descriptive token, with its old name recorded as retired. Each live marked site (RRULE, RFC 9421 component identifiers, TLS-RPT rua, SCIM attribute paths) was re-read and confirmed to split a grammar with no quoted-string members. Five marker comments that suppressed nothing were removed or turned into plain explanatory comments. No runtime code, public API, or wire format changed.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Detectors",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "Bare token-only header-split suppression class re-verified, renamed, and pruned of inert markers",
|
|
13
|
+
"body": "Each marked bare `.split(\",\")` / `.split(\";\")` on an RFC header value was re-read and confirmed to operate on a token-only grammar (no quoted-string members, so a quote-aware splitter is unnecessary): RFC 5545 RRULE, RFC 9421 signature component identifiers, RFC 8460 TLS-RPT rua, and RFC 7644 SCIM attribute paths. The guard class was renamed to a descriptive token and its old name added to the retired-token set. Five marker comments that the detector never actually evaluated (two sat on a date-normalizing `.replace`, three in header parsers the guard intentionally does not scan) were removed or converted to plain comments. This is test-suite tooling plus source-comment text; no shipped framework behavior changed."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.37",
|
|
4
|
+
"date": "2026-06-27",
|
|
5
|
+
"headline": "Several numeric options that silently accepted a non-finite value — disabling a clock-skew / freshness check or a resource cap — now reject it, so an `Infinity` skew or limit can no longer turn off the protection it configures",
|
|
6
|
+
"summary": "A number of configuration options validated a numeric value with a bare `typeof === \"number\" && value >= 0` check, which accepts `Infinity`. Where the value is a clock-skew tolerance or a resource cap, an `Infinity` (or `NaN`) silently disabled the very check it tunes: a CWT / OCSP-staple / ARC clock-skew of `Infinity` made the expiry, freshness, and expiration comparisons unsatisfiable (an expired token / a replayed pre-revocation \"good\" response / an expired ARC seal would be accepted); a WebSocket-client `maxMessageBytes` / `maxFrameBytes` / `handshakeTimeoutMs` of `Infinity` disabled the inbound-OOM and stalled-handshake defenses; and inbox / flag-cache / audit-chain size and count caps of `Infinity` admitted unbounded data. These options now route through the finite-bounds validator: a present non-finite value is refused at the entry point (or falls back to the safe default on the result-returning paths). Options where an unbounded value is a deliberate intent — reconnect \"retry indefinitely\", inbox \"retain indefinitely\" — continue to accept `Infinity`.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Security",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "A non-finite clock-skew no longer disables CWT / OCSP / ARC time checks",
|
|
13
|
+
"body": "b.cwt.verify, the OCSP-staple freshness check in b.network.tls, and b.mail.arc.verify each took an operator clock-skew tolerance validated as `typeof === \"number\" && >= 0`, which accepts `Infinity`. Because each check is of the form `now > deadline + skew`, a skew of `Infinity` made it unsatisfiable and silently turned the check off: an expired CWT verified, a stale (post-nextUpdate) OCSP \"good\" response — the exact reply an attacker replays after the certificate is revoked — was accepted, and an expired ARC seal passed. A present clock-skew that is not a non-negative finite integer is now refused (b.cwt.verify throws cwt/bad-clock-skew; the OCSP and ARC paths fall back to their safe default). Regression tests assert an expired token / stale staple / expired ARC seal is still rejected when the skew is `Infinity`."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "WebSocket-client inbound caps can no longer be disabled by an Infinity value",
|
|
17
|
+
"body": "b.wsClient.connect validated maxMessageBytes, maxFrameBytes, and handshakeTimeoutMs (and the ping/pong keepalive intervals) with a bare numeric check that accepted `Infinity`, which disabled the inbound-message and frame size limits — the defenses against a malicious server sending an unbounded message — and the handshake timeout. A present non-finite value for these is now refused at connect time. The reconnect maxAttempts still accepts `Infinity` (a deliberate \"reconnect indefinitely\" intent)."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "Inbox, flag-cache, and audit-chain caps reject a non-finite limit",
|
|
21
|
+
"body": "The inbox maxPayloadBytes / messageIdMaxLen / sourceMaxLen caps, the flag-cache ttlMs / maxEntries, and the audit-chain partition fan-out cap each accepted `Infinity`, disabling the cap (unbounded stored payloads, a never-expiring or unbounded cache, unbounded fan-out). These now require a positive finite integer — refused at create time, or clamped to the bounded default on the result-returning verify path. The inbox retentionDays still accepts `Infinity` (retain indefinitely)."
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -319,7 +319,7 @@ var VALID_ALLOW_CLASSES = {
|
|
|
319
319
|
"backup-adapter-storage-without-posture-check": 1,
|
|
320
320
|
"bare-canonicalize-walk": 1,
|
|
321
321
|
"bare-error-throw": 1,
|
|
322
|
-
"bare-split-on-quoted-header": 1,
|
|
322
|
+
"bare-split-on-quoted-header-token-grammar": 1,
|
|
323
323
|
"console-direct": 1,
|
|
324
324
|
"deny-path-hardcoded-response": 1,
|
|
325
325
|
"duplicate-regex": 1,
|
|
@@ -340,7 +340,7 @@ var VALID_ALLOW_CLASSES = {
|
|
|
340
340
|
"list-without-pagination": 1,
|
|
341
341
|
"math-random-noncrypto-jitter-sampling": 1,
|
|
342
342
|
"no-number-money-arithmetic": 1,
|
|
343
|
-
"numeric-opt-Infinity": 1,
|
|
343
|
+
"numeric-opt-Infinity-intentional": 1,
|
|
344
344
|
"primitive-unreachable": 1,
|
|
345
345
|
"process-exit-operator-optin": 1,
|
|
346
346
|
"raw-byte-literal": 1,
|
|
@@ -389,6 +389,8 @@ var RETIRED_ALLOW_TOKENS = {
|
|
|
389
389
|
"dynamic-require": "renamed to 'dynamic-require-operator-module' (2026-06-26 re-verify pass) — every site loads an operator-supplied path (migration / seed / extensibility entry, centralized in module-loader.js) or is diagnostic message text; re-verify before reusing",
|
|
390
390
|
"raw-hash-compare": "renamed to 'raw-hash-compare-nonsecret-tag' (2026-06-26 re-verify pass) — the one site compares a data-residency region tag with ===, not a secret hash; re-verify before reusing",
|
|
391
391
|
"seal-without-aad": "renamed to 'seal-without-aad-by-design' (2026-06-26 re-verify pass) — both sites intentionally seal without AAD (a non-regulated plain-mode table whose AAD is enforced by the posture sealEnvelopeFloor where required, and a throwaway vault-readiness probe sentinel); re-verify before reusing",
|
|
392
|
+
"bare-split-on-quoted-header": "renamed to 'bare-split-on-quoted-header-token-grammar' (2026-06-26 re-verify pass) — every live marker splits an RFC token-only header grammar with no quoted-string members (RRULE / RFC 9421 component-ids / TLS-RPT rua / SCIM attribute paths), so a bare comma/semicolon split is correct; re-verify the grammar before reusing. The re-verify also found 5 markers were inert (detector pre-filter skips their file, or the line is a .replace not a split) and cleared them",
|
|
393
|
+
"numeric-opt-Infinity": "renamed to 'numeric-opt-Infinity-intentional' (2026-06-27 re-verify pass) — only the markers where Infinity is genuinely SAFE remain: clamped to a max (agent-posture-chain maxHopCount), fail-closed (age-gate requireAge / consentRequired), or a deliberate unbounded intent (inbox retentionDays = retain indefinitely, ws-client reconnect maxAttempts = retry forever). The re-verify found 13 sites where Infinity SILENTLY DISABLED a cap or security check (clock-skew → expiry/OCSP-freshness/ARC-expiry; ws-client maxMessageBytes/maxFrameBytes/handshakeTimeoutMs; inbox/flag-cache/audit-chain caps; mail-arc-sign timestamp) — those now route through numeric-bounds (throw / safe-default on non-finite) and lost the marker. re-verify each remaining site before reusing",
|
|
392
394
|
};
|
|
393
395
|
|
|
394
396
|
function testNoRetiredAllowTokenReRegistered() {
|
|
@@ -693,13 +695,13 @@ function testNoRawTimeLiterals() {
|
|
|
693
695
|
}
|
|
694
696
|
|
|
695
697
|
function testNumericOptsValidate() {
|
|
696
|
-
// class: numeric-opt-Infinity
|
|
698
|
+
// class: numeric-opt-Infinity-intentional
|
|
697
699
|
var matches = _scan(
|
|
698
700
|
/typeof\s+opts\.\w+\s*===\s*"number"\s*&&\s*opts\.\w+\s*>=?\s*0\b/);
|
|
699
701
|
// numeric-bounds itself contains the helper definitions that look
|
|
700
702
|
// like the pattern.
|
|
701
703
|
matches = matches.filter(function (m) { return m.file !== "lib/numeric-bounds.js"; });
|
|
702
|
-
matches = _filterMarkers(matches, "numeric-opt-Infinity");
|
|
704
|
+
matches = _filterMarkers(matches, "numeric-opt-Infinity-intentional");
|
|
703
705
|
_report("numeric opts route through lib/numeric-bounds (or have an allow marker)",
|
|
704
706
|
matches);
|
|
705
707
|
}
|
|
@@ -10320,7 +10322,7 @@ function testNoBoolStringCoerceShape() {
|
|
|
10320
10322
|
//
|
|
10321
10323
|
// Surfaced 2026-05-11 — `b.cdnCacheControl.parse` did this.
|
|
10322
10324
|
function testNoBareCommaSplitOnQuotedHeader() {
|
|
10323
|
-
// class: bare-split-on-quoted-header
|
|
10325
|
+
// class: bare-split-on-quoted-header-token-grammar
|
|
10324
10326
|
var files = _libFiles();
|
|
10325
10327
|
var bad = [];
|
|
10326
10328
|
for (var fi = 0; fi < files.length; fi++) {
|
|
@@ -10342,7 +10344,7 @@ function testNoBareCommaSplitOnQuotedHeader() {
|
|
|
10342
10344
|
});
|
|
10343
10345
|
}
|
|
10344
10346
|
}
|
|
10345
|
-
bad = _filterMarkers(bad, "bare-split-on-quoted-header");
|
|
10347
|
+
bad = _filterMarkers(bad, "bare-split-on-quoted-header-token-grammar");
|
|
10346
10348
|
_report("RFC structured-fields parser must use quote-aware top-level splitter, not bare `.split(\",\") / .split(\";\")` (RFC 8941 §3.3.3 quoted-string values can contain delimiter chars — cdn-cache-control.parse bug class)",
|
|
10347
10349
|
bad);
|
|
10348
10350
|
}
|
|
@@ -105,6 +105,34 @@ async function testRobustTagAndMalformedClaims() {
|
|
|
105
105
|
check("verify: malformed (non-numeric) nbf refused", e2 && e2.code === "cwt/malformed-claim");
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
async function testClockSkewInfinityRejected() {
|
|
109
|
+
// A non-finite clockSkewSec must be a config error, NOT a silent
|
|
110
|
+
// expiry-disable: the check is `now > exp + skew`, so skew === Infinity
|
|
111
|
+
// makes it `now > Infinity` (always false) and ANY expired token would
|
|
112
|
+
// verify. A present-but-invalid skew (Infinity / NaN / negative /
|
|
113
|
+
// non-integer) is refused at the entry point.
|
|
114
|
+
var now = _now();
|
|
115
|
+
var expired = await b.cwt.sign({ exp: now - 100000 }, { alg: "ES256", privateKey: EC.privateKey });
|
|
116
|
+
var errInf = null;
|
|
117
|
+
try {
|
|
118
|
+
await b.cwt.verify(expired, { algorithms: ["ES256"], publicKey: EC.publicKey, clockSkewSec: Infinity });
|
|
119
|
+
} catch (e) { errInf = e; }
|
|
120
|
+
check("verify: clockSkewSec Infinity refused (does not disable expiry)",
|
|
121
|
+
errInf && errInf.code === "cwt/bad-clock-skew");
|
|
122
|
+
|
|
123
|
+
var errNeg = null;
|
|
124
|
+
try {
|
|
125
|
+
await b.cwt.verify(expired, { algorithms: ["ES256"], publicKey: EC.publicKey, clockSkewSec: -1 });
|
|
126
|
+
} catch (e) { errNeg = e; }
|
|
127
|
+
check("verify: negative clockSkewSec refused", errNeg && errNeg.code === "cwt/bad-clock-skew");
|
|
128
|
+
|
|
129
|
+
// Absent skew still applies the default (marginally-expired tolerated),
|
|
130
|
+
// and a valid finite skew still works — the fix must not regress those.
|
|
131
|
+
var justExpired = await b.cwt.sign({ exp: now - 30 }, { alg: "ES256", privateKey: EC.privateKey });
|
|
132
|
+
var ok = await b.cwt.verify(justExpired, { algorithms: ["ES256"], publicKey: EC.publicKey, clockSkewSec: 60 });
|
|
133
|
+
check("verify: a valid finite skew still tolerates a marginally-expired token", ok.claims.exp === now - 30);
|
|
134
|
+
}
|
|
135
|
+
|
|
108
136
|
async function testValidation() {
|
|
109
137
|
var bad = null;
|
|
110
138
|
try { await b.cwt.sign({ exp: "not-a-number" }, { alg: "ES256", privateKey: EC.privateKey }); } catch (e) { bad = e; }
|
|
@@ -124,6 +152,7 @@ async function run() {
|
|
|
124
152
|
await testIssuerAudience();
|
|
125
153
|
await testTaggedAndTamper();
|
|
126
154
|
await testRobustTagAndMalformedClaims();
|
|
155
|
+
await testClockSkewInfinityRejected();
|
|
127
156
|
await testValidation();
|
|
128
157
|
}
|
|
129
158
|
|
|
@@ -496,6 +496,12 @@ function run() {
|
|
|
496
496
|
function () { b.flag.cache({}); }, /must implement/);
|
|
497
497
|
rejects("cache: ttlMs too small",
|
|
498
498
|
function () { b.flag.cache(memProvider, { ttlMs: 100 }); }, /ttlMs/);
|
|
499
|
+
// A non-finite ttlMs / maxEntries must be refused (Infinity would otherwise
|
|
500
|
+
// give a never-expiring entry / an unbounded cache, not be clamped).
|
|
501
|
+
rejects("cache: maxEntries Infinity refused (unbounded cache)",
|
|
502
|
+
function () { b.flag.cache(memProvider, { ttlMs: 5000, maxEntries: Infinity }); }, /maxEntries|finite/);
|
|
503
|
+
rejects("cache: ttlMs Infinity refused",
|
|
504
|
+
function () { b.flag.cache(memProvider, { ttlMs: Infinity }); }, /ttlMs|finite/);
|
|
499
505
|
|
|
500
506
|
var cachedProvider = b.flag.cache(memProvider, { ttlMs: 5000, maxEntries: 50 });
|
|
501
507
|
check("cache: kind", cachedProvider.kind === "cache:memory");
|
|
@@ -118,6 +118,12 @@ async function run() {
|
|
|
118
118
|
var fake = _makeFakeExternalDb();
|
|
119
119
|
rejects("inbox.create: bad table name",
|
|
120
120
|
function () { b.inbox.create({ externalDb: fake.db, table: "bad-name" }); }, /not a safe SQL identifier/);
|
|
121
|
+
// A non-finite byte/length cap must be refused — Infinity would disable the
|
|
122
|
+
// cap and admit unbounded stored payloads / identifiers.
|
|
123
|
+
rejects("inbox.create: maxPayloadBytes Infinity refused",
|
|
124
|
+
function () { b.inbox.create({ externalDb: fake.db, table: "t", maxPayloadBytes: Infinity }); }, /maxPayloadBytes|finite/);
|
|
125
|
+
rejects("inbox.create: messageIdMaxLen Infinity refused",
|
|
126
|
+
function () { b.inbox.create({ externalDb: fake.db, table: "t", messageIdMaxLen: Infinity }); }, /messageIdMaxLen|finite/);
|
|
121
127
|
|
|
122
128
|
var inbox = b.inbox.create({
|
|
123
129
|
externalDb: fake.db,
|
|
@@ -182,6 +188,16 @@ async function run() {
|
|
|
182
188
|
|
|
183
189
|
var deleted = await inbox.sweep();
|
|
184
190
|
check("inbox.sweep: returns deleted count", typeof deleted === "number");
|
|
191
|
+
|
|
192
|
+
// retentionDays: Infinity means "retain indefinitely" — sweep() must be a
|
|
193
|
+
// no-op (delete nothing), not compute an invalid horizon. Before the fix the
|
|
194
|
+
// sqlite path threw RangeError on `new Date(now - Infinity).toISOString()`.
|
|
195
|
+
var foreverInbox = b.inbox.create({
|
|
196
|
+
externalDb: fake.db, table: "forever_inbox", audit: false, retentionDays: Infinity,
|
|
197
|
+
});
|
|
198
|
+
await foreverInbox.declareSchema(fake.xdb);
|
|
199
|
+
var foreverDeleted = await foreverInbox.sweep();
|
|
200
|
+
check("inbox.sweep: retentionDays Infinity retains all (no-op, no throw)", foreverDeleted === 0);
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
module.exports = { run: run };
|
|
@@ -367,6 +367,36 @@ async function testArcVerifyBadSignatures() {
|
|
|
367
367
|
rv.hops[0].asResult !== "pass");
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
async function testArcInfinityClockSkewDoesNotDisableExpiry() {
|
|
371
|
+
// RFC 8617 §5.2 — a past x= (expiration) MUST be rejected with operator
|
|
372
|
+
// skew tolerance. The expiry gate is `amsX + skewSec < nowSec`; a non-finite
|
|
373
|
+
// clockSkewMs makes skewSec === Infinity, so `amsX + Infinity < nowSec` is
|
|
374
|
+
// always false and the expiry check is silently disabled. A present
|
|
375
|
+
// non-finite skew must fall back to the safe default instead. RED before the
|
|
376
|
+
// fix: the expired x= slips through and amsErrors carries a signature error,
|
|
377
|
+
// not the x-expired fault.
|
|
378
|
+
var nowSec = Math.floor(Date.now() / 1000);
|
|
379
|
+
var expiredX = nowSec - 86400; // expired a day ago
|
|
380
|
+
var pastT = nowSec - 90000;
|
|
381
|
+
var msg = "ARC-Seal: i=1; a=rsa-sha256; cv=none; d=example.com; s=arc; b=AAAA\r\n" +
|
|
382
|
+
"ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=arc; t=" + pastT + "; x=" + expiredX + "; bh=AAAA; h=from; b=AAAA\r\n" +
|
|
383
|
+
"ARC-Authentication-Results: i=1; example.com; spf=pass\r\n" +
|
|
384
|
+
"From: alice@example.com\r\nTo: bob@example.com\r\n\r\nbody\r\n";
|
|
385
|
+
var dnsLookup = async function (qname) {
|
|
386
|
+
if (qname === "arc._domainkey.example.com") {
|
|
387
|
+
var nodeCrypto = require("crypto");
|
|
388
|
+
var pair = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
|
|
389
|
+
var spki = pair.publicKey.export({ type: "spki", format: "der" });
|
|
390
|
+
return [["v=DKIM1; k=rsa; p=" + spki.toString("base64")]];
|
|
391
|
+
}
|
|
392
|
+
var err = new Error("ENOTFOUND"); err.code = "ENOTFOUND"; throw err;
|
|
393
|
+
};
|
|
394
|
+
var rv = await b.mail.arc.verify(msg, { dnsLookup: dnsLookup, clockSkewMs: Infinity });
|
|
395
|
+
var amsErrs = ((rv.hops && rv.hops[0] && rv.hops[0].amsErrors) || []).join(" ; ");
|
|
396
|
+
check("ARC Infinity skew: expired x= still enforced (expiry not disabled)",
|
|
397
|
+
/x-expired/.test(amsErrs));
|
|
398
|
+
}
|
|
399
|
+
|
|
370
400
|
function _arcHopHeaders(i, cv) {
|
|
371
401
|
// Synthetic ARC headers — signatures are dummy; the cv= edge tests
|
|
372
402
|
// exercise the chain-rule validator, not the signature verifier.
|
|
@@ -615,6 +645,10 @@ function testArcSignChain() {
|
|
|
615
645
|
_rejects("arc.sign: bad instance (0)",
|
|
616
646
|
function () { b.mail.arc.sign({ rfc822: rfc822, instance: 0, authservId: "x", domain: "x", selector: "x", privateKey: arcKeyPem, algorithm: "rsa-sha256", cv: "none", authResults: "spf=pass" }); },
|
|
617
647
|
/instance must be/);
|
|
648
|
+
// A non-finite t= timestamp must be refused, not floored into a malformed tag.
|
|
649
|
+
_rejects("arc.sign: timestamp Infinity refused",
|
|
650
|
+
function () { b.mail.arc.sign({ rfc822: rfc822, instance: 1, authservId: "x", domain: "x", selector: "x", privateKey: arcKeyPem, algorithm: "rsa-sha256", cv: "none", authResults: "spf=pass", timestamp: Infinity }); },
|
|
651
|
+
/timestamp|finite/);
|
|
618
652
|
}
|
|
619
653
|
|
|
620
654
|
// ---- DMARCbis (B1) — psd= / np= / org-domain via PSL ----
|
|
@@ -1474,6 +1508,7 @@ async function run() {
|
|
|
1474
1508
|
await testArcVerifyMissing();
|
|
1475
1509
|
await testArcVerifyNone();
|
|
1476
1510
|
await testArcVerifyBadSignatures();
|
|
1511
|
+
await testArcInfinityClockSkewDoesNotDisableExpiry();
|
|
1477
1512
|
await testArcVerifyDuplicateInstance();
|
|
1478
1513
|
await testArcVerifyNonContiguous();
|
|
1479
1514
|
await testArcVerifyTooManyHops();
|
|
@@ -175,11 +175,39 @@ function testAcceptsFreshNoNextUpdate() {
|
|
|
175
175
|
check("no-nextUpdate fresh: certStatus good", rv.certStatus === "good");
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// A non-finite clockSkewMs must NOT disable the freshness window. The stale
|
|
179
|
+
// check is `now > nextUpdate + skew`; skew === Infinity makes it `now >
|
|
180
|
+
// Infinity` (always false), so a STALE (past-nextUpdate) response — exactly
|
|
181
|
+
// the pre-revocation "good" reply an attacker replays after the cert is
|
|
182
|
+
// revoked — would be accepted. A present-but-non-finite skew falls back to
|
|
183
|
+
// the safe default instead of being honored. RED before the fix: rv.ok===true.
|
|
184
|
+
function testInfinityClockSkewDoesNotDisableFreshness() {
|
|
185
|
+
var fx = _buildSignedOcsp({
|
|
186
|
+
thisUpdateMs: FIXED_NOW - 3 * 86400000, // 3 days ago
|
|
187
|
+
nextUpdateMs: FIXED_NOW - 2 * 86400000, // 2 days ago → STALE
|
|
188
|
+
});
|
|
189
|
+
var rv = b.network.tls.ocsp.evaluate(fx.der, {
|
|
190
|
+
issuerPem: fx.issuerPem, serialHex: fx.serialHex, now: FIXED_NOW,
|
|
191
|
+
clockSkewMs: Infinity,
|
|
192
|
+
});
|
|
193
|
+
check("Infinity skew: stale response still rejected (freshness not disabled)", rv.ok === false);
|
|
194
|
+
check("Infinity skew: rejected for the STALE reason",
|
|
195
|
+
/nextUpdate|stale/i.test((rv.errors || []).join(" ; ")));
|
|
196
|
+
// A negative skew is likewise treated as invalid → safe default, so the
|
|
197
|
+
// stale response stays rejected.
|
|
198
|
+
var rvNeg = b.network.tls.ocsp.evaluate(fx.der, {
|
|
199
|
+
issuerPem: fx.issuerPem, serialHex: fx.serialHex, now: FIXED_NOW,
|
|
200
|
+
clockSkewMs: -1,
|
|
201
|
+
});
|
|
202
|
+
check("negative skew: stale response still rejected", rvNeg.ok === false);
|
|
203
|
+
}
|
|
204
|
+
|
|
178
205
|
async function run() {
|
|
179
206
|
testRejectsStaleResponse();
|
|
180
207
|
testAcceptsFreshResponse();
|
|
181
208
|
testRejectsFutureThisUpdate();
|
|
182
209
|
testAcceptsFreshNoNextUpdate();
|
|
210
|
+
testInfinityClockSkewDoesNotDisableFreshness();
|
|
183
211
|
}
|
|
184
212
|
|
|
185
213
|
module.exports = { run: run };
|
|
@@ -205,6 +205,22 @@ async function _runTests() {
|
|
|
205
205
|
function () { b.wsClient.connect("ws://localhost:1", { reconnect: 42 }); },
|
|
206
206
|
/reconnect must be/);
|
|
207
207
|
|
|
208
|
+
// ---- non-finite resource caps ----
|
|
209
|
+
// maxMessageBytes / maxFrameBytes / handshakeTimeoutMs are inbound-OOM and
|
|
210
|
+
// hang defenses. An Infinity value passes a bare `typeof === "number" && > 0`
|
|
211
|
+
// check and silently DISABLES the cap (a malicious server can then send an
|
|
212
|
+
// unbounded message / frame, or stall the handshake forever). A present
|
|
213
|
+
// non-finite value is refused at connect time.
|
|
214
|
+
rejects("connect: maxMessageBytes Infinity refused (OOM cap not disabled)",
|
|
215
|
+
function () { b.wsClient.connect("ws://localhost:1", { maxMessageBytes: Infinity }); },
|
|
216
|
+
/maxMessageBytes|finite/);
|
|
217
|
+
rejects("connect: maxFrameBytes Infinity refused",
|
|
218
|
+
function () { b.wsClient.connect("ws://localhost:1", { maxFrameBytes: Infinity }); },
|
|
219
|
+
/maxFrameBytes|finite/);
|
|
220
|
+
rejects("connect: handshakeTimeoutMs Infinity refused",
|
|
221
|
+
function () { b.wsClient.connect("ws://localhost:1", { handshakeTimeoutMs: Infinity }); },
|
|
222
|
+
/handshakeTimeoutMs|finite/);
|
|
223
|
+
|
|
208
224
|
// ---- happy path: connect + send + echo ----
|
|
209
225
|
var server = await _makeServer({});
|
|
210
226
|
var port = server.address().port;
|
package/package.json
CHANGED