---
id: "GHSA-4wq4-2ppv-q3hg"
page: "https://infrastack.ai/live/GHSA-4wq4-2ppv-q3hg"
kind: "vulnerability"
aliases: ["GHSA-4wq4-2ppv-q3hg"]
title: "@better-auth/expo: Authorization proxy can sign values it did not issue"
severity: "critical"
cvss: 9.1
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "@better-auth/expo", "fixed": ["1.7.16"], "affected": ">= 1.4.0-beta.6, < 1.7.16", "ecosystem": "npm"}, {"name": "@better-auth/expo", "fixed": ["1.8.0-beta.5"], "affected": ">= 1.8.0-beta.0, < 1.8.0-beta.5", "ecosystem": "npm"}, {"name": "better-auth", "fixed": ["1.7.16"], "affected": "< 1.7.16", "ecosystem": "npm"}, {"name": "better-auth", "fixed": ["1.8.0-beta.5"], "affected": ">= 1.8.0-beta.0, < 1.8.0-beta.5", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["auth", "better-auth", "cwe-287", "cwe-345", "cwe-601", "expo", "node", "package"]
not_affected_if: ["no-expo-plugin", "versions-patched"]
published_at: "2026-11-03T21:37:25+00:00"
first_seen_at: "2026-11-09T22:53:20+00:00"
updated_at: "2026-11-09T22:54:05+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/better-auth/better-auth/security/advisories/GHSA-4wq4-2ppv-q3hg", "first_seen_at": "2026-11-09T22:53:20+00:00"}]
exploit_sources: []
---

# @better-auth/expo: Authorization proxy can sign values it did not issue

Link for people: https://infrastack.ai/live/GHSA-4wq4-2ppv-q3hg (this .md is for agents)

The expo() plugin in @better-auth/expo signed a caller-supplied state value with the application secret and returned it in a cookie. An unauthenticated attacker who knows a user's email can sign in as that user and change the account's email address.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:@better-auth/expo affected >= 1.4.0-beta.6, < 1.7.16 fixed 1.7.16
- npm:@better-auth/expo affected >= 1.8.0-beta.0, < 1.8.0-beta.5 fixed 1.8.0-beta.5
- npm:better-auth affected < 1.7.16 fixed 1.7.16
- npm:better-auth affected >= 1.8.0-beta.0, < 1.8.0-beta.5 fixed 1.8.0-beta.5

## How to check

- Check whether the server registers the `expo()` plugin from `@better-auth/expo` at version `1.4.0-beta.6` or later.
- Check installed versions of `@better-auth/expo` and `better-auth` with `npm ls @better-auth/expo better-auth`.
- Check whether `trustedOrigins` contains a wildcard entry such as `myapp://*` or `https://*.example.com`.

## Fix

- `@better-auth/expo` to `1.7.16` on the 1.7 line, or `1.8.0-beta.5` on the beta line.
- `better-auth` to `1.7.16` on the 1.7 line, or `1.8.0-beta.5` on the beta line.
- Rotate the application secret after upgrading if the endpoint was reachable.

## Timeline

- 2026-11-09T22:53:20+00:00 ghsa-repo https://github.com/better-auth/better-auth/security/advisories/GHSA-4wq4-2ppv-q3hg

## Documents

- [advisory] Vendor advisory: github.com https://github.com/better-auth/better-auth/security/advisories/GHSA-4wq4-2ppv-q3hg

## From the sources

## Am I affected?

You are affected if your server registers the `expo()` plugin from `@better-auth/expo` 1.4.0-beta.6 or later. No other setting is needed.

The wildcard origin problem below also needs a wildcard entry in `trustedOrigins`, such as `myapp://*` or `https://*.example.com`.

## Summary

**Signing values it did not issue.** The `expo()` plugin adds a public endpoint that helps the system browser start a social sign-in. The endpoint signed a value taken from the request with the application secret and returned the signature in a cookie. Better Auth uses the same secret for email verification links and, when enabled, the session cookie cache.

**Open redirect.** The same endpoint could redirect a visitor to any external HTTPS site.

**Wildcard trusted origins.** A wildcard entry in `trustedOrigins` could match URLs outside the intended scheme, host, or path.

## Impact

An unauthenticated attacker who knows a user's email address can sign in as that user and change the account's email address, with no action from the user. With the session cookie cache enabled, an attacker who knows a user's ID can also act as that user.

The open redirect can make phishing links that start on your auth origin look more convincing. The wildcard problem can let a URL you did not mean to trust pass the trusted origin check.

## Patches

Upgrade both `@better-auth/expo` and `better-auth` to 1.7.16, or to 1.8.0-beta.5 on the beta line.

After upgrading, rotate the application secret if the endpoint was reachable. Signatures issued before the fix stay valid until the secret changes.

If Expo sign-in through an SSO identity provider fails after upgrading, add the identity provider's origin to `trustedOrigins`.

## Workarounds

If you do not use Expo social sign-in, remove the `expo()` server plugin.

Otherwise, block requests to `/expo-authorization-proxy` (under your auth base path) whose `authorizationURL` carries a `state` value that is not exactly 32 characters from `A-Z`, `a-z`, `0-9`, `-` and `_`. State values that Better Auth issues always have this form. This blocks the signing problem only.

## Technical details

The endpoint read `state` from the caller-supplied authorization URL and stored it in a signed cookie without checking that the server had issued it. The cookie signature is an HMAC-SHA256 keyed with the application secret. That is the same computation as an HS256 JSON Web Token signature and as the compact session cookie cache signature, so a forged email-change token or cookie cache value was accepted.

The fix signs only an unexpired state the server issued, and redirects only to the authorization endpoint origin of a configured social, generic OAuth, or trusted SSO provider. Wildcard trusted origins now match a URL by its parsed scheme, host, and path, and no longer match a URL with userinfo or one that cannot be parsed. A wildcard host made only of digits, dots, and wildcards, such as `192.168.*.*`, now matches only IPv4 addresses.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
