Skip to content

Commit 5eab496

Browse files
tofikwestclaude
andcommitted
fix(device-agent): atomic getdel for auth code + encode URL params
- Use Redis GETDEL for atomic get+delete to prevent TOCTOU race on auth code exchange - URL-encode callback_port and state params to prevent parameter injection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2b26a44 commit 5eab496

File tree

2 files changed

+3
-6
lines changed

2 files changed

+3
-6
lines changed

apps/portal/src/app/(public)/auth/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default async function Page({
3131

3232
const deviceAuthRedirect =
3333
isDeviceAuth && callbackPort && state
34-
? `/auth/device-callback?callback_port=${callbackPort}&state=${state}`
34+
? `/auth/device-callback?callback_port=${encodeURIComponent(callbackPort)}&state=${encodeURIComponent(state)}`
3535
: undefined;
3636

3737
const defaultSignInOptions = (

apps/portal/src/app/api/device-agent/exchange-code/route.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export async function POST(req: Request) {
3636
const { code } = parsed.data;
3737
const kvKey = `device-auth:${code}`;
3838

39-
// Fetch and immediately delete (single-use)
40-
const stored = await kv.get<StoredAuthCode>(kvKey);
39+
// Atomic get+delete to prevent race condition (TOCTOU)
40+
const stored = await kv.getdel<StoredAuthCode>(kvKey);
4141

4242
if (!stored) {
4343
return NextResponse.json(
@@ -46,9 +46,6 @@ export async function POST(req: Request) {
4646
);
4747
}
4848

49-
// Delete immediately to prevent replay
50-
await kv.del(kvKey);
51-
5249
return NextResponse.json({
5350
session_token: stored.sessionToken,
5451
user_id: stored.userId,

0 commit comments

Comments
 (0)