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.
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).
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 — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
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 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 Proof of Reproduction for CVE-2026-53513
- 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
attacker-controlled OIDC endpoint URLs (authorizationEndpoint, tokenEndpoint, userInfoEndpoint, jwksEndpoint, discoveryEndpoint) in POST /sso/register with skipDiscovery:true
- POST /api/auth/sso/register
- stored OIDC config
- POST /api/auth/sign-in/sso
- /api/auth/sso/callback/:providerId
- server-side fetch to tokenEndpoint and userInfoEndpoint
How the agent worked
Root Cause and Exploit Chain for CVE-2026-53513
@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 monorepopackages/sso) - Affected versions:
>= 0.1.0, < 1.6.11(also affects1.7.0-beta.xpre-releases) - Patched version:
1.6.11 - Risk level: Critical
- Consequences:
- Server-Side Request Forgery (SSRF) – any authenticated user can force the better-auth server to fetch arbitrary internal/private URLs during the OIDC callback.
- Account Takeover (ATO) – when
trustEmailVerified: trueis enabled, a forgeduserInfopayload 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/tokenandhttp://127.0.0.1:9876/userinfo, and the callback produced a session whoseuser.emailwasvictim@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:
- URL parsing +
http(s)scheme requirement. isPublicRoutableHostfrom@better-auth/core/utils/host(rejects loopback, RFC 1918, link-local, cloud-metadata FQDNs, etc.).trustedOriginsallowlist 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
- Reference script:
bundle/repro/reproduction_steps.sh - 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:3000using thesso({ trustEmailVerified: true })plugin. - Starts an attacker-controlled HTTP server on
127.0.0.1:9876with/authorize,/token,/userinfo, and/jwksendpoints. - Creates a victim user (
victim@example.com). - Signs in as the attacker test user.
- Sends
POST /api/auth/sso/registerwithskipDiscovery: trueand attacker-controlled OIDC endpoints. - Initiates
POST /api/auth/sign-in/ssoand follows the redirect through the attacker/authorizeendpoint back to the better-auth callback. - Verifies the callback succeeded and fetches
/tokenand/userinfo, then validates the resulting session belongs tovictim@example.com.
- Starts a real better-auth HTTP server on
- Creates two isolated Node.js workspaces in the durable project cache, one using
- Expected evidence of reproduction:
- Vulnerable (
1.6.10): registration returns200, attacker logs showPOST /tokenandGET /userinfo, callback redirects to/dashboard, and/api/auth/get-sessionreturnsuser.email === "victim@example.com". - Fixed (
1.6.11): registration returns400with error codediscovery_private_host, blocking the SSRF chain before the callback is ever reached.
- Vulnerable (
Evidence
bundle/logs/reproduction_steps.log– high-level script outputbundle/logs/vuln-test.log– full Vitest output for the vulnerable runbundle/logs/fixed-test.log– full Vitest output for the fixed runbundle/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/ssoto>= 1.6.11(or the latestbetter-authmonorepo 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
trustedOriginsbetter-auth option rather than disabling validation. - Testing recommendations:
- Add regression tests that attempt
POST /sso/registerwith loopback, RFC 1918, and cloud-metadata URLs and assertdiscovery_private_host. - Verify that
trustedOriginscorrectly allows legitimate internal IdPs. - Audit
POST /sso/update-providerwith the same URL set, as the fix covers both endpoints.
- Add regression tests that attempt
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.1for 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.
Artifacts and Evidence for CVE-2026-53513
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-53513
Upgrade @better-auth/sso · npm to 1.6.11 or later.
FAQ: CVE-2026-53513
How does the CVE-2026-53513 SSRF-to-account-takeover chain work?
Which versions of @better-auth/sso are affected by CVE-2026-53513, and where is it fixed?
How severe is CVE-2026-53513?
How can I reproduce CVE-2026-53513?
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.