Skip to content

fix(auth): CoinPay binding in app_metadata, not user_metadata - #343

Merged
ralyodio merged 1 commit into
masterfrom
fix/coinpay-sub-binding
Oct 27, 2026
Merged

ralyodio merged 1 commit into
masterfrom
fix/coinpay-sub-binding

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

CoinPay sign-in already binds accounts by sub (never by the unverified email, since #273), but it read the binding from user_metadata.coinpay_sub, which every signed-in user can write with auth.updateUser({ data }). Someone who learned a victim's CoinPay sub could stamp it on their own account and, since GoTrue lists newest users first, receive the victim's next CoinPay login.

  • The binding is now app_metadata.coinpay_sub (service role only); new CoinPay accounts get it at creation.
  • Prod: both existing CoinPay accounts were backfilled into app_metadata before this deploys (auth.users backed up first). No account had a forged user_metadata sub.
  • Test: an account carrying a self-written user_metadata sub is never matched.

🤖 Generated with Claude Code

…-writable user_metadata

user_metadata can be set by any signed-in user via auth.updateUser({ data }),
so a user who learned a victim's CoinPay sub could stamp it on their own
account and receive the victim's next CoinPay login (GoTrue lists newest
first). The binding now lives in app_metadata (service role only). The two
existing CoinPay accounts were backfilled on prod before deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

17 finding(s)

MEDIUM: 13 | LOW: 4

Severity Rule Location
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:66
MEDIUM js-unescaped-html-sink src/app/faq/page.jsx:57
MEDIUM js-unescaped-html-sink src/app/layout.jsx:142
MEDIUM js-unescaped-html-sink src/app/layout.jsx:146
MEDIUM js-unescaped-html-sink src/app/page.jsx:50
MEDIUM js-open-redirect src/app/phone/page.jsx:70
MEDIUM redos-nested-quantifier src/lib/auth/dns-name.js:88
MEDIUM redos-nested-quantifier src/lib/chat/voice-mode.js:116
MEDIUM redos-nested-quantifier src/lib/chat/voice-mode.js:121
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:85
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:130
MEDIUM js-unescaped-html-sink src/lib/components/PitFooter.jsx:15
LOW secret-generic-credential src/app/api/auth/register-anon/route.test.js:32
LOW secret-jwt tests/debug-sms.js:10
LOW secret-generic-credential tests/private-key-import-export.test.js:252
LOW secret-generic-credential tests/private-key-import-export.test.js:264

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 51467dc into master Oct 27, 2026
11 checks passed
@ralyodio
ralyodio deleted the fix/coinpay-sub-binding branch October 27, 2026 03:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant