VDB
KO
HIGH 8.7

GHSA-9h47-pqcx-hjr4

Better Auth has insecure cryptographic defaults in oidcProvider: alg=none advertised and plain PKCE accepted by default

Details

### Am I affected?

Users are affected if all of the following are true:

- Their application uses `better-auth` at a version below the patched release. - Their application enables `oidcProvider()` from `better-auth/plugins/oidc-provider` or `mcp()` from `better-auth/plugins/mcp` (the mcp plugin delegates to `oidcProvider` and inherits both defaults). - For the algorithm-negotiation impact: relying parties of the application's OIDC server use a JWT verification library that performs algorithm negotiation from the discovery document without pinning to a specific signing algorithm. - For the PKCE impact: the authorization URL is exposed to any party other than the user agent and the application's OP.

If the application only uses `@better-auth/oauth-provider` (the canonical replacement) and have not enabled the legacy plugins, it is not affected. The new package's discovery document excludes `none` and its authorize schema rejects `plain` at parse time.

Fix:

1. Upgrade to `better-auth@1.6.11` or later. 2. Migrate from the deprecated `oidcProvider` and `mcp` plugins to `@better-auth/oauth-provider` when feasible. 3. If developers cannot upgrade their applications, see workarounds below.

### Summary

The legacy `oidcProvider` and `mcp` plugins exhibit two related defects in their OIDC discovery and authorize surfaces.

The discovery document advertises `"none"` in `id_token_signing_alg_values_supported` (and, for `mcp`, in `resource_signing_alg_values_supported` on the OAuth protected-resource metadata). Any relying party that performs algorithm negotiation from this metadata without pinning to a real signing algorithm may accept unsigned tokens.

PKCE `plain` is enabled by default. The runtime gate in the authorize handler accepts `code_challenge_method=plain` under this default, and a missing `code_challenge_method` parameter is silently downgraded to `"plain"` before the allowlist check. Discovery advertises `code_challenge_methods_supported: ["S256"]`, contradicting the runtime acceptance of `plain`. RFC 9700 §2.1.1 (OAuth 2.1) explicitly forbids `plain`.

### Details

The metadata builders unconditionally inject `"none"` into the alg list. The runtime authorize gate is structured so a buggy client that strips the `code_challenge_method` parameter still enters the plain code path because the handler rewrites the missing value to `"plain"` before the allowlist check fires.

`@better-auth/oauth-provider` (the deprecation target for `oidcProvider`) is not affected by either defect. The metadata builder uses a `JWSAlgorithms` type union that structurally excludes `"none"`. The authorize schema is `code_challenge_method: z.literal("S256").optional()`, which rejects `plain` at parse time.

### Patches

Fixed in `better-auth@1.6.11`. The legacy `oidcProvider` and `mcp` plugins now:

- Drop `"none"` from `id_token_signing_alg_values_supported` (both plugins) and from `resource_signing_alg_values_supported` (`mcp`). Discovery no longer advertises the unsigned-token option. - Default `allowPlainCodeChallengeMethod` to `false`. A request that explicitly passes `code_challenge_method=plain` is rejected with `invalid_request` unless the integrator opts in. - Reject a `code_challenge` without an accompanying `code_challenge_method` instead of silently rewriting the missing value to `plain`. Clients that send `code_challenge` must also send `code_challenge_method=S256`.

Discovery and runtime behavior align on `S256` only by default.

Integrators who must keep plain PKCE for legacy clients can restore the previous shape with `oidcProvider({ allowPlainCodeChallengeMethod: true })` (and likewise for `mcp`). With the opt-in set, a request that omits `code_challenge_method` is treated as `plain` again, preserving backwards compatibility while keeping the secure default for everyone else. Both legacy plugins are deprecated long-term; the recommended migration is `@better-auth/oauth-provider`, which never advertised `none` or accepted plain PKCE.

### Workarounds

If developers cannot upgrade their applications immediately:

- **Disable plain PKCE explicitly**: set `oidcProvider({ allowPlainCodeChallengeMethod: false })` (and the equivalent on `mcp`). Closes the runtime acceptance of `plain` even though the silent downgrade still rewrites missing methods. - **Override the metadata** to drop `"none"` from `id_token_signing_alg_values_supported`. For `oidcProvider`, pass `metadata: { id_token_signing_alg_values_supported: ["RS256"] }`. For `mcp`, set the same on `options.oidcConfig.metadata`. Verify by curling the `.well-known` endpoint. - **Migrate to `@better-auth/oauth-provider`**: the package is the deprecation target and is unaffected by both defects.

### Impact

- **Algorithm-negotiation downgrade**: relying parties that read the discovery document without pinning may accept unsigned (`alg: "none"`) tokens. - **Authorization-code interception**: PKCE `plain` does not protect the authorization code if the URL leaks (Referer headers, browser history, screen capture, proxy logs). PKCE `S256` is what protects against that exposure; with `plain` the protection is absent. - **OAuth 2.1 / RFC 9700 non-conformance**: deployments shipping the defaults are non-compliant with current standards.

### Credit

Reported by @subhanUmer.

### Resources

- [CWE-327: Use of a Broken or Risky Cryptographic Algorithm](https://cwe.mitre.org/data/definitions/327.html) - [CWE-757: Selection of Less-Secure Algorithm During Negotiation](https://cwe.mitre.org/data/definitions/757.html) - [CWE-1188: Insecure Default Initialization of Resource](https://cwe.mitre.org/data/definitions/1188.html) - [RFC 9700 §2.1.1: PKCE](https://datatracker.ietf.org/doc/html/rfc9700#section-2.1.1) - [RFC 9700 §2.1.2: Token Replay Prevention](https://datatracker.ietf.org/doc/html/rfc9700#section-2.1.2) - [RFC 8414 §2: Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414#section-2)

Are you affected?

Enter the version of the package you're using.

Affected packages

npm / better-auth
Introduced in: 0 Fixed in: 1.6.11
Fix npm install better-auth@1.6.11

References