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.
- 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.
-
Craft the Payload: By manipulating the callback URL, I could essentially "swap" the authentication context.
-
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:
// 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 thestate
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
ClaudeCode 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
Mazu AI: Scaling Weather Forecasting for the Global South 1h ago
Nvidia's Market Strategy 1h ago
Claude Code: Automating Infrastructure Deployment from Scratch 1h ago
Claude Code: Finding Cryptographic Weaknesses in Legacy Code 1h ago
Humanoid Robot Trade Restrictions: Impact on AI Development 2h ago
Next NoClick: Building Always-On AI Agents with Existing Subs →