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 org

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