Dependabot flags GHSA-p2fr-6hmx-4528
(medium) against @better-auth/oauth-provider. We are on 1.6.25; the
vulnerable range is >= 1.4.8, < 1.7.0-beta.4, and the 1.6.x stable line is
not being patched.
Analysed rather than deferred — the conclusion is that it is not exploitable in
this deployment, but the alert stays open until we can upgrade, so the reasoning
belongs somewhere findable.
The defect
The provider accepts the RFC 8707 resource parameter only at the token
endpoint, never at /oauth2/authorize. The resource a client states when the
user authorises is discarded; whatever it sends at token exchange is honoured on
its own, checked only against the server-wide validAudiences allowlist. The
refresh grant has the same gap — the audience is re-derived from the refresh
request body, because no resource from the original grant is retained.
So the token audience is a per-request, client-chosen value rather than a
property of the authorisation. RFC 8707 expects the AS to record resources at
authorisation and let the token endpoint narrow them, never widen.
Why we match the detector
Two of the advisory's three conditions hold:
- We are on a vulnerable version.
- We configure two audiences, in
src/server/auth.ts:
validAudiences: [env.baseUrl, ${env.baseUrl}/api/mcp]. The advisory notes
a single-entry deployment cannot mint a token for a different audience; ours
can.
Why it is not exploitable here
The third condition — resource servers make authorization decisions based on
that aud value — does not hold.
- There is exactly one resource server, and it accepts both audiences.
mcpHandler in src/server/mcp/oauth.ts verifies
audience: [env.baseUrl, "${env.baseUrl}/api/mcp"]. It is the only JWT
consumer in the codebase — nothing else verifies against the JWKS, and the
admin UI uses Better Auth session cookies rather than these tokens. Swapping
between the two allow-listed audiences moves a token from /api/mcp to
/api/mcp.
- Privilege comes from scopes, not audience.
accessFromScopes derives
read/write from vault:read / vault:write, and the advisory states scope
checks are independent of the resource, so this cannot widen access.
- Completing the flow requires the operator. Dynamic client registration is
open, so anyone may register a client, but authorisation requires signing in
and approving at the consent screen. A client that gets that far already holds
the scopes granted to it; the audience swap adds nothing.
The spec-compliance gap is real regardless, and that is what the upgrade fixes.
Why not act now
Both documented workarounds cost more than the bug does here:
- Reduce
validAudiences to one entry — would reject clients sending the
other resource value and risks breaking working MCP connectors, to close a
path that leads nowhere in this topology.
- Take
1.7.0-beta.4 — a beta, a breaking API change
(customAccessTokenClaims.resource becomes a resources array, uncovered
resources return invalid_target), and a schema migration.
What to do
The condition this analysis rests on
Both validAudiences entries must keep naming the same resource server. If a
second, genuinely distinct resource server is ever added to that allowlist while
we are still on 1.6.x, the escalation becomes real. A comment to that effect
belongs next to validAudiences in src/server/auth.ts — worth adding whether
or not this issue is actioned soon.
Dependabot flags GHSA-p2fr-6hmx-4528
(medium) against
@better-auth/oauth-provider. We are on1.6.25; thevulnerable range is
>= 1.4.8, < 1.7.0-beta.4, and the 1.6.x stable line isnot being patched.
Analysed rather than deferred — the conclusion is that it is not exploitable in
this deployment, but the alert stays open until we can upgrade, so the reasoning
belongs somewhere findable.
The defect
The provider accepts the RFC 8707
resourceparameter only at the tokenendpoint, never at
/oauth2/authorize. The resource a client states when theuser authorises is discarded; whatever it sends at token exchange is honoured on
its own, checked only against the server-wide
validAudiencesallowlist. Therefresh grant has the same gap — the audience is re-derived from the refresh
request body, because no resource from the original grant is retained.
So the token audience is a per-request, client-chosen value rather than a
property of the authorisation. RFC 8707 expects the AS to record resources at
authorisation and let the token endpoint narrow them, never widen.
Why we match the detector
Two of the advisory's three conditions hold:
src/server/auth.ts:validAudiences: [env.baseUrl,${env.baseUrl}/api/mcp]. The advisory notesa single-entry deployment cannot mint a token for a different audience; ours
can.
Why it is not exploitable here
The third condition — resource servers make authorization decisions based on
that
audvalue — does not hold.mcpHandlerinsrc/server/mcp/oauth.tsverifiesaudience: [env.baseUrl, "${env.baseUrl}/api/mcp"]. It is the only JWTconsumer in the codebase — nothing else verifies against the JWKS, and the
admin UI uses Better Auth session cookies rather than these tokens. Swapping
between the two allow-listed audiences moves a token from
/api/mcpto/api/mcp.accessFromScopesderivesread/write from
vault:read/vault:write, and the advisory states scopechecks are independent of the resource, so this cannot widen access.
open, so anyone may register a client, but authorisation requires signing in
and approving at the consent screen. A client that gets that far already holds
the scopes granted to it; the audience swap adds nothing.
The spec-compliance gap is real regardless, and that is what the upgrade fixes.
Why not act now
Both documented workarounds cost more than the bug does here:
validAudiencesto one entry — would reject clients sending theother
resourcevalue and risks breaking working MCP connectors, to close apath that leads nowhere in this topology.
1.7.0-beta.4— a beta, a breaking API change(
customAccessTokenClaims.resourcebecomes aresourcesarray, uncoveredresources return
invalid_target), and a schema migration.What to do
@better-auth/[email protected].npx auth migrate.customAccessTokenClaimsusage against the renamedresourcesarray.breaking change touches exactly the resource-indicator handling those
clients rely on.
The condition this analysis rests on
Both
validAudiencesentries must keep naming the same resource server. If asecond, genuinely distinct resource server is ever added to that allowlist while
we are still on 1.6.x, the escalation becomes real. A comment to that effect
belongs next to
validAudiencesinsrc/server/auth.ts— worth adding whetheror not this issue is actioned soon.