You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Segurança/Multiplayer: action start não exige status = lobby — um POST reinicia corrida em andamento (route.ts:147-164) #131
A action start (src/app/api/rooms/[code]/route.ts:147-164) só verifica isLeader. Ela não
exige que a sala esteja em lobby — o update é incondicional no status:
Ou seja: start postado no meio de uma corrida em andamento sorteia um snippet novo, zera results e joga start_at para Date.now() + COUNTDOWN_MS (4 s no futuro), para todo mundo que
está digitando.
Como reproduzir (anônimo, sem login)
GET /api/rooms lista as salas públicas com o código (src/app/api/rooms/route.ts).
GET /api/rooms/<code> faz select("*") e devolve leader_id em claro.
Com a corrida rolando: POST /api/rooms/<code> {"action":"start","playerId":"<leader_id>"}.
O passo 3 nem precisa de sequestro quando a sala é sua; e a #6 (claim-leader sem autorização, já decisao-dono) dá o passo 2 de graça em qualquer sala.
Independe de anti-cheat: hoje, na main, um start no meio da corrida já destrói a partida em
andamento de todos os jogadores da sala (snippet trocado embaixo dos dedos, results zerado). O
custo disso sobe se a #34 entrar com um filtro que descarta linha por tempo decorrido: start_at no
futuro faz o finish seguinte ver elapsed = 0 e apagar todos os resultados honestos, com a
sala flipando para finished com results: [] (achado das três lentes no quórum da PR #119, SHA 2247d44).
Acceptance criteria
start só transiciona a partir de lobby — update condicional (.eq("status", "lobby"), no
mesmo padrão do finish, que já usa .eq("status", "racing")), não checagem lida antes.
start numa sala em racing/finished responde erro honesto (409) sem tocar snippet, start_at nem results.
Revanche continua funcionando: o caminho real do produto é reset (→ lobby) e só então start — confirmar no fluxo de useRoom/RoomView que nenhum caminho legítimo chama start
direto de finished.
Teste em src/lib/room.test.ts (ou espelho em scripts/validate-persistence.mjs) cobrindo start fora de lobby.
Achado pelo PR Doctor durante o quórum da PR #119 (rodada de 2026-08-05).
Como:start virou transição condicionallobby→racing — a guarda { status: "lobby" } vai na própria escrita (.match()), no mesmo padrão que o finish já usa com .eq("status","racing"), e não numa checagem lida antes (o SELECT da linha 106 deixaria o TOCTOU aberto). A decisão mora numa função pura nova, startRaceSpec (src/lib/room.ts), e roomUpdateOutcome ganhou um conflict? opcional para o 409 honesto — sem ele, o comportamento do #56 fica idêntico.
AC 3 (revanche): confirmado no código — RoomView.tsx:94 só monta o <Lobby onStart> em status === "lobby"; de finished o único caminho é onPlayAgain → resetToLobby (reset → lobby) e só então start. Nenhum caminho legítimo chama start direto de finished.
AC 4 (teste): 6 casos em src/lib/room.test.ts. Verificados contra mutação: trocando match: { status: "lobby" } por {}, o suite fica vermelho (1 failed / 198 passed).
Activity
caioross commentedon Aug 5, 2026
Resolvida na PR #133 (non-draft, quórum §7.2).
Como:
startvirou transição condicionallobby→racing— a guarda{ status: "lobby" }vai na própria escrita (.match()), no mesmo padrão que ofinishjá usa com.eq("status","racing"), e não numa checagem lida antes (oSELECTda linha 106 deixaria o TOCTOU aberto). A decisão mora numa função pura nova,startRaceSpec(src/lib/room.ts), eroomUpdateOutcomeganhou umconflict?opcional para o 409 honesto — sem ele, o comportamento do #56 fica idêntico.AC 3 (revanche): confirmado no código —
RoomView.tsx:94só monta o<Lobby onStart>emstatus === "lobby"; definishedo único caminho éonPlayAgain → resetToLobby(reset→lobby) e só entãostart. Nenhum caminho legítimo chamastartdireto definished.AC 4 (teste): 6 casos em
src/lib/room.test.ts. Verificados contra mutação: trocandomatch: { status: "lobby" }por{}, o suite fica vermelho (1 failed / 198 passed).Gate: typecheck ✅ · vitest 199 passed ✅ · build ✅ · validate-persistence 78/78 ✅ · validate-metrics 45/45 ✅.
fix(security): `reset` só transiciona a partir de `finished` (#131)
fix(security): `start` e `reset` só transicionam do estado certo — PO…