security: validate OIDC avatar URLs and only store real images - #1295
Open
claudiusbirdwhistle wants to merge 8 commits into
Open
claudiusbirdwhistle wants to merge 8 commits into
claudiusbirdwhistle wants to merge 8 commits into
Conversation
User supplied URLs (integrations, map styles, geocoders, federation, OIDC avatars) are fetched server side. Add OutboundUrlValidator which only allows http(s), resolves the host and always rejects loopback, link-local/cloud metadata, unspecified, multicast and embedded IPv4 variants of those, plus the instance's own database, redis and tile cache. Private networks (RFC 1918 IPv4 ranges, carrier-grade NAT 100.64.0.0/10 and IPv6 unique local addresses fc00::/7) are rejected only when reitti.security.outbound.allow-private-networks / OUTBOUND_ALLOW_PRIVATE_NETWORKS is false (default true so LAN integrations keep working). The shared RestTemplate no longer follows redirects and gets finite timeouts; OutboundHttp offers a size-capped GET that validates every redirect hop. ImageTypes detects images by their file signature (the "magic bytes" at the start of the file) instead of trusting the name.
- Validate style, TileJSON and tile template URLs on save and again before every server side fetch (style.json, TileJSON, tiles), validate redirect hops and cap response sizes. - proxyTiles and shared can only be set by admins (checked against the stored role), they were only hidden in the UI. - Tile proxy endpoints fail closed when the style is unknown or not accessible; the tile URL caches are checked against the user's access and keyed by style version. - Deleting a style is limited to its owner (admins may delete shared styles); default styles cannot be deleted. - Escape <, >, & and U+2028/9 in the map style JSON that templates inline into script blocks (stored XSS via shared style names).
…regression tests Tests cover admin-only proxyTiles/shared, rejected internal style URLs, scoped deletes, script-safe inlining of shared style names and the fail-closed tile proxy.
The tile cache proxies to whatever URL is sent in X-Reitti-Upstream-Url. It is only reachable on the internal docker network and Reitti validates user supplied upstreams, but as defense in depth reject non-http(s) upstreams and loopback, unspecified, link-local (cloud metadata) and IPv6 literal hosts.
The IdP supplied picture URL was fetched server side and stored as the user's avatar with a content type guessed from the URL. Now the URL and every redirect hop are validated (loopback, link-local/metadata, ... are rejected), the download is capped at 5 MB and the avatar is only stored if its bytes are a PNG/JPEG/GIF/WebP image, with the detected type.
The shared bean also knows the instance's internal services (database, redis, tile cache) and honours reitti.security.outbound.allow-private-networks.
This was referenced Oct 2, 2026
Open
Open
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #1291, for the URL validator. This branch contains its commits. The new commits are the last two.
Problem
On login, the
pictureURL supplied by the identity provider is fetched by the server, with redirects followed, and stored as the user's avatar with a content type guessed from the URL. Many identity providers let users set that URL themselves. That gives server-side request forgery, with the response readable afterwards through the avatar endpoint.Changes
OutboundUrlValidator, and the download is capped at 5 MB.Tests
CustomOidcUserServiceTest: the avatar URLs in these tests now use a private-network address instead oflocalhost, because loopback targets are rejected.Security fix series (from a review of 5.6.0; most PRs are independent, dependencies are noted in each PR):
GitHub's stacked pull requests are not available for contributions from forks, so dependent PRs include their prerequisite commits and name the new ones. Happy to rebase, split or merge these differently if that is easier to review.
🤖 Generated with Claude Code