Segurança de código na era da IA

Sete itens simples que já colocam seu sistema na frente de 99% dos sistemas por aí em termos de segurança. Nada de teoria complicada, só o básico que todo sistema em produção precisa ter.


Segurança de código não é assunto reservado para especialista. É um conjunto de hábitos simples que, se você seguir, já coloca seu sistema na frente da maioria do que existe por aí. Não precisa de curso avançado nem de certificação, precisa de disciplina em sete pontos. Vamos por eles.

1. Nunca confie no input do usuário

Tudo que vem de fora do seu servidor é suspeito por padrão. Input de formulário, arquivo enviado, parâmetro na URL, query parameter, tudo isso é externo e não pode ser tratado como confiável.

O processo tem dois passos. Primeiro, validar. Cada tipo de informação tem sua validação: um campo de e-mail precisa pelo menos parecer um e-mail, um campo de ordenação só pode receber uma das opções que você realmente tem disponível, um nome precisa ter um tamanho mínimo plausível. Dependendo da exigência do sistema, a validação pode ir mais longe, por exemplo checar se o domínio de um e-mail tem registro MX válido, ou seja, se aquele domínio realmente pode receber mensagens.

Segundo passo, e o que mais gente esquece: sanitizar. Não basta validar que o formato bate, é preciso remover do input tudo que não faz parte do que era esperado. Um nome não tem aspas, não tem interrogação, não tem símbolo de código. Se você pega o que o usuário digitou e manda direto para o banco de dados sem sanitizar, abre espaço para coisas como SQL injection, onde um input malicioso engana o banco e executa comandos que não deveriam rodar, inclusive apagar tabelas inteiras.

Se o usuário errou o próprio nome, o problema é dele. Mas garantir que a informação que chega no seu servidor é segura para processar, isso é responsabilidade sua.

2. Autenticação e autorização

Autenticação é provar quem você é. Autorização é o que você pode fazer depois de provado quem você é. São conceitos diferentes e os dois precisam de cuidado.

Primeira regra, e essa não se negocia: nunca salve senha pura. Nem em base64, nem em MD5, isso não é criptografia adequada para senha. O padrão é usar algo como bcrypt, que permite validar a senha sem que o sistema precise saber qual é a senha real. Mesmo que seu banco de dados vaze por qualquer motivo, a senha do usuário continua protegida.

Se o sistema é multi-tenant, ou seja, várias empresas ou contas diferentes usam o mesmo sistema mas cada uma só deveria ver os próprios dados, o cuidado extra é nas ações, não só nas visualizações. Antes de deletar ou alterar um registro, não basta verificar se ele existe, é preciso verificar se ele pertence a quem está fazendo a requisição. Sempre validar o tenant em toda operação que modifica dado, senão você abre brecha para uma empresa mexer no dado de outra.

3. Gerenciamento de segredos

Chave de API, chave privada, senha de acesso, nada disso vai para o repositório. Nunca, mesmo que o repositório seja privado.

O caminho é variável de ambiente. Você concentra essas informações num arquivo .env e cria um .gitignore que ignora esse arquivo, garantindo que ele nunca seja versionado. Para dar contexto de quais chaves o sistema precisa, sem expor valor nenhum, você mantém um .env.example com o nome das variáveis, vazio. Em times maiores, é comum ir além disso e usar um cofre de segredos, tipo Infisical, para nem os próprios desenvolvedores terem acesso direto aos valores.

Se por algum motivo uma chave foi commitada sem querer, trocar a chave não resolve sozinho, porque ela continua visível no histórico do repositório. Nesse caso o caminho mais seguro é descartar aquele repositório e recriar um novo sem aquele histórico.

4. Dependências

Mais de 80% das vulnerabilidades de um sistema vêm de dependências externas, não do código que você escreveu. Cada biblioteca que você instala carrega o próprio risco, e muitas vezes carrega outras dependências junto.

Isso não quer dizer parar de usar dependências, quer dizer escolher com critério. Prefira bibliotecas com o mínimo de dependências próprias e que tenham manutenção ativa, ou seja, alguém realmente cuidando daquilo, corrigindo falha quando aparece. E usa ferramentas de auditoria, como npm audit ou Dependabot, para monitorar automaticamente se alguma versão que você tem instalada ganhou uma vulnerabilidade conhecida.

5. Rate limiting

Se sua API está aberta sem nenhum limite, alguém pode simplesmente bombardear ela com milhares de requisições por segundo e derrubar o servidor, ou tentar força bruta em endpoints de login.

A solução básica é limitar quantas requisições uma mesma origem pode fazer num intervalo de tempo, por IP, por chave de acesso ou por usuário logado. Passou do limite, bloqueia antes mesmo de processar a requisição. Em Node com Express, express-rate-limit já resolve a maioria dos casos. Cloudflare e qualquer API gateway também oferecem isso pronto. Redis costuma ser usado por trás para guardar essa contagem, porque roda em memória e é rápido o suficiente para não virar gargalo.

6. Observabilidade e logs

Log é importante, mas nem todo tipo de log deve existir. Senha digitada, chave de acesso, CPF, prompt inteiro de IA com dado sensível dentro, nada disso deveria parar no log.

O ponto de equilíbrio é registrar o evento sem registrar o dado sensível. Em vez de logar a senha que o usuário tentou usar no login, loga que aquele IP ou usuário tentou fazer login, e mascara o resto. Você continua sabendo que a tentativa aconteceu, sem expor a informação que não deveria estar ali. Log tende a se acumular sem ninguém prestar atenção nele até o dia em que alguém precisa olhar, e nesse dia você não quer descobrir que ele está cheio de dado sensível.

7. Princípio do menor privilégio

Dê para cada recurso, seja usuário de banco de dados, contêiner, permissão de nuvem ou CORS, só o privilégio mínimo necessário para fazer o que ele precisa fazer, nada além disso.

É tentador liberar tudo porque é mais rápido, mas isso significa que se alguém explorar uma falha em qualquer ponto do sistema, o estrago vai até onde a permissão daquele recurso permitir. Um usuário de banco de dados sem permissão de drop table não impede um ataque, mas impede que aquele ataque específico consiga apagar o banco inteiro. É a última linha de defesa, e ela só existe se você desenhar as permissões pensando nisso desde o início.

Fechando

Nenhum desses sete itens é complexo isoladamente. O que os torna importantes é que, juntos, cobrem a maior parte dos vetores de ataque mais comuns. Não é sobre virar paranoico, é sobre ter esses hábitos incorporados no jeito de programar, principalmente agora que boa parte do código sai de uma IA e precisa de alguém revisando com esses pontos em mente.