Skip to content

Commit 17f798f

Browse files
authored
Changes: draft v4.0.2 release notes (#2154)
1 parent 8d60da6 commit 17f798f

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

Changes

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,158 @@ Changes
44
v4 has many incompatibilities with v3. To see the full list of differences between
55
v3 and v4, please read the [Changes-v4.md file](./Changes-v4.md). Coding Agents should read [MIGRATION-v4.md](./MICRATION-v4.md)
66

7+
v4.0.2 UNRELEASED
8+
* [jwk] BREAKING: `jwk.RegisterKeyImporter` now takes a
9+
`jwk.KeyImporter[T]` interface rather than a bare typed
10+
function. The v4.0.0 release exported `KeyImporter` and
11+
`KeyImportFunc` but neither type was reachable from the
12+
registration call, leaving `RegisterKeyImporter` inconsistent
13+
with every other extension hook in `jwk` (which all anchor on
14+
an interface plus a `FooFunc` adapter). The unused exported
15+
types were the leftover surface of the original design that
16+
didn't make it through to the registration call.
17+
18+
Correcting this is a compile-time break, but at the time of
19+
this release the only known consumers of `RegisterKeyImporter`
20+
are the `github.com/jwx-go/*` extension modules, which have
21+
been updated in lock-step. Landing the intended shape now,
22+
while the surface is still effectively private, costs less
23+
than carrying the inconsistency through the rest of v4's
24+
lifetime.
25+
26+
Wrap typed importer functions in `jwk.KeyImportFunc[T](...)`:
27+
28+
// before
29+
jwk.RegisterKeyImporter(func(src *MyKey) (jwk.Key, error) { ... })
30+
31+
// after
32+
jwk.RegisterKeyImporter(jwk.KeyImportFunc[*MyKey](func(src *MyKey) (jwk.Key, error) { ... }))
33+
34+
`KeyImporter[T any]` is now a generic interface; struct types
35+
implementing `Import(T) (jwk.Key, error)` can be passed
36+
directly without the adapter. (#2152)
37+
38+
* [jws] Coordinated RFC 7797 `b64=false` handling pass:
39+
`jws.Verify` rejects payloads with `b64=false` unless `b64`
40+
is also listed in `crit`; `jws.Sign` auto-declares `b64` in
41+
`crit` when emitting `b64=false`; `Message.MarshalJSON`
42+
honors `b64=false` instead of silently re-encoding;
43+
`jws.VerifyCompactFast` refuses any compact JWS carrying
44+
`b64` (the fast path doesn't process extension headers); and
45+
`b64` is now declared as a typed boolean header field rather
46+
than handled ad-hoc. (#2080, #2086, #2101, #2103, #2105)
47+
48+
* [jws] Reject malformed general-form JSON-serialized JWS:
49+
inputs with a top-level `header` member as a sibling of
50+
`signatures` are rejected (the spec only permits `header`
51+
inside per-signature objects), as are inputs whose `protected`
52+
member is a literal JSON object instead of a base64url-encoded
53+
string. (#2088, #2107)
54+
55+
* [jws] `jws.AlgorithmsForKey` failures from unclassifiable
56+
keys are now wrapped in a typed sentinel so callers can
57+
branch on "couldn't categorize this key" without string
58+
matching the error message. (#2109)
59+
60+
* [jws] Verify error-shape consistency: `VerifyCompactFast`
61+
refusals now match the `jws.VerifyError()` taxonomy used by
62+
the slow path, fan-out verify errors name the loose
63+
`WithKeySet` options that were tried, multi-signature `b64`
64+
mismatches name the offending signature index and conflicting
65+
value, and the compact `b64=false`+payload-contains-`.` error
66+
references RFC 7797 §5.2 and points at `WithDetachedPayload`.
67+
(#2082, #2084, #2113)
68+
69+
* [jws][jwe] `jws.VerifyMessage` and `jwe.DecryptMessage`
70+
observe context cancellation between loop iterations rather
71+
than only at boundaries. Long fan-out verify/decrypt loops
72+
now respond to a cancelled context promptly. (#2111, #2116)
73+
74+
* [jwe] Reject PBES2 messages whose `p2c` (iteration count)
75+
does not parse cleanly into int64 or violates the configured
76+
bound. The error now names the violated bound (min vs max)
77+
instead of the generic "out of range". (#2118)
78+
79+
* [jwe] `jwe.WithKey()` validates the alg-vs-key shape at
80+
option construction time rather than during encryption, so
81+
misuse surfaces at the call site instead of inside the
82+
encrypt loop. (#2120)
83+
84+
* [jwe] Decrypt error-path cleanup: per-key failures from
85+
key-set providers are surfaced via `errors.Join` so each
86+
underlying error remains inspectable; the joined error count
87+
is bounded to keep diagnostics readable; the redundant outer
88+
`Decrypt:` prefix is dropped; and the compression-cap error
89+
names the "decompressed" payload, the option, and the size.
90+
(#2122, #2124, #2126)
91+
92+
* [jwe] Add `jwe.WithDisabledKeyAlgorithms(...)` as a global
93+
policy hook (`jwe.Configure(...)` or per-call) for refusing
94+
specific key-management algorithms across all `jwe.Decrypt`
95+
calls. (#2128)
96+
97+
* [jwk] Stop duplicating JWK fields at the JWKS top level on
98+
parse. A JWKS whose top-level object carries fields with the
99+
same names as JWK members no longer copies those values into
100+
every key in `keys`. (#2132)
101+
102+
* [jwk] The probe pass tolerates duplicate JSON field names in
103+
the input. Previously the probe rejected duplicates even
104+
though the second-pass unmarshal accepts them, so valid
105+
inputs that round-tripped through tolerant decoders were
106+
inconsistently rejected at parse time. (#2138)
107+
108+
* [jwk] A custom `jwk.KeyParser` returning `(nil, nil)` now
109+
signals "continue to the next parser" rather than
110+
"successfully produced a nil key". Callers no longer end up
111+
with a nil `jwk.Key` from a successful parse when an
112+
extension returns the empty pair. (#2139)
113+
114+
* [jwk] Stream the JWKS `keys` array with a cap-before-allocate
115+
strategy. Inputs respect `WithMaxKeys` before any unbounded
116+
slice growth, and bounded-size JWKS no longer over-allocate
117+
based on attacker-controlled length hints. (#2136)
118+
119+
* [jwk] Additional typed errors on parse and export paths:
120+
`UnknownKeyTypeError{KeyType string}` (when input has no
121+
usable `kty` or names an unregistered family),
122+
`KeyTypeMismatchError` surfaced from `jwk.Export` when the
123+
source key doesn't match the destination type, and
124+
`ParseError` sentinel wrapping on `jwk.ParseKey` /
125+
`jwk.ParseKeyAs`. All follow the existing zero-value-struct +
126+
`errors.Is` / `errors.AsType[T]` pattern.
127+
(#2134, #2143, #2151)
128+
129+
* [jwt] `jwt.ParseRequest` no longer skips the request body
130+
when the request uses chunked transfer encoding. The
131+
Content-Length-based fast path previously bypassed
132+
`ParseForm` for chunked requests even when `WithFormKey` was
133+
supplied. (#2090)
134+
135+
* [jwt] `jwt.Settings` rejects out-of-range NumericDate
136+
precision values at configuration time instead of silently
137+
clamping. (#2092)
138+
139+
* [jwt] Pedantic mode (`jwt.WithPedanticParse(true)`) enforces
140+
that nested-envelope JWTs declare `cty=JWT`. Tokens missing
141+
`cty` or carrying a different value are rejected. (#2093)
142+
143+
* [jwt] `jwt.ParseInsecure` parses the loop-local payload split
144+
from the input rather than the original input bytes. Fixes a
145+
case where `ParseInsecure` could return a token assembled
146+
from a different signature segment than the one being
147+
inspected. (#2096)
148+
149+
* [jwt] `jwt.WithMaxDeltaIs(...)` and `jwt.WithMinDeltaIs(...)`
150+
reject tokens whose compared claim is missing rather than
151+
treating it as zero. Validation no longer silently succeeds
152+
when the claim isn't present on the token. (#2098)
153+
154+
* [jwt] `jwt.Validate` fast and slow paths now check
155+
`iat`/`exp`/`nbf` in the same order, so the same token
156+
reaches the same verdict regardless of which path evaluated
157+
it. (#2100)
158+
7159
v4.0.1 28 Apr 2026
8160
* [jwt] `jwt.Parse` / `jwt.ParseRequest` only call `ParseForm` when
9161
`WithFormKey` is supplied. Previously the form body could be

0 commit comments

Comments
 (0)