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
A PR #106 (filtros de linguagem/dificuldade/período no Ranking Global, issue #92) passou no
gate, na CI e na lente AppSec do quórum — mas foi vetada pela lente Ofensiva com vetor
concreto. Não é um defeito de implementação: é um conflito estrutural entre o que a #92 pede e
como o banco está hoje. Por isso vem para você.
O problema em uma frase
Para mostrar "o melhor de cada jogador dentro do bucket filtrado", o dedupe por nick tem de
acontecer depois do filtro. A view leaderboard faz distinct on (lower(name))antes de
qualquer filtro, então a PR move o dedupe para o TypeScript — e, para isso, lê uma janela de 500
linhas cruas antes de deduplicar. Quem controla essas 500 linhas controla o ranking inteiro.
O ataque (verificado passo a passo em origin/main)
Sem login, sem digitar nada, ~52 requests de curl:
POST /api/rooms com maxPlayers: 30 (room.ts:282)
{action:"start"} → racing
{action:"finish", results:[…30 linhas com wpm:350…]} — finishnão exige liderança nem
prova de corrida, e sanitizeResults (room.ts:322) só descarta wpm > 350
{action:"reset"} → repete · 17 iterações = 510 linhas no teto de WPM
A leitura (supabase.ts:90) é lte(350) → order wpm desc, created_at desc → limit 500: essas
linhas ocupam a janela inteira, bestPerName colapsa todas em uma, e /leaderboard passa a
exibir um único jogador — permanentemente, até alguém limpar o banco.
A raiz é a #34 (finish forjável em 1 request), já aberta como P1 — a PR não abre o buraco.
O que ela muda é o preço: hoje o ataque custa 1 das 25 vagas (a view deduplica a tabela toda
antes do limit, então as outras 24 seguem legítimas); com a #106 ele custa o ranking inteiro.
Como main = deploy, não mergeei.
Opções
A) Fechar a #34 primeiro, depois mergear a #106 como está. ← recomendada
Ataca a raiz. A #34 já é P1 e vale por si: hoje qualquer um forja o leaderboard global em 1
request, com ou sem esta PR. Fechada ela, a janela de 500 deixa de ser controlável por um
atacante e o veto cai sozinho. Custo: a #106 espera a #34. Nenhuma migration.
B) Dedupe no banco — migration aditiva com função RPC (distinct on (lower(name)) já com os
filtros dentro). Fecha o vetor de leitura independentemente da #34 e ainda melhora o custo da
query. Exige que você aplique a migration ANTES do merge — se o deploy sair antes, a página
chama uma função inexistente e /leaderboard quebra em produção. Um agente cria o arquivo; a
aplicação e a ordem do deploy são suas.
C) Mergear como está, aceitando o risco. Defensável só se o ranking for descartável no curto
prazo. Não recomendo: o dano é permanente e barato de causar.
D) Meio-termo sem migration — view no caminho sem filtro (ranking global fica tão resiliente
quanto hoje), scores + dedupe em TS só quando há filtro. Limita o dano ao bucket filtrado, mas
reintroduz o bug do registro legado no caminho principal e desfaz a unificação que o Parecer do
Conselho pediu. Troca um defeito por outro.
Estado da PR
Convertida para DRAFT + decisao-dono, aguardando esta decisão. O segundo veto do quórum
(contraste dos chips de linguagem — 15 das 24 cores reprovavam WCAG AA, lua = 1.58:1) já foi
reparado por mim em c0f0b59, com o gate inteiro verde. Fora esse ponto, a PR está pronta:
diff lido inteiro, CI verde, 170/170 testes, e a lente AppSec não achou vetor.
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.
O que aconteceu
A PR #106 (filtros de linguagem/dificuldade/período no Ranking Global, issue #92) passou no
gate, na CI e na lente AppSec do quórum — mas foi vetada pela lente Ofensiva com vetor
concreto. Não é um defeito de implementação: é um conflito estrutural entre o que a #92 pede e
como o banco está hoje. Por isso vem para você.
O problema em uma frase
Para mostrar "o melhor de cada jogador dentro do bucket filtrado", o dedupe por nick tem de
acontecer depois do filtro. A view
leaderboardfazdistinct on (lower(name))antes dequalquer filtro, então a PR move o dedupe para o TypeScript — e, para isso, lê uma janela de 500
linhas cruas antes de deduplicar. Quem controla essas 500 linhas controla o ranking inteiro.
O ataque (verificado passo a passo em
origin/main)Sem login, sem digitar nada, ~52 requests de
curl:POST /api/roomscommaxPlayers: 30(room.ts:282){action:"start"}→racing{action:"finish", results:[…30 linhas com wpm:350…]}—finishnão exige liderança nemprova de corrida, e
sanitizeResults(room.ts:322) só descartawpm > 350{action:"reset"}→ repete · 17 iterações = 510 linhas no teto de WPMA leitura (
supabase.ts:90) élte(350) → order wpm desc, created_at desc → limit 500: essaslinhas ocupam a janela inteira,
bestPerNamecolapsa todas em uma, e/leaderboardpassa aexibir um único jogador — permanentemente, até alguém limpar o banco.
A raiz é a #34 (
finishforjável em 1 request), já aberta como P1 — a PR não abre o buraco.O que ela muda é o preço: hoje o ataque custa 1 das 25 vagas (a view deduplica a tabela toda
antes do
limit, então as outras 24 seguem legítimas); com a #106 ele custa o ranking inteiro.Como
main= deploy, não mergeei.Opções
A) Fechar a #34 primeiro, depois mergear a #106 como está. ← recomendada
Ataca a raiz. A #34 já é P1 e vale por si: hoje qualquer um forja o leaderboard global em 1
request, com ou sem esta PR. Fechada ela, a janela de 500 deixa de ser controlável por um
atacante e o veto cai sozinho. Custo: a #106 espera a #34. Nenhuma migration.
B) Dedupe no banco — migration aditiva com função RPC (
distinct on (lower(name))já com osfiltros dentro). Fecha o vetor de leitura independentemente da #34 e ainda melhora o custo da
query. Exige que você aplique a migration ANTES do merge — se o deploy sair antes, a página
chama uma função inexistente e
/leaderboardquebra em produção. Um agente cria o arquivo; aaplicação e a ordem do deploy são suas.
C) Mergear como está, aceitando o risco. Defensável só se o ranking for descartável no curto
prazo. Não recomendo: o dano é permanente e barato de causar.
D) Meio-termo sem migration — view no caminho sem filtro (ranking global fica tão resiliente
quanto hoje),
scores+ dedupe em TS só quando há filtro. Limita o dano ao bucket filtrado, masreintroduz o bug do registro legado no caminho principal e desfaz a unificação que o Parecer do
Conselho pediu. Troca um defeito por outro.
Estado da PR
Convertida para DRAFT +
decisao-dono, aguardando esta decisão. O segundo veto do quórum(contraste dos chips de linguagem — 15 das 24 cores reprovavam WCAG AA,
lua= 1.58:1) já foireparado por mim em
c0f0b59, com o gate inteiro verde. Fora esse ponto, a PR está pronta:diff lido inteiro, CI verde, 170/170 testes, e a lente AppSec não achou vetor.
Parecer completo do quórum: #106 (comment)
All reactions