Flask-Security-Too's OAuth bypass lets attackers log in as your customers by matching email alone. Check your version, audit your providers today.
You added 'Sign in with Google' last Thursday because a Cursor suggestion made it look like four lines of work. Your requirements.txt has flask-security-too pinned to whatever version pip grabbed when you scaffolded the project. Your app has paying customers who logged in with email and password from six months ago, before you added the OAuth button. That combination is the bug. An attacker with control of an OAuth identity that returns your customer's email address can sign in as your customer. Not their account. Your customer's account. The OAuth callback finds the email, matches it to the existing user_id, hands over the session cookie. Flask-Security-Too did not check whether the provider actually verified that email, and it did not check whether the existing account was created through a different path. GHSA-97r5-pg8x-p63p is the advisory. If you have not upgraded since it landed, the bug is live in your production app right now, and your auth logs will show a normal successful login when it gets used against you.
02The OAuth flow you didn't write
The thing about flask-security-too is that you trust it because the README is calm and the import statement is one line. You called oauth_register('google', ...) once in app.py. You never wrote the callback that handles what happens after the user clicks Allow on Google's consent screen. Flask-Security-Too wrote it for you. That callback does roughly this: pull the email from the OAuth response, look up a User row where email matches, log that user in. The vulnerable versions skipped two checks that a junior security review would have flagged. First, they did not require the OAuth provider to assert email_verified in the userinfo response. Second, they did not check whether the matched user had been created via password registration and was never linked to this OAuth identity. So if your provider integration includes anything looser than Google (a custom Authlib provider, an enterprise SSO you wired up for one customer, a self-hosted Keycloak you forgot about), the email field is whatever the attacker tells it to be. Run this today. Open requirements.txt or pyproject.toml and find your flask-security-too version. If it sits older than the GHSA-97r5-pg8x-p63p fix, upgrade now. Then grep your codebase for oauth_register and write down every provider you enabled, including the one for that single enterprise prospect.
03How this ships in a vibe-coded app
The typical path: you are building a B2B SaaS in Flask because that is what Cursor is best at. Six months ago you launched with email and password through flask-security-too. You got fifty paying customers. Last sprint you added 'Sign in with Google' because a prospect asked. You also added a 'custom SSO' option for your one enterprise lead, configured through a generic OAuth2 Authlib client pointing at their IdP. Cursor wrote it. You merged it. It worked. Here is the bug path. Attacker signs up at your marketing site to scout the login flow. They identify a real customer email, say founder@bigcustomer.com, from a public case study on your site. They stand up their own OAuth2 server in fifteen minutes, or use a provider where they control the userinfo response. They click 'Sign in with custom SSO' on your login page, the redirect happens, the userinfo endpoint returns {"email": "founder@bigcustomer.com", "sub": "attacker-controlled"}. Flask-Security-Too matches the email to the existing user row. Session cookie issued. The attacker is now logged in as your customer with full access to their organization_id, their Stripe billing portal link, the .env screenshots they pasted into support tickets. The check: in your Flask app, list every provider in current_app.security.oauth_provider registry. For each, confirm who verifies the email. If any provider is custom, treat the email field as attacker controlled and refuse to link to an existing account without a separate proof-of-ownership step.
04Pin, check, lock
Three things you can finish before lunch. One. Pin flask-security-too to the patched release and run your test suite. The fix adds the email_verified gate and rejects the OAuth login when the provider does not assert it. If your tests do not cover the OAuth callback path, write one test that mocks a userinfo response without email_verified and asserts the login fails. Otherwise the next dependency bump will silently downgrade you and you will not notice until a customer files a ticket about activity they did not perform. Two. Audit your providers. Open the file where you call oauth_register. For each provider, write a one-line comment: who verifies the email, and what happens if they do not. Google and Microsoft are fine. Anything custom needs an explicit allowlist of trusted issuer URLs, or a manual account-linking flow where the user proves they own the existing account before the OAuth identity gets attached. Three. Add a database constraint. Your users table should have a separate oauth_identities table with a unique constraint on (provider, sub). When OAuth login fires, look up by (provider, sub), not by email. Email is for display. Sub is for identity. If you only match on email, you are one misconfigured provider away from a takeover. Guardian scans your Flask app for vulnerable flask-security-too versions, unverified OAuth provider configs, and email-only account linking in your login handler. Point it at your repo and get the report in three minutes.
Find the Flask-Security-Too OAuth bypass in your app
Guardian checks your requirements.txt for vulnerable flask-security-too versions and inspects your OAuth callback for email-only account linking that lets attackers log in as your customers.
Scan my app free