GHSA-J8V8-G9CX-5QF4

GHSA-J8V8-G9CX-5QF4 is a high-severity missing authorization vulnerability in @better-auth/scim (npm), affecting versions >= 1.5.0, < 1.7.0-beta.4. It is fixed in 1.7.0-beta.4.

Summary

@better-auth/scim: Account/provider takeover via missing owner binding on non-org SCIM providers

Full technical description

Am I affected?

Users are affected if all of these hold:

  • They install and register the @better-auth/scim plugin (plugins: [scim()]).
  • They create SCIM providers without an organizationId, that is, non-organization ("personal") providers. Organization-scoped providers are not affected because they enforce organization membership and role.
  • Their application's deployment has more than one authenticated user, so one account can target another user's provider.
  • They have not set providerOwnership: { enabled: true }.

The SCIM management endpoints first shipped in 1.5.0, so every stable release from 1.5.0 onward is affected, and the stable line is not patched. On the pre-release line, builds through 1.7.0-beta.3 are affected, and 1.7.0-beta.4 carries the fix.

Fix:

  1. Upgrade to @better-auth/[email protected] (then 1.7.0). This is a breaking change: it removes the providerOwnership option, makes owner binding mandatory, and adds a permanent scimProvider.userId column.
  2. Run the schema migration after upgrading (npx auth migrate). Connections created before the upgrade carry no owner and become unreachable through the management endpoints, so reclaim them at the database level.
  3. The 1.6.x stable line is not patched. If developers stay on it, apply the workaround below.

@better-auth/scim does not bind non-organization SCIM providers to their creator in the default configuration. Any authenticated user can manage another user's non-org provider, including reading its metadata, listing connections, regenerating its SCIM bearer token, and deleting the connection. Regenerating the token rotates it: the legitimate token stops working and the attacker holds a valid one.

Details

The plugin tracks provider ownership through an opt-in providerOwnership option and a scimProvider.userId column. Both default to off. When ownership is disabled, a non-org provider row is created without a userId, and the management access check denies access only when a stored owner id is present and differs from the caller:

// non-organization branch of the access check
} else if (provider.userId && provider.userId !== userId) {
  throw new APIError("FORBIDDEN", { message: "You must be the owner to access this provider" });
}

Because the default row has no userId, the condition is false and the request is allowed. The same gap reaches every management operation: token generation and regeneration, single-provider read, the connection list (which returns providers where p.userId === userId || !p.userId), and provider deletion.

The most sensitive operation is token regeneration. If Alice creates a non-org provider corp-idp, Bob can call the same generate-token endpoint with providerId: "corp-idp". The existing row passes the ownerless access check, so the plugin deletes it, creates a fresh row, and returns a new SCIM bearer token to Bob. Alice's previous token is now invalid, and Bob's token authenticates against that provider's SCIM API routes.

This requires no organization membership and no elevated role. It is reachable in the default scim() configuration whenever non-organization providers are used.

Proof of concept

Two authenticated users, Alice and Bob, in a default scim() setup:

  1. Alice signs in and creates a non-org provider: POST /scim/generate-token with { "providerId": "corp-idp" }.
  2. Bob signs in and reads Alice's provider: GET /scim/get-provider-connection?providerId=corp-idp returns 200 with { "providerId": "corp-idp" }.
  3. Bob regenerates the token: POST /scim/generate-token with { "providerId": "corp-idp" } returns 201 and a new scimToken.
  4. Alice's old token is rejected at GET /scim/v2/Users (401); Bob's regenerated token is accepted (200).

The behavior is reproducible through Better Auth's public API surface with two distinct sessions.

Workarounds

Until a patched version is available:

  • Set providerOwnership: { enabled: true } when registering the plugin, then run the schema update so the scimProvider.userId column exists (npx auth generate or npx auth migrate). New non-org providers are then owner-bound and non-owners are denied. Providers created before enabling ownership stay ownerless until recreated.
  • Or scope every SCIM provider to an organization by always passing organizationId. Organization providers enforce membership and role and are not exposed.
  • Or restrict access to the SCIM management endpoints at the edge while non-org providers remain ownerless.

Credit

Reported by Jvr2022 through a private GitHub Security Advisory.

Resources

Impact

Any authenticated user can take over non-organization SCIM providers created by other users when the plugin runs with its default ownership configuration. An attacker can discover non-org provider connections, read provider metadata, regenerate another user's SCIM token (invalidating the legitimate one), authenticate to the SCIM API routes with the new token, manage SCIM-provisioned users subject to the enabled SCIM functionality, and delete the provider connection.

The application does not perform an authorization check before performing a sensitive operation. Typical impact: unauthorized access to restricted functionality or data.

GHSA-J8V8-G9CX-5QF4 has a CVSS score of 8.3 (High). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.7.0-beta.4); upgrading removes the vulnerable code path.

Affected versions

@better-auth/scim (>= 1.5.0, < 1.7.0-beta.4)

Security releases

@better-auth/scim → 1.7.0-beta.4 (npm)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Fixed in @better-auth/[email protected] (then 1.7.0). The fix makes owner binding mandatory: generateSCIMToken records the creator's userId on every personal connection, and the management endpoints grant access only to that owner. Organization-scoped connections keep their existing membership and role checks. The 1.6.x stable line is not patched.

This release is breaking. It removes the providerOwnership option, owner binding can no longer be disabled, and the scimProvider.userId column becomes a permanent part of the schema, so run npx auth migrate after upgrading. Connections created before the upgrade carry no owner and fail closed; reclaim them by deleting scimProvider rows that have neither organizationId nor userId, or by setting userId to the intended owner, then regenerating tokens.

Frequently Asked Questions

  1. What is GHSA-J8V8-G9CX-5QF4? GHSA-J8V8-G9CX-5QF4 is a high-severity missing authorization vulnerability in @better-auth/scim (npm), affecting versions >= 1.5.0, < 1.7.0-beta.4. It is fixed in 1.7.0-beta.4. The application does not perform an authorization check before performing a sensitive operation.
  2. How severe is GHSA-J8V8-G9CX-5QF4? GHSA-J8V8-G9CX-5QF4 has a CVSS score of 8.3 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of @better-auth/scim are affected by GHSA-J8V8-G9CX-5QF4? @better-auth/scim (npm) versions >= 1.5.0, < 1.7.0-beta.4 is affected.
  4. Is there a fix for GHSA-J8V8-G9CX-5QF4? Yes. GHSA-J8V8-G9CX-5QF4 is fixed in 1.7.0-beta.4. Upgrade to this version or later.
  5. Is GHSA-J8V8-G9CX-5QF4 exploitable, and should I be worried? Whether GHSA-J8V8-G9CX-5QF4 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether GHSA-J8V8-G9CX-5QF4 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix GHSA-J8V8-G9CX-5QF4? Upgrade @better-auth/scim to 1.7.0-beta.4 or later.

Other vulnerabilities in @better-auth/scim

Stop the waste.
Protect your environment with Kodem.