Summary
The /api/connect/github OAuth flow generates a nonce in the state parameter but never persists it anywhere. The callback at /api/connect/github/callback only checks that the state is base64-decodable JSON with a userId and nonce field — it never verifies that the nonce was actually issued by this server. An attacker can craft a forged state containing an arbitrary userId, complete the GitHub OAuth flow, and attach a GitHub token to any user account.
Root Cause
apps/backend/src/routes/connect.ts line 40:
// In a real app, store this in Redis to cross-check in callback
const state = JSON.stringify({
userId: (request.user as any).id,
nonce: generateState(), // generated but never stored
});
Callback (line 63–68):
const decodedState = parseOAuthState(state);
if (!decodedState) { ... }
const userId = decodedState.userId; // userId comes entirely from attacker-controlled input
There is no server-side validation that the nonce was actually issued for this user session.
Proposed Fix
- On
/api/connect/github (the initiation endpoint), store the nonce in Redis keyed by nonce value with a 10-minute TTL:
await app.redis.set(`oauth:nonce:${nonce}`, userId, 'EX', 600);
- On the callback, look up the nonce in Redis; reject if missing or if the stored
userId doesn't match the decoded one:
const storedUserId = await app.redis.get(`oauth:nonce:${decodedState.nonce}`);
if (!storedUserId || storedUserId !== decodedState.userId) {
return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=invalid_state`);
}
await app.redis.del(`oauth:nonce:${decodedState.nonce}`);
Acceptance Criteria
Please assign this issue to me under GSSoC
Summary
The
/api/connect/githubOAuth flow generates anoncein the state parameter but never persists it anywhere. The callback at/api/connect/github/callbackonly checks that the state is base64-decodable JSON with auserIdandnoncefield — it never verifies that the nonce was actually issued by this server. An attacker can craft a forged state containing an arbitraryuserId, complete the GitHub OAuth flow, and attach a GitHub token to any user account.Root Cause
apps/backend/src/routes/connect.tsline 40:Callback (line 63–68):
There is no server-side validation that the nonce was actually issued for this user session.
Proposed Fix
/api/connect/github(the initiation endpoint), store the nonce in Redis keyed by nonce value with a 10-minute TTL:userIddoesn't match the decoded one:Acceptance Criteria
Please assign this issue to me under GSSoC