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
[Decisão] Anti-cheat do leaderboard (#34): duas PRs, dois quóruns, um limite estrutural — escolher A, B ou C
#132
A issue #34 (P1, a mais antiga do backlog) já consumiu duas PRs e dois quóruns adversariais, e as
duas morreram no mesmo lugar. Não é falta de capricho: é um limite estrutural que só o dono pode
destravar, porque envolve o que o leaderboard significa.
O buraco, em uma linha
POST /api/rooms/<code> {"action":"finish","results":[{"name":"HACK","wpm":349,"place":1}]} — um
request anônimo põe qualquer nick no topo do Ranking Global, sem digitar uma tecla. Continua aberto
em produção hoje.
vazado por alegar trabalho ≈ 0 (progress: 1e-9); e descarta honesto rápido por skew do relógio do cliente (até 4 s) em snippet curto
A causa raiz é sempre a mesma: o servidor não conhece nem a identidade de quem manda a linha, nem a
janela real de digitação dela. Só tem (trabalho alegado, tempo do servidor) — e com esses dois
insumos, toda regra ou é vazada por alegar trabalho ≈ 0, ou apaga jogador honesto.
Opções (precisa da sua escolha)
A — Elegibilidade: só corrida COMPLETA entra no ranking.buildScoreRows/buildMatchRow passam a
exigir finished: trueeprogress >= 1. Aí o trabalho deixa de ser escolhido pelo atacante (é
sempre o snippet inteiro) e o mínimo físico morde de verdade. Custo: barato (uma PR pequena, sem migração). Resíduo: o forjador ainda entra esperando o mínimo
do menor snippet — 1,4 s (40 chars) — então não fecha a #34, só encarece. Perde-se também
qualquer aparição de run parcial no ranking (hoje entra, e é discutível que devesse).
B — Identidade/roster no servidor (fecha a classe inteira). O finish só aceita linhas de nicks
que o servidor viu na sala, e só do líder legítimo. Fecha #34, #6 e boa parte da #103 de uma vez. Custo: alto — a presença hoje vive no Realtime (cliente); exige o servidor manter roster por sala
(tabela ou presence server-side). É a única opção que realmente fecha.
C — Aceitar o resíduo e reposicionar o produto. Assumir que o Ranking Global é "placar de
diversão", não competição verificável; mergear só as peças estritamente positivas (guarda de status
no start, #131) e fechar a #34 como "não fecha sem B", tirando ela da fila. Custo: zero de engenharia, custo de posicionamento.
Recomendação da frota
A + #131 agora (ambas pequenas e sem risco), com a #34re-escopada e marcada blocked por B —
em vez de continuar gerando PRs que o quórum derruba. B é candidata natural a epic junto com #6/#103,
que já estão contigo.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
A issue #34 (P1, a mais antiga do backlog) já consumiu duas PRs e dois quóruns adversariais, e as
duas morreram no mesmo lugar. Não é falta de capricho: é um limite estrutural que só o dono pode
destravar, porque envolve o que o leaderboard significa.
O buraco, em uma linha
POST /api/rooms/<code> {"action":"finish","results":[{"name":"HACK","wpm":349,"place":1}]}— umrequest anônimo põe qualquer nick no topo do Ranking Global, sem digitar uma tecla. Continua aberto
em produção hoje.
Por que duas tentativas falharam
wpm <= f(tempo)68c47aeprogress2247d44(reparo de hoje)(N/5)/350progress: 1e-9); e descarta honesto rápido por skew do relógio do cliente (até 4 s) em snippet curtoA causa raiz é sempre a mesma: o servidor não conhece nem a identidade de quem manda a linha, nem a
janela real de digitação dela. Só tem
(trabalho alegado, tempo do servidor)— e com esses doisinsumos, toda regra ou é vazada por alegar trabalho ≈ 0, ou apaga jogador honesto.
Opções (precisa da sua escolha)
A — Elegibilidade: só corrida COMPLETA entra no ranking.
buildScoreRows/buildMatchRowpassam aexigir
finished: trueeprogress >= 1. Aí o trabalho deixa de ser escolhido pelo atacante (ésempre o snippet inteiro) e o mínimo físico morde de verdade.
Custo: barato (uma PR pequena, sem migração). Resíduo: o forjador ainda entra esperando o mínimo
do menor snippet — 1,4 s (40 chars) — então não fecha a #34, só encarece. Perde-se também
qualquer aparição de run parcial no ranking (hoje entra, e é discutível que devesse).
B — Identidade/roster no servidor (fecha a classe inteira). O
finishsó aceita linhas de nicksque o servidor viu na sala, e só do líder legítimo. Fecha #34, #6 e boa parte da #103 de uma vez.
Custo: alto — a presença hoje vive no Realtime (cliente); exige o servidor manter roster por sala
(tabela ou presence server-side). É a única opção que realmente fecha.
C — Aceitar o resíduo e reposicionar o produto. Assumir que o Ranking Global é "placar de
diversão", não competição verificável; mergear só as peças estritamente positivas (guarda de status
no
start, #131) e fechar a #34 como "não fecha sem B", tirando ela da fila.Custo: zero de engenharia, custo de posicionamento.
Recomendação da frota
A + #131 agora (ambas pequenas e sem risco), com a #34 re-escopada e marcada
blockedpor B —em vez de continuar gerando PRs que o quórum derruba. B é candidata natural a epic junto com #6/#103,
que já estão contigo.
O que já está feito e não espera decisão
decisao-dono, com o parecer completo do quórum (3 vetores reproduzidos).startnão exigestatus = lobby— um POST reinicia corrida em andamento (route.ts:147-164) #131 aberta:startnão exigestatus = lobby— um POST reinicia corrida em andamento. Bugindependente da Anti-cheat: finish sem coerência temporal — leaderboard forjável em 1 request e corrida alheia encerrável #34, e pré-existente na
main.All reactions