Bug: workout-session sync endpoint trusts client-supplied userId (cross-user write)
Bug
The workout-session sync endpoint (POST /api/workout-sessions/sync) lets an authenticated user write workout data as another user. The route authenticates the caller but then forwards the client-supplied body.session (including body.session.userId) to the sync action unchanged, so a logged-in attacker can sync a session under any userId they choose.
Root cause
app/api/workout-sessions/sync/route.ts:
const session = await getMobileCompatibleSession(request);
if (!session?.user) {
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
}
const body = await request.json();
...
// Use the existing server action
const result = await syncWorkoutSessionAction({ session: body.session }); // ← body.session.userId is attacker-controlledThe route validates the caller is authenticated but never replaces body.session.userId with the authenticated session.user.id. The downstream syncWorkoutSessionAction only checks that the supplied userId exists (userExists), not that it matches the caller — so the attacker's userId flows straight into the upsert's userId and the row is created/updated under the victim's account.
Same class of bug as #238 (delete), but on the write path: cross-user workout-data injection/overwrite.
Impact
A logged-in user can post workout sessions that appear under another user's account (and, because the action uses upsert by session id, potentially overwrite an existing session's exercises/sets if they know or guess a id).
Fix
In the sync route, override the client-supplied userId with the authenticated user's id before calling the action:
const result = await syncWorkoutSessionAction({
session: { ...body.session, userId: session.user.id },
});I have a PR ready.
Source: Snouzy/workout-cool