@BETTER-AUTH/STRIPE · GHSA-x8j7-xh58-rv6x · Package
FIX → 1.8.0-beta.5 +1
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.
first public Nov 3, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Applications that register the stripe() plugin from @better-auth/stripe with subscriptions enabled are exposed, because sign-up and profile update requests could set the Stripe customer ID. Apps that do not use the plugin, or that leave billing disabled, are not affected. Values written before the upgrade remain and should be checked for IDs shared by more than one user.
Not affected if managed-hosting, no-stripe-plugin, subscription-disabled
Who cares better-auth, node, saas-billing, stripe
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.stripe() plugin is registered in your auth configuration; the advisory says billing effects need subscription.enabled: true.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.
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.
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.
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).
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
SELECT "stripeCustomerId", array_agg("id") ... HAVING count(*) > 1).Upgrade to 1.8.0-beta.5 +1
@better-auth/stripe 1.7.16@better-auth/stripe 1.8.0-beta.5 on the beta linedatabaseHooks.user.create.before and databaseHooks.user.update.before hooks that strip stripeCustomerId from incoming data; the upgrade does not change values written before it.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:
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.
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.