Validate returnTo at creation (scheme injection / open redirect) #57
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lab/authz-bridge#57
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Status (updated 21 Sep 2026)
Partially addressed by
2c7699b. AvalidReturnTocheck now runs at the callback (before the exchange): an absolute returnTo whose host is not one of the App's registered hostnames, or that carries userinfo, is rejected with 403. This closes the cross-host open-redirect vector.Still open — the scheme-injection vector is not closed.
validReturnTotreats any hostless URL as safe (u.Host == ""→true), so a non-https scheme such asjavascript:/data:(planted viaX-Forwarded-Proto) still passes. The issue's core requirement — https-only — is unmet, as is applying the check at creation (relogin) with a fallback to the App root (it is applied at the callback, failing closed with 403 instead).Remaining work:
javascript:/data:).Context
Security architecture review, 18 Sep 2026 (candidate 3, "Worth exploring"). The returnTo value is built from the ext_authz attributes (
scheme://host + path + query) at relogin, stored in the signed State cookie, and becomes the callback's post-loginLocation. The host part is safe — it is the App's own hostname, the same value that resolved the App. But the scheme comes fromX-Forwarded-Protovia the gateway: an attacker who can influence XFP can plant a non-https scheme (e.g.javascript:) into the signed cookie and have the browser redirected there after the victim's login. Current browser hardening (strippingjavascript:/data:fromLocation) limits practical impact today, but the invariant "the post-login redirect target is an https URL on the App's host" is enforced nowhere. Notably, the logout flow already enforces the analogous invariant for its Referer-based redirect — the callback path doesn't apply the rule the codebase already knows.What to build
A small pure validator module for the return-To value (https only; host equals the App's hostname; no userinfo), applied once at the seam where returnTo is created (relogin), so the State cookie can never carry an unsafe value. An invalid returnTo falls back to a safe default (the App's root). The callback's redirect then becomes a trust-the-cookie path, and the open-redirect rule lives in exactly one place both flows share.
Acceptance criteria
2c7699b; https-only + corpus outstanding2c7699b; fallback + scheme corpus outstandingBlocked by