Skip to content

Commit 12c6da0

Browse files
authored
docs: warn about anchoring RegexpWhitelist patterns (#2187)
1 parent ad7198b commit 12c6da0

4 files changed

Lines changed: 19 additions & 1 deletion

File tree

MIGRATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ type Fetcher interface {
261261
>
262262
> `jwkfetch.Client` applies the whitelist to both the initial URL and every HTTP redirect target, so a hostile JWKS host cannot 302 into an off-allowlist URL. This redirect-hop enforcement only applies when the configured `HTTPClient` is a `*http.Client`; if you supply a custom transport, you are responsible for policing redirects yourself.
263263
>
264+
> If you migrate a `RegexpWhitelist`, **anchor your patterns** — they are **not** anchored for you. `example\.com` matches anywhere in the URL and also allows `https://example.com.attacker.com/evil`, reopening the SSRF / key-substitution hole. Write `^https://example\.com/` (anchor the start with `^`, escape the dots, terminate the host with `/`), or use `MapWhitelist` when the issuer URLs are known exactly.
265+
>
264266
> `jws.WithVerifyAuto(nil)` / `jwt.WithVerifyAuto(nil)` is no longer supported — both error at jku-verification time rather than silently using any default.
265267
266268
**Default behavior is equivalent to v3 for trusted, hard-coded URLs.** Both v3's `jwk.Fetch()` and v4's `jwkfetch.NewClient().Fetch()` permit every URL by default — the right choice when the URL is a compile-time constant or comes from trusted configuration. You generally do not need to pass `WithWhitelist` to migrate a hard-coded-URL call site. The SSRF risk above is specific to `jku`-style verification, where the URL originates in the untrusted JWS header.

docs/02-jws.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,8 @@ jwx does NOT wrap the fetcher in a default-deny — it has no way to inspect a `
634634

635635
A `jwkfetch.Client` whitelist is applied to both the initial URL and every redirect target, so a hostile JWKS host cannot 302-bypass the allowlist. See the [jwkfetch README](https://github.com/jwx-go/jwkfetch) for `MapWhitelist` / `RegexpWhitelist` / `WhitelistFunc` patterns and the regex footguns to avoid.
636636

637+
**If you reach for `RegexpWhitelist`, anchor your patterns.** They are **not** anchored for you, so `example\.com` matches anywhere in the URL and also allows `https://example.com.attacker.com/evil` — a whitelist bypass back into the SSRF / key-substitution territory the whitelist was meant to close. Write `^https://example\.com/` (anchor the start with `^`, escape the dots, terminate the host with `/`), or prefer `MapWhitelist` when the `jku` URLs are known up front.
638+
637639
The URL in the `jku` field must have the `https` scheme and the key ID in the fetched JWK Set must match the key ID in the JWS header.
638640

639641
Passing `nil` to `jws.WithVerifyAuto` is not supported: jku verification will error at use time rather than silently falling back to any default. This is intentional — there is no correct default fetcher for jku verification because the policy (which URLs to trust) is site-specific.

docs/04-jwk.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,11 +621,23 @@ Pass any implementation of `jwkfetch.Whitelist` to `jwkfetch.WithWhitelist`:
621621
- `jwkfetch.InsecureWhitelist{}` — allow every URL (the default when `WithWhitelist` isn't passed)
622622
- `jwkfetch.BlockAllWhitelist{}` — deny every URL (useful for tests, safety assertions)
623623
- `jwkfetch.NewMapWhitelist().Add(url1).Add(url2)` — fixed allow-list of exact URLs
624-
- `jwkfetch.NewRegexpWhitelist().Add(pattern)` — pattern-based allow-list
624+
- `jwkfetch.NewRegexpWhitelist().Add(regexp.MustCompile(pattern))` — pattern-based allow-list (**anchor your patterns — see the warning below**)
625625
- `jwkfetch.WhitelistFunc(func(string) bool)` — custom predicate
626626

627627
All the restrictive types fail closed: a URL that doesn't match any listed entry / pattern / predicate is rejected with a `WhitelistError`, for both the initial URL and every redirect target. Whitelist rejections can be detected with `errors.Is(err, jwkfetch.WhitelistError())`.
628628

629+
> **Anchor your `RegexpWhitelist` patterns.** Patterns are **not** anchored for you, so a naive pattern matches anywhere in the URL and can allow far more than you intend. `example\.com` also matches `https://example.com.attacker.com/evil` and `https://attacker.com/?redirect=https://example.com` — a whitelist bypass that hands an attacker SSRF or key substitution. Anchor the start with `^`, escape the dots (`\.`), and terminate the host with `/`:
630+
>
631+
> ```go
632+
> // BAD — matches "example.com" anywhere in the URL
633+
> jwkfetch.NewRegexpWhitelist().Add(regexp.MustCompile(`example\.com`))
634+
>
635+
> // GOOD — anchored to scheme + host
636+
> jwkfetch.NewRegexpWhitelist().Add(regexp.MustCompile(`^https://example\.com/`))
637+
> ```
638+
>
639+
> Use `^https://example\.com$` to allow the bare origin with no path, and `^https://example\.com(:\d+)?/` to also allow an explicit port. Prefer `MapWhitelist` whenever the set of URLs is known up front.
640+
629641
The `Whitelist` concept applies to `Client` only. `Cache` has no `Whitelist` field — it's a cache, and the set of URLs it will ever contact is exactly the set you passed to `Register`. Trying to pass `WithWhitelist` to `NewCache` is a compile-time error.
630642
631643
See the [`jwkfetch` section of the extensions doc](./10-extensions.md#http-jwk-set-retrieval-jwkfetch) and the [module README](https://github.com/jwx-go/jwkfetch) for the full API reference, including allowlist patterns and the regex footguns to avoid.

docs/10-extensions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,8 @@ Policy options (`WithHTTPClient`, `WithMaxBodySize`, `WithParseOptions`) work fo
808808

809809
See the [module README](https://github.com/jwx-go/jwkfetch) for the full API reference and whitelist types (`InsecureWhitelist`, `BlockAllWhitelist`, `MapWhitelist`, `RegexpWhitelist`, `WhitelistFunc`).
810810

811+
`RegexpWhitelist` patterns are **not** anchored for you. A naive pattern like `example\.com` matches anywhere in the URL, so it also allows `https://example.com.attacker.com/evil` and `https://attacker.com/?redirect=https://example.com` — a whitelist bypass that reopens the SSRF / key-substitution hole the whitelist was meant to close. Anchor the start with `^`, escape the dots (`\.`), and terminate the host with `/` — e.g. `^https://example\.com/`, or `^https://example\.com$` for the bare origin. Prefer `MapWhitelist` when the URL set is known up front.
812+
811813
---
812814

813815
# Filters and Introspection (jwxfilter)

0 commit comments

Comments
 (0)