Consertando um redirect, e o caso extremo que uma linha deixou passar
Esse aqui saiu de um fix anterior, não do nada. Revisando o fix do 500 sem autenticação no painel de jobs há uns dois dias, percebi que o fluxo de login faz algo meio arriscado: qualquer URL que você estava tentando acessar quando sua sessão expirou fica guardada, e depois que você loga de novo, você é redirecionado direto de volta para ela. Conveniente, quando funciona. O bug é que isso guarda a URL para qualquer requisição, incluindo aquelas que só fazem sentido como POST, e depois redireciona cegamente para ela com um GET. Um GET numa rota que só aceita POST não bate com nada. 404, logo depois que você acabou de logar de novo.
O que eu construí
O fix parecia pequeno de início: só lembrar da URL se a requisição original fosse um GET (ou HEAD, que o Rails trata como GET para fins de roteamento). Ignorar qualquer outro método e deixar o fallback existente te mandar para a homepage. Uma linha, uma palavra adicionada.
O que me surpreendeu
Rodei isso pelo Brakeman antes de abrir o PR, mais por hábito a essa altura, e ele sinalizou justamente a linha que eu tinha acabado de escrever. Não a lógica, a checagem específica. Eu tinha escrito request.get? sozinho, e o Brakeman apontou que uma requisição HEAD escaparia dessa checagem, mesmo o Rails roteando HEAD para o mesmo lugar que um GET iria. Eu teria chamado isso de detalhe irrelevante uma semana atrás. Não é. Se um navegador ou uma ferramenta de monitoramento manda uma requisição HEAD para uma página protegida, meu fix de uma linha trataria isso como inseguro e pularia guardar o caminho de retorno, quando o objetivo inteiro era manter isso justamente para esse tipo de requisição só de leitura. Corrigido com || request.head?, que, como percebi depois revisando meu próprio diff, é a expressão literal que o próprio Rails usa internamente no seu código de proteção CSRF. Não foi uma escolha de estilo que eu inventei, é um nome para uma categoria que o Rails já reconhece e que eu só tinha implementado pela metade.
A pegadinha de verdade veio da revisão de código, porém. Ela apontou que pular a escrita para uma requisição insegura não é a mesma coisa que limpar o que já estava guardado ali. Imagine isso: você acessa uma página sem estar autenticado, é mandado para o login, e a URL é salva. Antes de você realmente logar, alguma outra coisa dispara um POST em segundo plano, um botão desatualizado numa página que você tinha aberta, uma nova tentativa, sei lá. Esse POST também é interceptado e redirecionado para o login, mas com meu fix, ele só deixa de guardar qualquer coisa nova. Ele não toca no que já estava sentado na sessão vindo do GET anterior. Então quando você finalmente loga, você cai naquela primeira página, sem relação nenhuma, em vez de algo previsível. Dessa vez não é um 404, só está silenciosamente errado.
Escrevi a reprodução como um teste antes de tocar no código: GET, depois POST, depois login, verifico onde caio. Falhou exatamente como a revisão previu. O fix de verdade foi mais uma linha, limpar explicitamente o valor guardado em vez de só pular a escrita, e o mesmo teste passou a ficar verde.
O que vem a seguir
Nada pendente. O que ficou comigo aqui foi a distância entre “o fix que eu escrevi” e “o fix que de fato fecha a brecha”. Tanto a ferramenta quanto uma segunda olhada pegaram coisas que eu tinha certeza de já ter coberto. Vale lembrar que confiança não é o mesmo que cobertura.
Leitura relacionada
Transformando um seletor de imagem de destaque só do Pexels num registro de provedores (e a revisão que pegou uma falsificação de licença)
Uma refatoração de rotina no registro de provedores revelou um parâmetro sem validação que podia commitar uma imagem mal licenciada como legítima.
Três linhas de config, uma tarde de verificação
Descomentar três flags de SSL do Rails levou dez minutos. Ler o framework e testar dois achados de revisão plausíveis mas errados levou o resto do dia.
O que acontece quando um job faz broadcast para ninguém
Fechando o loop da imagem de destaque: frontmatter só por inserção, uma guarda que pegou divergência real na primeira execução, e um broadcast sem ouvinte.
Você também pode achar útil
Proton Mail
E-mail criptografado de ponta a ponta, com arquitetura de acesso zero.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisProton Pass
Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisProton VPN
VPN comercial com filtragem NetShield e interruptor de desligamento automático.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba mais