Como pensar sobre segurança
Uma funcionalidade pode funcionar perfeitamente e ainda assim não ser segura. Imagine uma pequena rota de pedidos:
app.get('/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id)
res.json(order)
})A rota lê um id, busca um pedido e o devolve. Se você testar com o seu próprio pedido, tudo parece certo. Agora mude o id na URL. Se o servidor nunca verifica quem é o dono daquele pedido, um cliente autenticado consegue ler o recibo de outro cliente.
Essa é a mentalidade de segurança: não pergunte apenas se o código funciona. Pergunte o que alguém poderia fazer ele fazer.
Segurança faz uma segunda pergunta: o que acontece quando alguém a usa de um jeito que não pretendíamos?
Perceba a fronteira
Uma fronteira de confiança é a linha entre o código que você controla e algo que pode te enviar um valor que você não escolheu.
O navegador fica do outro lado dessa linha. Um usuário pode mudar a URL, editar um campo de formulário oculto, repetir um parâmetro de consulta, remover um cookie ou pular seu front-end e enviar uma requisição manualmente.
Isso faz do navegador o lugar errado para tomar decisões que importam para você.
Verificações no navegador são úteis, mas não são o controle
A validação do lado do cliente ajuda as pessoas a preencherem formulários. Ela pega erros de digitação cedo e proporciona uma experiência melhor.
O servidor ainda precisa verificar o valor de novo, porque é o servidor que protege os dados.
Aqui está a mesma rota de pedidos com a decisão que faltava agora explícita:
app.get('/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id)
if (!order || order.userId !== req.user.id) {
return res.status(404).json({ error: 'Order not found' })
}
res.json(order)
})A linha importante é a comparação entre order.userId e req.user.id. O servidor está dizendo: esse pedido pode existir, mas também precisa pertencer à pessoa que está pedindo por ele.
Uma requisição é permitida quando o servidor consegue provar a decisão por conta própria.
Coloque as verificações importantes no servidor. É lá que seu app pode comparar a requisição com os dados em que já confia.
Nomeie o que pode quebrar
Depois de identificar uma decisão arriscada, dê um nome à possível falha:
| Pergunta | Termo de segurança | Exemplo |
|---|---|---|
| Quem pode ler isso? | Confidencialidade | Um cliente lê o pedido de outro cliente. |
| Quem pode mudar isso? | Integridade | Um cliente muda o preço antes de finalizar a compra. |
| As pessoas ainda conseguem usar isso? | Disponibilidade | Uma requisição deixa o app lento para todo mundo. |
Juntas, essas propriedades são chamadas de tríade CIA: confidencialidade, integridade e disponibilidade.
Um recibo vazado é um problema de confidencialidade. Um campo de preço oculto é um problema de integridade. Uma rota de busca que permite que uma requisição peça um milhão de registros é um problema de disponibilidade.
Alguns bugs afetam mais de uma propriedade. Se um cliente pode editar o endereço de entrega de outro cliente, ele consegue tanto vê-lo quanto mudá-lo.
Essas perguntas transformam uma preocupação vaga em algo que você consegue explicar. Um relatório de bug fica mais claro quando você consegue nomear o dano.
Pense pelo outro lado
A frase "pense como um atacante" pode soar dramática. No dia a dia do desenvolvimento, ela é mais discreta. Escolha uma entrada e pergunte:
- E se esse valor estiver ausente?
- E se ele for muito maior do que o esperado?
- E se ele for do tipo errado?
- E se ele pertencer a outro usuário?
- E se a mesma requisição for enviada milhares de vezes?
Pegue um formulário de perfil. A página pode mostrar um campo de texto para displayName, um userId oculto e um botão de enviar. O caminho pretendido é pequeno: a pessoa autenticada atualiza o próprio nome.
O caminho da segurança faz perguntas diferentes. Por que o userId está no formulário, afinal? O que acontece se ele apontar para outra pessoa? O servidor usa a sessão autenticada, ou ele confia no campo enviado?
Oculto não significa confiável
Um campo de formulário oculto está escondido da página, não da pessoa que envia a requisição.
Se o valor importa, derive-o no servidor a partir da sessão ou do banco de dados. Não peça para o navegador devolver uma decisão que o servidor já conhece.
O mesmo hábito funciona fora dos formulários. Um upload de arquivo afirma um nome de arquivo e um tipo de conteúdo. Um token afirma uma identidade. Um webhook afirma que um evento aconteceu. Verifique a afirmação no momento em que seu código está prestes a depender dela.
Isso já é pensamento de segurança, em uma forma pequena e utilizável.
Para onde isso vai a seguir
Essa mentalidade é a preparação para a próxima parte do curso.
A modelagem de ameaças pergunta o que poderia dar errado antes que uma funcionalidade seja lançada. O OWASP dá a você nomes compartilhados para bugs em aplicações em produção. A triagem de bugs ajuda você a decidir o que corrigir primeiro.
Mantenha o ciclo pequeno:
- Encontre a fronteira.
- Nomeie o que pode quebrar.
- Verifique a afirmação no servidor.

