GHSA-j8v8-g9cx-5qf4: @better-auth/scim Account/provider Takeover Vulnerability
HIGHGHSA-j8v8-g9cx-5qf4 is a HIGH (CVSS 8.3) account/provider takeover vulnerability (CWE-639: Authorization Bypass Through User-Controlled Key) affecting @better-auth/scim libraries in the npm ecosystem. The issue involves account/provider takeover via missing owner binding on non-org scim providers. Remediation is available by upgrading affected packages to version 1.7.0-beta.4.
GHSA-j8v8-g9cx-5qf4 at a Glance
Security Impact
CVSS: 8.3
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L).
How to Fix GHSA-j8v8-g9cx-5qf4
To remediate this vulnerability, upgrade all affected @better-auth/scim dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install @better-auth/scim@1.7.0-beta.4
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls @better-auth/scim
How to Check if You Are Affected
To determine whether your Node.js application is affected by GHSA-j8v8-g9cx-5qf4, inspect your project dependencies using npm:
npm ls @better-auth/scim
Analyze the terminal output to locate any version of @better-auth/scim in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.7.0-beta.4), your application is affected.
Note: The npm ls @better-auth/scim command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-639: Authorization Bypass Through User-Controlled Key, where the system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data. In @better-auth/scim, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-j8v8-g9cx-5qf4 Affected @better-auth/scim Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
@better-auth/scim
|
npm |
≥ 1.5.0
< 1.7.0-beta.4
|
1.7.0-beta.4
|
References
Frequently Asked Questions
What is GHSA-j8v8-g9cx-5qf4?
Which @better-auth/scim versions are affected?
What version fixes GHSA-j8v8-g9cx-5qf4?
Is @better-auth/scim affected?
What is the CVSS score of GHSA-j8v8-g9cx-5qf4?
What CWE is associated with GHSA-j8v8-g9cx-5qf4?
How can I check npm dependencies for GHSA-j8v8-g9cx-5qf4?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-j8v8-g9cx-5qf4",
"modified" : "2026-07-07T20:57:06Z",
"published" : "2026-07-07T20:57:05Z",
"aliases" : [ ],
"summary" : "@better-auth/scim: Account/provider takeover via missing owner binding on non-org SCIM providers",
"details" : "### Am I affected?\n\nUsers are affected if all of these hold:\n\n- They install and register the `@better-auth/scim` plugin (`plugins: [scim()]`).\n- 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.\n- Their application's deployment has more than one authenticated user, so one account can target another user's provider.\n- They have not set `providerOwnership: { enabled: true }`.\n\nThe 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.\n\nFix:\n\n1. Upgrade to `@better-auth/scim@1.7.0-beta.4` (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.\n2. 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.\n3. The `1.6.x` stable line is not patched. If developers stay on it, apply the workaround below.\n\n### Summary\n\n`@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.\n\n### Details\n\nThe 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:\n\n```ts\n// non-organization branch of the access check\n} else if (provider.userId && provider.userId !== userId) {\n throw new APIError(\"FORBIDDEN\", { message: \"You must be the owner to access this provider\" });\n}\n```\n\nBecause 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.\n\nThe 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.\n\nThis requires no organization membership and no elevated role. It is reachable in the default `scim()` configuration whenever non-organization providers are used.\n\n### Proof of concept\n\nTwo authenticated users, Alice and Bob, in a default `scim()` setup:\n\n1. Alice signs in and creates a non-org provider: `POST /scim/generate-token` with `{ \"providerId\": \"corp-idp\" }`.\n2. Bob signs in and reads Alice's provider: `GET /scim/get-provider-connection?providerId=corp-idp` returns `200` with `{ \"providerId\": \"corp-idp\" }`.\n3. Bob regenerates the token: `POST /scim/generate-token` with `{ \"providerId\": \"corp-idp\" }` returns `201` and a new `scimToken`.\n4. Alice's old token is rejected at `GET /scim/v2/Users` (`401`); Bob's regenerated token is accepted (`200`).\n\nThe behavior is reproducible through Better Auth's public API surface with two distinct sessions.\n\n### Patches\n\nFixed in `@better-auth/scim@1.7.0-beta.4` (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.\n\nThis 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.\n\n### Workarounds\n\nUntil a patched version is available:\n\n- 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.\n- Or scope every SCIM provider to an organization by always passing `organizationId`. Organization providers enforce membership and role and are not exposed.\n- Or restrict access to the SCIM management endpoints at the edge while non-org providers remain ownerless.\n\n### Impact\n\nAny 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.\n\n### Credit\n\nReported by Jvr2022 through a private GitHub Security Advisory.\n\n### Resources\n\n- CWE-862 Missing Authorization: https://cwe.mitre.org/data/definitions/862.html\n- CWE-639 Authorization Bypass Through User-Controlled Key: https://cwe.mitre.org/data/definitions/639.html\n- SCIM 2.0 Protocol (RFC 7644): https://datatracker.ietf.org/doc/html/rfc7644\n- OAuth 2.0 Bearer Token Usage (RFC 6750): https://datatracker.ietf.org/doc/html/rfc6750",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "@better-auth/scim"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "1.5.0"
}, {
"fixed" : "1.7.0-beta.4"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/better-auth/better-auth/security/advisories/GHSA-j8v8-g9cx-5qf4"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/better-auth/better-auth"
}, {
"type" : "WEB",
"url" : "https://github.com/better-auth/better-auth/releases/tag/v1.7.0-beta.4"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-639", "CWE-862" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-07T20:57:05Z",
"nvd_published_at" : null
}
}