# Tinyauth has OAuth account confusion via shared mutable state on singleton service instances

Tinyauth OAuth services store PKCE verifiers and tokens on shared singletons, so racing callbacks can hand one user another user's identity.

- Payout: Research
- Severity: high
- Program: Tinyauth
- Found by: kq5y
- Disclosed: 2026-04-01
- CVE: CVE-2026-33544
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-9q5m-jfc4-wc92
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-9q5m-jfc4-wc92
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:go tech:oauth technique:race-window vuln:account-takeover vuln:auth-bypass vuln:dos vuln:race-condition

**Target:** OAuth callback handler / OAuth service singletons (Go)
**Root cause:** OAuthBrokerService.GetService returns one shared service instance per provider, and VerifyCode/Userinfo store and read the PKCE verifier and access token as mutable struct fields, so concurrent requests overwrite each other's state.
**Steps:**
1. Two users initiate OAuth login for the same provider on one tinyauth instance and both obtain authorization codes.
2. Both callbacks hit `service.VerifyCode(code)` nearly simultaneously; request B overwrites `singleton.token` set by request A.
3. Request A then calls `controller.broker.GetUser(req.Provider)` which reads the overwritten token via `Userinfo()`.
4. Request A receives User B's identity claims (email, name, groups) and a session under that identity.
5. Confirm statically with `go test -race` on a concurrent VerifyCode/Userinfo test: 4 data races on token/verifier fields.
**How found:** Code review of the OAuth broker: noticed GetService returns a single shared instance per provider while the flow stores intermediate state (verifier, token) as struct fields; confirmed with Go race detector showing 4 data races and 99.9% token overwrite rate over 10,000 iterations.
**Exploitation:** Attacker times their OAuth callback to race a victim's callback on the same provider; the victim's VerifyCode overwrites the shared token field before the attacker's GetUser reads it, yielding a session with the victim's identity and ACL access. PKCE verifier overwrite also causes DoS on concurrent logins.
**Escalation:** Session inherits victim's identity, granting access to all resources permitted by tinyauth's ACL system.
**Insight:** When an auth flow keeps per-request state (PKCE verifier, token) on a service object, check whether that object is a singleton from a broker/registry — per-request state must travel via parameters or the state-bound session, not struct fields.

