Skip to content

CVE-2026-53513: Verified Reproduction

CVE-2026-53513: @better-auth/sso <1.6.11 allows non-blind SSRF via unvalidated OIDC endpoint URLs during SSO provider registration, with potential account takeover when trustEmailVerified is enabled.

CVE-2026-53513 is verified against @better-auth/sso · npm. Affected versions: >=0.1.0, <1.6.11 (also 1.7.0-beta.x). Fixed in 1.6.11. Vulnerability class: SSRF. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00274.

REPRO-2026-00274 @better-auth/sso · npm SSRF Jul 8, 2026 CVE entry .txt
Severity
CRITICAL
CVSS
9.6
Confidence
HIGH
Reproduced in
30m 49s
Tool calls
281
Spend
$5.19
01 · Overview

What Is CVE-2026-53513?

CVE-2026-53513 is a critical (CVSS 9.6) SSRF vulnerability in the @better-auth/sso plugin for better-auth, where unvalidated OIDC endpoint URLs supplied during SSO provider registration are fetched server-side, and if trustEmailVerified is enabled, can lead to account takeover. Pruva reproduced it (reproduction REPRO-2026-00274).

02 · Severity & CVSS

CVE-2026-53513 Severity & CVSS Score

CVE-2026-53513 is rated critical severity, with a CVSS base score of 9.6 out of 10.

CRITICAL threat level
9.6 / 10 CVSS base
Weakness CWE-20 — Improper Input Validation

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

Affected @better-auth/sso Versions

@better-auth/sso · npm versions >=0.1.0, <1.6.11 (also 1.7.0-beta.x) are affected.

How to Reproduce CVE-2026-53513

$ pruva-verify REPRO-2026-00274
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00274/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-53513

Authorization bypass — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

attacker-controlled OIDC endpoint URLs (authorizationEndpoint, tokenEndpoint, userInfoEndpoint, jwksEndpoint, discoveryEndpoint) in POST /sso/register with skipDiscovery:true

Attack chain
  1. POST /api/auth/sso/register
  2. stored OIDC config
  3. POST /api/auth/sign-in/sso
  4. /api/auth/sso/callback/:providerId
  5. server-side fetch to tokenEndpoint and userInfoEndpoint
How the agent worked 749 events · 281 tool calls · 31 min
31 minDuration
281Tool calls
227Reasoning steps
749Events
7Dead-ends
Agent activity over 31 min
Support
13
Hypothesis
2
Repro
302
Judge
21
Variant
407
0:0030:49

Root Cause and Exploit Chain for CVE-2026-53513

Versions: >= 0.1.0, < 1.6.11 (also affects 1.7.0-beta.x pre-releases)

@better-auth/sso versions < 1.6.11 allow an authenticated attacker to register or update an OIDC SSO provider with arbitrary attacker-controlled endpoint URLs when skipDiscovery: true is used. The supplied authorizationEndpoint, tokenEndpoint, userInfoEndpoint, jwksEndpoint, and discoveryEndpoint values are persisted without validating that the host is publicly routable. During the OIDC callback flow the server makes server-side HTTP requests to those stored URLs, enabling non-blind SSRF. When the plugin is configured with trustEmailVerified: true, a malicious userInfo response can claim emailVerified: true for any existing email, causing the attacker to be linked to (and receive a session for) the matching victim account.

  • Package/component affected: npm:@better-auth/sso (better-auth monorepo packages/sso)
  • Affected versions: >= 0.1.0, < 1.6.11 (also affects 1.7.0-beta.x pre-releases)
  • Patched version: 1.6.11
  • Risk level: Critical
  • Consequences:
    1. Server-Side Request Forgery (SSRF) – any authenticated user can force the better-auth server to fetch arbitrary internal/private URLs during the OIDC callback.
    2. Account Takeover (ATO) – when trustEmailVerified: true is enabled, a forged userInfo payload can claim an arbitrary email is verified, causing the attacker to be logged in as the existing user with that email.

Impact Parity

  • Disclosed/claimed maximum impact: SSRF with possible account takeover when trustEmailVerified: true.
  • Reproduced impact from this run: Full SSRF and account takeover were reproduced. The attacker-registered provider caused the server to hit http://127.0.0.1:9876/token and http://127.0.0.1:9876/userinfo, and the callback produced a session whose user.email was victim@example.com.
  • Parity: full
  • Not demonstrated: None. The reproduction reaches the disclosed endpoint surface and matches the claimed impact.

Root Cause

The POST /sso/register endpoint (and POST /sso/update-provider) has a skipDiscovery: true branch that serializes the user-supplied oidcConfig object directly into the provider row without checking URL origins or host reachability. In packages/sso/src/routes/sso.ts (vulnerable 1.6.10):

if (body.oidcConfig.skipDiscovery) {
  return JSON.stringify({
    ...body.oidcConfig,
    issuer: body.issuer,
  });
}

Later, the OIDC callback (/sso/callback/:providerId) uses the stored config.tokenEndpoint with validateAuthorizationCode() and config.userInfoEndpoint with betterFetch(), sending real HTTP requests to those URLs. Because the callback runs server-side, any URL (loopback, RFC 1918, cloud metadata) is reachable.

The trustEmailVerified: true option in sso({ trustEmailVerified: true }) causes the callback to accept the email_verified claim from the attacker-controlled userInfo response, which then matches an existing user by email and links the attacker to that account via the OAuth auto-linking flow.

Fix

The fix was introduced in PR #9574 / commit 37f60cb176cb53147da7dfd5ec15afa5b486e81e and released in @better-auth/sso@1.6.11. It adds a layered gate for all user-supplied OIDC URLs:

  1. URL parsing + http(s) scheme requirement.
  2. isPublicRoutableHost from @better-auth/core/utils/host (rejects loopback, RFC 1918, link-local, cloud-metadata FQDNs, etc.).
  3. trustedOrigins allowlist as an escape hatch for private/internal IdPs.

In our reproduction the fixed version rejects the malicious registration with BAD_REQUEST and the discovery_private_host error code for http://127.0.0.1:9876/authorize.

Reproduction Steps

  1. Reference script: bundle/repro/reproduction_steps.sh
  2. What the script does:
    • Creates two isolated Node.js workspaces in the durable project cache, one using @better-auth/sso@1.6.10 (vulnerable) and one using @better-auth/sso@1.6.11 (fixed).
    • In each workspace it writes a Vitest test that:
      • Starts a real better-auth HTTP server on 127.0.0.1:3000 using the sso({ trustEmailVerified: true }) plugin.
      • Starts an attacker-controlled HTTP server on 127.0.0.1:9876 with /authorize, /token, /userinfo, and /jwks endpoints.
      • Creates a victim user (victim@example.com).
      • Signs in as the attacker test user.
      • Sends POST /api/auth/sso/register with skipDiscovery: true and attacker-controlled OIDC endpoints.
      • Initiates POST /api/auth/sign-in/sso and follows the redirect through the attacker /authorize endpoint back to the better-auth callback.
      • Verifies the callback succeeded and fetches /token and /userinfo, then validates the resulting session belongs to victim@example.com.
  3. Expected evidence of reproduction:
    • Vulnerable (1.6.10): registration returns 200, attacker logs show POST /token and GET /userinfo, callback redirects to /dashboard, and /api/auth/get-session returns user.email === "victim@example.com".
    • Fixed (1.6.11): registration returns 400 with error code discovery_private_host, blocking the SSRF chain before the callback is ever reached.

Evidence

  • bundle/logs/reproduction_steps.log – high-level script output
  • bundle/logs/vuln-test.log – full Vitest output for the vulnerable run
  • bundle/logs/fixed-test.log – full Vitest output for the fixed run
  • bundle/repro/runtime_manifest.json – structured runtime evidence manifest

Key excerpts from bundle/logs/reproduction_steps.log (vulnerable run):

register response: 200 {
  "tokenEndpoint": "http://127.0.0.1:9876/token",
  "userInfoEndpoint": "http://127.0.0.1:9876/userinfo",
  ...
}
callback status: 302 location: /dashboard
session: { "user": { "email": "victim@example.com", "emailVerified": true, ... } }
attacker requests: [
  { "url": "/token", "method": "POST" },
  { "url": "/userinfo", "method": "GET" }
]

Fixed run excerpt:

register response: 400 {
  "message": "The authorizationEndpoint URL (http://127.0.0.1:9876/authorize) is not publicly routable: 127.0.0.1...",
  "code": "discovery_private_host"
}

Environment captured:

  • Node.js version: v24.18.0
  • npm version: 11.16.0
  • Vulnerable package: @better-auth/sso@1.6.10 + better-auth@1.6.10
  • Fixed package: @better-auth/sso@1.6.11 + better-auth@1.6.11

Recommendations / Next Steps

  • Upgrade guidance: Upgrade @better-auth/sso to >= 1.6.11 (or the latest better-auth monorepo release). The patched release rejects private/internal OIDC URLs during provider registration unless explicitly allowlisted.
  • Configuration guidance: If an internal IdP on a private host is required, add its origin to the trustedOrigins better-auth option rather than disabling validation.
  • Testing recommendations:
    • Add regression tests that attempt POST /sso/register with loopback, RFC 1918, and cloud-metadata URLs and assert discovery_private_host.
    • Verify that trustedOrigins correctly allows legitimate internal IdPs.
    • Audit POST /sso/update-provider with the same URL set, as the fix covers both endpoints.

Additional Notes

  • Idempotency: The reproduction script was executed twice consecutively from a clean bundle and produced the same vulnerable/fixed divergence both times.
  • Limitations: The reproduction uses 127.0.0.1 for the attacker endpoints to demonstrate the SSRF without needing an external network. The same vulnerable code path would allow arbitrary internal/cloud metadata targets in a production deployment.
  • Surface: The reproduction exercises the real better-auth HTTP handler on a real TCP listener (127.0.0.1:3000), with real HTTP requests crossing the attacker (127.0.0.1:9876) and the better-auth callback boundary.

CVE-2026-53513 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:45
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · GHSA-5RR4-8452-HF4V · REPRO-20
0:11
0:13
0:14
web search
0:24
0:25
web search
0:48
0:48
extract_facts
no facts extracted
0:49
0:49
0:49
supportrepro
1:17
1:21
1:22
1:22
1:26
1:26
1:26
1:26
1:29
1:29
1:32
1:37
web search
1:40
1:40
1:42
1:44
web search
1:45
08 · How to Fix

How to Fix CVE-2026-53513

Upgrade @better-auth/sso · npm to 1.6.11 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-53513 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-53513

How does the CVE-2026-53513 SSRF-to-account-takeover chain work?

An authenticated attacker registers or updates an OIDC provider with skipDiscovery: true and attacker-controlled endpoint URLs pointing at internal services. During the OIDC callback, the server fetches those URLs -- non-blind SSRF. If the plugin is configured with trustEmailVerified: true, the attacker's own server can additionally return a forged userInfo response claiming emailVerified: true for an existing victim email, causing better-auth to link the session to that victim account -- full account takeover.

Which versions of @better-auth/sso are affected by CVE-2026-53513, and where is it fixed?

Versions >=0.1.0, <1.6.11 (also 1.7.0-beta.x) are affected. It is fixed in 1.6.11.

How severe is CVE-2026-53513?

Critical, CVSS 9.6 -- an authenticated user can force server-side requests to arbitrary internal/private URLs and, when trustEmailVerified is enabled, escalate to full account takeover of any existing email.

How can I reproduce CVE-2026-53513?

Download the verified script from this page and run it in an isolated environment against @better-auth/sso <1.6.11. It registers an SSO provider with skipDiscovery: true and attacker-controlled endpoint URLs, triggers the OIDC callback to demonstrate the server-side fetch (SSRF), and -- with trustEmailVerified enabled -- shows a forged userInfo response linking to an existing account.
11 · References

References for CVE-2026-53513

Authoritative sources for CVE-2026-53513 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.