mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
Closes DEVEX-604 Hardens `redirect` URL handling in the OAuth2/OIDC/external-auth callback flows so redirects are always reduced to a safe, relative path local to the application. Previously a redirect value with an opaque scheme (e.g. `javascript:...`) or a path with multiple leading slashes (e.g. `///evil.com`) could survive sanitization mostly intact. Also de-duplicates the previously copy-pasted `uriFromURL` helper (now exported `httpmw.URIFromURL`) so there's a single implementation shared by `coderd/userauth.go`, `coderd/externalauth.go`, and `coderd/httpmw/oauth2.go`. <details> <summary>Context</summary> Addresses a low-severity finding reported via a pentest disclosure: the redirect sanitizer used `url.Parse(...).RequestURI()`, which doesn't reject non-hierarchical (opaque) URLs and doesn't collapse extra leading slashes, so crafted `redirect` values could partially survive sanitization. </details> This PR was authored by a Coder Agent on behalf of @aslilac.
32 lines
1.0 KiB
Go
32 lines
1.0 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// SafeRedirectPath reduces a redirect URL down to a safe, relative path. The
|
|
// scheme and host are dropped to prevent redirecting to another origin. Opaque
|
|
// URLs (e.g. `javascript:`, `data:`) are rejected outright and default to /.
|
|
func SafeRedirectPath(u string) string {
|
|
uri, err := url.Parse(u)
|
|
if err != nil || uri.Opaque != "" {
|
|
return "/"
|
|
}
|
|
|
|
// A path with 2 or more leading slashes (e.g. "//evil.com") is interpreted as
|
|
// protocol-relative, so make sure there is exactly one.
|
|
path := "/" + strings.TrimLeft(uri.EscapedPath(), "/")
|
|
if uri.RawQuery != "" {
|
|
path += "?" + uri.RawQuery
|
|
}
|
|
// We're specifically checking Fragment instead of RawFragment here because
|
|
// RawFragment is only populated when the parser needs to preserve a
|
|
// non-default escaping, so it is empty for plain-alphanumeric fragments like
|
|
// "#wooble". EscapedFragment handles escaping correctly in either case.
|
|
if uri.Fragment != "" {
|
|
path += "#" + uri.EscapedFragment()
|
|
}
|
|
return path
|
|
}
|