GHSA-9h47-pqcx-hjr4 Better Auth has insecure cryptographic defaults in oidcProvider: alg=none advertised and plain PKCE accepted by default
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:Upgrade to `better-auth@1.6.11` or later.Migrate from the deprecated `oidcProvider` and `mcp` plugins to `@better-auth/oauth-provider` when feasible.If developers cannot upgrade their applications, see workarounds below.SummaryThe 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`.DetailsThe 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.PatchesFixed 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.WorkaroundsIf 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.CreditReported 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)
CVSS v3.1 score: 8.7