Feb 18, 2026·5 min read·0 visits
OpenClaw tried to make manual OAuth easy by letting users paste just the code. Instead, they broke CSRF protection. Attackers can trick victims into logging into the attacker's account by providing a raw code, which the app blindly accepts.
A logic flaw in OpenClaw's manual OAuth input parsing allowed attackers to bypass state validation by simply providing a bare authorization code. The application helpfully, but insecurely, assumed that any non-URL input was a valid code and automatically attached the expected session state to it, enabling credential substitution attacks.
OAuth is a nightmare. It’s a dance of redirects, tokens, and state parameters that makes even seasoned developers weep. In the world of CLI tools and local agents—like OpenClaw—it gets even messier. You can't always rely on a local web server to catch the callback. Sometimes, you just have to ask the user to copy the URL from their browser and paste it into the terminal.
OpenClaw wanted to make this "manual mode" seamless. Users are lazy; they don't want to copy a full, messy URL like https://api.openclaw.com/callback?code=abc&state=xyz. They just want to copy the code. So, the developers added a convenience feature: if the input doesn't look like a URL, assume it's the auth code.
Sounds friendly, right? It is. It's also a massive security hole. By prioritizing convenience over strict validation, they accidentally created a mechanism to completely bypass the one thing protecting the OAuth flow from forgery: the state parameter.
The vulnerability lives in parseOAuthCallbackInput. The logic was designed with a fallback mechanism. First, it tries to parse the user's input as a full URL. If that fails (because the user pasted a raw string like 4/0AdQt...), it drops into a catch block.
Here is where the logic goes off the rails. Inside the catch block, the code checks if the string looks "safe" (no spaces, no protocol handlers). If it passes this superficial vibe check, the code automatically assigns the expectedState to the input.
Let that sink in. The application essentially says: "I see you didn't provide a state parameter. That's fine, I'll just assume you have the correct one and fill it in for you." This defeats the entire purpose of the state parameter, which exists specifically to prove that the response came from the request this user initiated. By auto-filling it, the application validates its own challenge, effectively talking to itself in a mirror and nodding in agreement.
Let's look at the crime scene in src/agents/chutes-oauth.ts. This is a textbook example of how a catch block can introduce critical logic flaws.
The Vulnerable Code:
try {
// Try to parse as a full URL first
const url = new URL(trimmed);
return { code: url.searchParams.get("code"), state: url.searchParams.get("state") };
} catch {
// THE BUG: Fallback for manual pasting
if (!/\s/.test(trimmed) && !trimmed.includes("://") && trimmed.length > 0) {
// > [!CAUTION]
// > The app blindly trusts the input and injects the valid state!
return { code: trimmed, state: expectedState };
}
}The fix is brutal but necessary. It removes the assumption entirely. If the user pastes a raw string, the new code refuses to guess. It enforces that the input must contain the parameters we need.
The Fix (Commit a99ad11):
// New Logic: No more free rides
let url: URL;
try {
url = new URL(trimmed);
} catch {
// If it's not a URL, it MUST be a query string with code AND state
if (!trimmed.includes("?") && !trimmed.includes("=")) {
return { error: "Paste the full redirect URL (must include code + state)." };
}
// ... construct dummy URL to parse parameters ...
}
// Explicit validation
if (state !== expectedState) {
return { error: "OAuth state mismatch - possible CSRF attack." };
}Since this is a client-side tool, we aren't talking about stealing the victim's session in the traditional web sense. We are talking about Credential Substitution (or Login CSRF).
Here is the attack scenario:
4/0Ad...."state to the attacker's code.This is particularly dangerous in automation tools ("agents") where the user might not verify the identity of the account they just linked before running a massive batch job.
The remediation is straightforward: stop trying to be magic. The developers removed the "convenience" path in commit a99ad11a4107ba8eac58f54a3c1a8a0cf5686f47.
Now, users are forced to paste the full URL or a query string that explicitly includes the state. If the state is missing or doesn't match, the flow fails hard.
They also updated the UI prompts from "Paste the redirect URL (or authorization code)" to just "Paste the redirect URL". This is a good lesson in security UX: don't suggest insecure workflows in your prompt text. If you want security, sometimes you have to force the user to do the slightly annoying thing (copying the whole URL) rather than the easy thing.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
openclaw openclaw | < 2026.2.14 | 2026.2.14 |
| Attribute | Detail |
|---|---|
| CWE | CWE-352 (CSRF) |
| Attack Vector | Network / Social Engineering |
| CVSS | 4.3 (Medium) |
| Impact | Credential Substitution |
| Exploit Status | PoC Available |
| Patch | v2026.2.14 |
The application fails to verify that the state parameter in the OAuth callback matches the state parameter sent in the authorization request when parsing manual input.