---
id: "GHSA-x8j7-xh58-rv6x"
page: "https://infrastack.ai/live/GHSA-x8j7-xh58-rv6x"
kind: "vulnerability"
aliases: ["GHSA-x8j7-xh58-rv6x"]
title: "@better-auth/stripe: Billing actions can target another customer's Stripe account"
severity: "medium"
cvss: 5.9
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "@better-auth/stripe", "fixed": ["1.7.16"], "affected": ">= 1.2.0-beta.18, < 1.7.16", "ecosystem": "npm"}, {"name": "@better-auth/stripe", "fixed": ["1.8.0-beta.5"], "affected": ">= 1.8.0-beta.0, < 1.8.0-beta.5", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["better-auth", "cwe-639", "cwe-915", "node", "package", "saas-billing", "stripe"]
not_affected_if: ["managed-hosting", "no-stripe-plugin", "subscription-disabled"]
published_at: "2026-11-03T21:39:30+00:00"
first_seen_at: "2026-11-09T22:53:20+00:00"
updated_at: "2026-11-09T22:56:13+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/better-auth/better-auth/security/advisories/GHSA-x8j7-xh58-rv6x", "first_seen_at": "2026-11-09T22:53:20+00:00"}]
exploit_sources: []
---

# @better-auth/stripe: Billing actions can target another customer's Stripe account

Link for people: https://infrastack.ai/live/GHSA-x8j7-xh58-rv6x (this .md is for agents)

The @better-auth/stripe plugin accepted a Stripe customer ID from sign-up and profile update requests, so a signed-in user could point their account at someone else's Stripe customer. That lets them open that customer's billing portal, view invoices, change payment methods and cancel or change subscriptions.

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

## Affected

- npm:@better-auth/stripe affected >= 1.2.0-beta.18, < 1.7.16 fixed 1.7.16
- npm:@better-auth/stripe affected >= 1.8.0-beta.0, < 1.8.0-beta.5 fixed 1.8.0-beta.5

## How to check

- Check the installed version with `npm ls @better-auth/stripe`; affected ranges are `>= 1.2.0-beta.18, < 1.7.16` and `>= 1.8.0-beta.0, < 1.8.0-beta.5`.
- Confirm the `stripe()` plugin is registered in your auth configuration; the advisory says billing effects need `subscription.enabled: true`.
- Look for Stripe customer IDs held by more than one user with the PostgreSQL query in the advisory (`SELECT "stripeCustomerId", array_agg("id") ... HAVING count(*) > 1`).

## Fix

- `@better-auth/stripe` `1.7.16`
- `@better-auth/stripe` `1.8.0-beta.5` on the beta line
- Until upgraded, the advisory suggests `databaseHooks.user.create.before` and `databaseHooks.user.update.before` hooks that strip `stripeCustomerId` from incoming data; the upgrade does not change values written before it.

## Timeline

- 2026-11-09T22:53:20+00:00 ghsa-repo https://github.com/better-auth/better-auth/security/advisories/GHSA-x8j7-xh58-rv6x

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/better-auth/better-auth/security/advisories/GHSA-x8j7-xh58-rv6x

## From the sources

## Am I affected?

You are affected if you register the `stripe()` plugin from `@better-auth/stripe` 1.2.0-beta.18 or later. The billing effects need `subscription.enabled: true`.

## Summary

The Stripe plugin stores each user's Stripe customer ID on the user record. That field accepted values from sign-up and profile update requests, so a user could replace their own customer ID with another customer's. The billing portal, checkout, and webhook handling then treated that customer as theirs.

## Impact

A signed-in user who knows another customer's Stripe ID can open the billing portal for that customer. There they can see invoices and billing details, change payment methods, and cancel or change subscriptions. A subscription created for that customer outside checkout could also be attributed to the attacker. The flaw does not sign anyone in as another user or expose Stripe API keys.

## Patches

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

Customer IDs and the subscription fields the plugin manages can no longer be set from sign-up or update-user requests, including server calls such as `auth.api.updateUser`. Write them through your database adapter instead.

The upgrade does not change values written before it. Check for customer IDs held by more than one user (see Workarounds).

## Workarounds

Add `databaseHooks.user.create.before` and `databaseHooks.user.update.before` hooks that remove `stripeCustomerId` from incoming data.

To find values written before the fix, look for customer IDs held by more than one user. For example, on PostgreSQL with the default schema names:

```sql
SELECT "stripeCustomerId", array_agg("id") AS user_ids
FROM "user"
WHERE "stripeCustomerId" IS NOT NULL
GROUP BY "stripeCustomerId"
HAVING count(*) > 1;
```

Compare each row with the Stripe customer's `metadata.userId`. Run the same check on `organization` if organization billing is enabled.

## Technical details

`stripeCustomerId` had no `input: false`, so the generic user routes accepted it. The billing routes now also take the customer type from the authorized reference instead of the request body. When several records share a customer ID, the `customer.subscription.created` webhook now attributes the subscription to the owner recorded on the Stripe customer, and skips it with a warning when no recorded owner holds the ID.

---

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