Claude Code: Hunting Account Takeover Vulnerabilities in Granola A security researcher found that Granola's OAuth flow lacked strict state parameter verification, enabling account takeover via a crafted callback URL that could hijack a victim's session and inherit webcam permissions. The researcher demonstrated the vulnerability by capturing the auth request, crafting a payload to swap the authentication context, and triggering the hijack when the victim clicked the malicious link. The fix requires strict state validation, PKCE implementation, and permission scoping to prevent such attacks. Claude Code: Hunting Account Takeover Vulnerabilities in Granola The Vulnerability Path The issue stemmed from a flawed implementation of the OAuth flow combined with improper session validation. Essentially, the application didn't strictly verify the state parameter during the callback, allowing an attacker to craft a malicious link that forced a victim to link their account to the attacker's identity. To replicate this as a real-world AI workflow for security testing, I used a combination of manual interception and some custom scripts to automate the request forging. 1. Capture the Auth Request: I monitored the network traffic during a standard login. I noticed the code and state parameters were being passed back to the redirect URI without sufficient cryptographic binding to the original user session. 2. Craft the Payload: By manipulating the callback URL, I could essentially "swap" the authentication context. 3. The Trigger: Once the victim clicked the crafted link, the session was hijacked. Because Granola requests webcam permissions for its core functionality transcribing/recording meetings , the hijacked session inherited those active permissions. Technical Breakdown If you're doing a deep dive into LLM agent security or general app deployment, this is a classic example of why "trusting the client" is a mistake. The logic looked something like this in the backend: js // Hypothetical flawed logic app.get '/auth/callback', async req, res = { const { code, state } = req.query; const token = await exchangeCodeForToken code ; // Problem: No verification that 'state' matches the session that initiated the request const user = await findUserByToken token ; req.session.userId = user.id; res.redirect '/dashboard' ; } ; By bypassing the state check, the attacker controls who is logged into the current browser session. Impact and Fixes The fallout here is significant. Beyond just seeing a user's notes, the webcam access means an attacker could potentially trigger recordings or capture images without the user realizing the session had been compromised. For anyone building an AI workflow or a SaaS app from scratch, here is the practical tutorial for preventing this: Strict State Validation: Always generate a high-entropy random string for the state parameter, store it in a secure, HTTP-only cookie, and verify it matches the callback exactly. PKCE Implementation: Use Proof Key for Code Exchange PKCE even for server-side apps to ensure the entity requesting the token is the same one that started the flow. Permission Scoping: Don't grant "blanket" permissions. Request webcam access only at the moment of recording, rather than maintaining a persistent open door. This kind of bug is exactly why I've been using Claude /en/tags/claude/ Code to audit my own config files; it's surprisingly good at spotting missing validation checks if you prompt it to look for specific OWASP top 10 vulnerabilities. NoClick: Building Always-On AI Agents with Existing Subs 25m ago /en/news/4149/ Mazu AI: Scaling Weather Forecasting for the Global South 1h ago /en/news/4147/ Nvidia's Market Strategy 1h ago /en/news/4145/ Claude Code: Automating Infrastructure Deployment from Scratch 1h ago /en/news/4139/ Claude Code: Finding Cryptographic Weaknesses in Legacy Code 1h ago /en/news/4136/ Humanoid Robot Trade Restrictions: Impact on AI Development 2h ago /en/news/4133/ Next NoClick: Building Always-On AI Agents with Existing Subs → /en/news/4149/