← Blog
·10 blog.minutes

Código Seguro: Um Guia do Desenvolvedor para Escrever Código Seguro

Segurança não é trabalho do time de operações. Cada linha de código que você escreve ou abre uma porta ou a fecha com ferrolho. Aqui está o que todo desenvolvedor precisa saber sobre escrever código seguro em 2026.

Existe uma ideia perigosa que se infiltrou em nossa indústria: que segurança é problema de outra pessoa. Que pertence ao time de operações, aos engenheiros de segurança, aos testadores de penetração dedicados que aparecem uma vez por trimestre para cutucar a aplicação. Esta ideia está errada, e é responsável pela maioria das violações que você lê em qualquer terça-feira.

A realidade é que a segurança é decidida no editor, não na revisão de segurança. Cada decisão que você toma — como você constrói uma consulta SQL, como você lida com entrada do usuário, como você armazena um token de sessão — ou abre uma porta ou a fecha com ferrolho. O atacante precisa de uma porta aberta. Você precisa fechar todas.

Este artigo aborda as práticas essenciais de codificação segura que todo desenvolvedor deveria conhecer. Ele se baseia no OWASP Top 10 para 2026, que viu mudanças significativas, e cobre as vulnerabilidades que aparecem em bases de código reais dia após dia: injeção, cross-site scripting, cross-site request forgery, falhas de autenticação, riscos de dependência e exposição de segredos. Cada seção inclui padrões concretos e acionáveis que você pode aplicar hoje.

O OWASP Top 10 em 2026: O Que Mudou e Por Quê

O OWASP Top 10 é o mais próximo que nossa indústria tem de uma lista consensual dos riscos de segurança mais críticos em aplicações web. A edição de 2026 introduziu várias mudanças notáveis que refletem como o cenário de ameaças evoluiu. Entender essas mudanças ajuda você a focar seus esforços nos riscos que realmente importam.

A maior mudança em 2026 é a elevação do código gerado por IA como uma categoria de risco distinta. Pela primeira vez, o OWASP reconhece explicitamente que código produzido por modelos de linguagem de grande escala introduz desafios únicos de segurança. Esses modelos são treinados em vastos corpora de código público, que inclui desde bibliotecas bem mantidas até snippets do Stack Overflow copiados e colados por desenvolvedores que não os entendiam. A IA reproduz os padrões que viu, incluindo os vulneráveis. Um estudo de 2025 da Synk descobriu que assistentes de IA sugeriram código contendo vulnerabilidades conhecidas aproximadamente 30% das vezes quando recebiam prompts ambíguos em relação à segurança. A mensagem é clara: código gerado por IA precisa do mesmo nível de escrutínio que código escrito por um contratante desconhecido.

Outra mudança significativa é a consolidação das categorias de injeção. Edições anteriores tratavam injeção SQL, injeção NoSQL, injeção de comando de sistema operacional e injeção LDAP como entradas separadas. A edição de 2026 as agrupa sob uma única categoria chamada "Injeção", refletindo a realidade de que o padrão subjacente — dados não confiáveis concatenados em um interpretador — é o mesmo independentemente do alvo. Esta é uma reestruturação útil porque direciona a atenção para a causa raiz em vez da variante específica.

Falhas criptográficas foram promovidas na lista, impulsionadas pela prevalência crescente da criptoanálise baseada em IA e pelo contínuo desastre do planejamento de migração quântica-adjacente. Se você não está usando criptografia autenticada moderna (AES-GCM, ChaCha20-Poly1305) e se você nem começou a pensar em migração pós-quântica para dados de longa duração, você está acumulando dívida futura que vencerá com juros.

A lista de 2026 também mescla má configuração de segurança e falhas de integridade de software em uma categoria mais ampla chamada "Configuração e Comprometimento", reconhecendo que muitas falhas de integridade — como usar imagens base não confiáveis ou dependências não assinadas — são fundamentalmente decisões de configuração tomadas no início do ciclo de vida de desenvolvimento.

Segurança não é um produto ou uma funcionalidade. É uma propriedade da cultura de engenharia. Se seu time não se importa com segurança durante a fase de design, nenhuma quantidade de varredura no final vai consertar isso.

Ataques de Injeção: A Mesma História de Sempre, Ainda o Maior Problema

Ataques de injeção continuam no topo da lista OWASP por uma razão: são simples de executar, devastadores em impacto e surpreendentemente comuns em código de produção. O problema fundamental é que um programa constrói um comando ou consulta concatenando strings que incluem entrada controlada pelo usuário. O atacante cria essa entrada para escapar do contexto pretendido e injetar suas próprias instruções.

Injeção SQL é o exemplo clássico, e ainda funciona. Desenvolvedores que aprenderam sobre injeção SQL na universidade há dez anos ainda escrevem código vulnerável sob pressão de prazos. A correção é bem compreendida: consultas parametrizadas, prepared statements ou um ORM que lida com escaping corretamente.

// Vulnerável: concatenação de strings com entrada do usuário
const query = `SELECT * FROM users WHERE email = '${req.query.email}'`;
db.execute(query);

// Corrigido: consulta parametrizada
const query = 'SELECT * FROM users WHERE email = ?';
db.execute(query, [req.query.email]);

O padrão vulnerável deveria parecer obviamente errado para qualquer desenvolvedor experiente, ainda assim aparece em revisões de código toda semana. As razões são sempre as mesmas: pressão de prazo, uma consulta que parecia simples demais para se preocupar com parametrização, ou um ORM que não estava disponível para o driver de banco de dados específico. Nenhuma dessas razões sobrevive a um post-mortem de incidente.

O mesmo princípio se aplica a bancos de dados NoSQL. MongoDB, por exemplo, é vulnerável a injeção quando a entrada do usuário é passada diretamente para objetos de consulta. Um atacante pode passar { "$ne": "" } como valor de senha para contornar a autenticação completamente se a consulta for construída a partir de entrada bruta.

Injeção de comando de sistema operacional é outra variante que se recusa a morrer. Chamar exec, spawn ou child_process com entrada controlada pelo usuário é catastrófico. Se você precisa executar um comando externo, valide a entrada contra uma lista de permissões estrita de valores permitidos. Nunca construa strings de comando a partir de entrada do usuário.

Cross-Site Scripting e o Desafio das SPAs

Cross-site scripting (XSS) é um ataque de injeção onde o alvo é o DOM do navegador em vez de um banco de dados. Um atacante injeta JavaScript malicioso em uma página web, e esse script executa no contexto da sessão da vítima, dando a ele acesso a cookies, localStorage, dados de sessão e a capacidade de fazer requisições em nome do usuário.

A ascensão de aplicações de página única construídas com React, Vue e Svelte mudou o cenário de XSS significativamente. Frameworks modernos automaticamente escapam valores em expressões de template, o que elimina os vetores de XSS mais comuns — mas desenvolvedores ainda podem contornar essas proteções. O bypass mais comum é dangerouslySetInnerHTML no React, v-html no Vue ou @html no Svelte. Essas APIs existem para casos de uso legítimos (renderizar rich text de um CMS), mas abrem a porta para XSS se o conteúdo não for sanitizado primeiro.

// Vulnerável: renderizando HTML não sanitizado de entrada do usuário
function Comment({ body }) {
  return <div dangerouslySetInnerHTML={{ __html: body }} />;
}

// Corrigido: sanitize antes de renderizar
import DOMPurify from 'dompurify';

function Comment({ body }) {
  const clean = DOMPurify.sanitize(body);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

A renderização do lado do servidor adiciona outra dimensão à proteção XSS. Se sua aplicação renderiza conteúdo gerado pelo usuário no servidor e envia HTML para o cliente, esse conteúdo deve ser escapado no lado do servidor. Muitos mecanismos de template padrão produzem saída escapada (Handlebars, EJS com escaping, Pug), mas desenvolvedores podem optar por saída não escapada com helpers como triple-stash {{{ }}} ou o operador pipe. Revise toda ocorrência de saída não escapada em seus templates.

O terceiro vetor de XSS que desenvolvedores frequentemente ignoram é a injeção indireta via atributos de dados e URLs. Se você define um atributo data em um elemento DOM usando entrada do usuário, você pode estar vulnerável se a entrada contiver caracteres que quebrem o contexto do atributo. Similarmente, se você constrói URLs a partir de entrada do usuário e as injeta em tags ancora, um atacante pode usar URLs javascript: para executar código arbitrário.

Cross-Site Request Forgery e Endurecimento de Autenticação

Cross-site request forgery (CSRF) explora a confiança que uma aplicação web tem no navegador de um usuário autenticado. Um atacante cria uma requisição para sua aplicação e engana um usuário autenticado para executá-la — visitando uma página maliciosa, clicando em um link, ou até mesmo carregando uma imagem. Se sua aplicação depende apenas de cookies de sessão para autenticação, o navegador automaticamente inclui esses cookies com toda requisição ao seu domínio, tornando ataques CSRF possíveis.

A defesa padrão é um token CSRF: um valor único e impossível de adivinhar embutido em todo formulário e validado no servidor. Quando um usuário envia um formulário, o servidor verifica se o token CSRF corresponde ao armazenado na sessão. Uma requisição falsificada de um atacante não pode incluir este token porque a Same-Origin Policy impede a página do atacante de ler a resposta do seu servidor.

// Vulnerável: sem proteção CSRF
app.post('/api/transfer', (req, res) => {
  const { toAccount, amount } = req.body;
  transferFunds(req.session.userId, toAccount, amount);
  res.json({ ok: true });
});

// Corrigido: valide o token CSRF
app.post('/api/transfer', (req, res) => {
  const token = req.headers['x-csrf-token'];
  if (!validateCsrfToken(req.session, token)) {
    return res.status(403).json({ error: 'Token CSRF inválido' });
  }
  const { toAccount, amount } = req.body;
  transferFunds(req.session.userId, toAccount, amount);
  res.json({ ok: true });
});

Frameworks modernos incluem proteção CSRF pronta para uso. Middleware Express.js como csurf (obsoleto mas ainda usado), middleware CSRF embutido do Django e o mecanismo de authenticity token do Rails implementam o mesmo padrão. O erro que desenvolvedores cometem é desabilitar a proteção CSRF para endpoints de API que não servem formulários HTML, esquecendo que esses endpoints ainda podem ser alvo de requisições cross-origin se a configuração CORS adequada não estiver em vigor.

O endurecimento de autenticação vai além do CSRF. Toda sessão deve ter uma expiração. Todo token de redefinição de senha deve ser de uso único e com limite de tempo. Todo endpoint de login deve implementar limitação de taxa para prevenir ataques de força bruta. Autenticação multifator deve estar disponível e ser incentivada para todos os usuários, e obrigatória para contas administrativas.

Limitação de taxa é uma das medidas de segurança mais custo-efetivas que você pode implementar. Requer código mínimo, não tem custo de manutenção contínuo e previne uma classe inteira de ataques: credential stuffing, força bruta, ataques de enumeração e muitas formas de abuso de API. Implemente limitação de taxa no nível da aplicação (por usuário), no nível da rede (por IP) e no nível do endpoint (por operação sensível). Um ponto de partida razoável para endpoints de login é cinco tentativas por minuto por usuário.

Gerenciamento de Dependências e Manipulação de Segredos

Aplicações modernas são construídas sobre uma fundação de dependências de código aberto. Um projeto Node.js típico tem milhares de dependências transitivas. Cada uma é um vetor de ataque em potencial. O incidente event-stream de 2024, onde um ator malicioso ganhou acesso de mantenedor e injetou código de roubo de criptomoedas em um pacote npm popular, não é uma exceção — é um aviso sobre o risco sistêmico da cadeia de suprimentos de código aberto.

  • Use um gerenciador de pacotes com verificação de integridade. npm shrinkwrap e yarn.lock garantem que toda instalação use exatamente a mesma árvore de dependências. Fixe suas dependências, não use operadores de intervalo.
  • Execute varredura de vulnerabilidades de dependências como parte do seu pipeline CI. Ferramentas como npm audit, Snyk, Dependabot e Trivy detectam automaticamente vulnerabilidades conhecidas em seu grafo de dependências e bloqueiam builds quando vulnerabilidades críticas estão presentes.
  • Remova dependências não utilizadas. Todo pacote que você não está usando ativamente é um passivo sem benefício. Use ferramentas como depcheck para identificar peso morto em seu package.json.
  • Audite as permissões solicitadas por cada dependência. Um pacote que lê variáveis de ambiente, acessa o sistema de arquivos e faz requisições de rede é um risco. Justifique toda permissão.
  • Configure atualizações automatizadas de dependências com um processo de revisão. Dependabot ou Renovate abrirão pull requests quando novas versões estiverem disponíveis. Revise o changelog, verifique se há mudanças que quebram compatibilidade e faça merge prontamente — especialmente para patches de segurança.

Gerenciamento de segredos é a outra metade desta equação. Chaves de API, senhas de banco de dados e segredos de criptografia codificados no código fonte são um dos achados mais comuns em testes de penetração. E são totalmente evitáveis.

// Vulnerável: segredos codificados no código fonte
const DB_PASSWORD = 'sup3r-s3cr3t-passw0rd!';
const API_KEY = 'sk-live-abc123def456';

// Corrigido: use variáveis de ambiente com validação
function getEnv(name: string): string {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Variável de ambiente obrigatória ausente: ${name}`);
  }
  return value;
}

const dbPassword = getEnv('DB_PASSWORD');
const apiKey = getEnv('API_KEY');

// Também: use arquivos .env localmente, nunca os commite
// Adicione .env ao .gitignore desde o dia um

Se você já cometeu um segredo a um repositório git, ele está comprometido. Remover o segredo do último commit não é suficiente — ele ainda está no histórico do git. Qualquer pessoa com acesso ao repositório pode encontrá-lo. O único curso de ação seguro é rotacionar o segredo imediatamente e usar uma ferramenta como git-filter-repo ou BFG Repo-Cleaner para purgá-lo do histórico se o repositório for público ou compartilhado.

Use um gerenciador de segredos dedicado em produção. HashiCorp Vault, AWS Secrets Manager, Azure Key Vault ou Google Secret Manager fornecem armazenamento seguro, auditoria de acesso, rotação automática e controle de acesso refinado. Variáveis de ambiente são aceitáveis para desenvolvimento local, mas segredos de produção devem ser buscados de um repositório de segredos na inicialização da aplicação, não embutidos na configuração de ambiente.

Ferramentas: SAST, DAST e Headers de Segurança

Codificação segura não é apenas sobre o que você escreve — é também sobre o que você verifica e como você implanta. Os times de desenvolvimento mais disciplinados combinam análise estática, análise dinâmica e endurecimento de infraestrutura para criar múltiplas camadas de defesa.

Ferramentas de Teste de Segurança de Aplicação Estática (SAST) analisam o código fonte sem executá-lo. Elas procuram padrões que indicam vulnerabilidades: injeção SQL, XSS, desserialização insegura, segredos codificados e mais. Ferramentas SAST integram-se ao fluxo de trabalho de desenvolvimento e fornecem feedback no momento em que o código é escrito, que é quando é mais barato corrigir. Ferramentas SAST populares incluem Semgrep, SonarQube, CodeQL e ESLint com plugins de segurança. A chave para a adoção bem-sucedida de SAST é corrigir o nível de ruído — ajuste as regras para que todo alerta seja acionável, ou os desenvolvedores aprenderão a ignorá-los.

Ferramentas de Teste de Segurança de Aplicação Dinâmica (DAST) analisam uma aplicação em execução enviando requisições elaboradas e observando as respostas. Elas detectam vulnerabilidades que ferramentas SAST não conseguem encontrar, como CSRF, bypass de autenticação e problemas de configuração. Ferramentas DAST incluem OWASP ZAP, Burp Suite, Acunetix e StackHawk. DAST é mais eficaz quando integrada ao pipeline CI/CD e executada contra ambientes de staging após o deploy.

A terceira camada é o endurecimento de infraestrutura através de headers de segurança. Headers de resposta HTTP dizem ao navegador como se comportar ao renderizar sua aplicação. Configurá-los corretamente previne uma classe inteira de ataques do lado do cliente sem custo de tempo de execução.

  • Content-Security-Policy: Restringe quais fontes o navegador pode carregar recursos. Previne XSS mesmo se um atacante conseguir injetar uma tag script.
  • Strict-Transport-Security: Força o navegador a usar HTTPS para todas as conexões ao seu domínio. Previne ataques de stripping SSL.
  • X-Frame-Options: Impede que sua aplicação seja embutida em iframes em outros domínios. Mitiga ataques de clickjacking.
  • X-Content-Type-Options: Impede o navegador de farejar o tipo MIME da resposta. Reduz o risco de injeção de script via tipos de conteúdo mal rotulados.
  • Set-Cookie com flags Secure, HttpOnly e SameSite: Garante que cookies sejam enviados apenas sobre HTTPS, sejam inacessíveis a JavaScript e não sejam enviados cross-origin. SameSite=Lax ou Strict é a defesa CSRF mais eficaz.

Headers de segurança são fáceis de configurar e imediatamente eficazes. Use uma ferramenta como securityheaders.com para escanear sua aplicação e identificar headers ausentes. A maioria dos frameworks de aplicação suporta configurar esses headers através de middleware — Helmet para Express.js, o middleware de segurança para Django e Rack::Protection para Rails são todos bem mantidos e amplamente utilizados.

A combinação de SAST em tempo de desenvolvimento, DAST em tempo de deploy e headers de segurança na camada de infraestrutura cria cobertura sobreposta. Se uma camada perde uma vulnerabilidade, outra camada pode pegá-la. Isto é defesa em profundidade aplicada ao ciclo de vida de desenvolvimento de software.

Construindo uma Cultura de Segurança

Nenhuma ferramenta, checklist ou framework pode substituir um time de desenvolvimento que genuinamente internaliza segurança como uma restrição de design. A diferença entre um time que escreve código seguro e um que não escreve não é a qualidade de sua ferramenta de análise estática — é o conjunto de hábitos e suposições embutidos em seu fluxo de trabalho diário.

O hábito mais importante é a modelagem de ameaças antes da implementação. Antes de escrever uma única linha de código para uma nova funcionalidade, pergunte: o que o atacante está tentando fazer? Qual é o dado mais valioso que esta funcionalidade toca? O que acontece se um atacante controlar a entrada? O que acontece se o banco de dados for comprometido? Responder estas perguntas no início revela vulnerabilidades de nível de design que nenhuma revisão de código pegará porque a vulnerabilidade está na arquitetura, não na implementação.

O segundo hábito é a revisão por pares com segurança como uma preocupação de primeira classe. Revisões de código que focam apenas em estilo, correção e desempenho perdem problemas de segurança. Adicione um item de checklist de segurança ao template do seu pull request: injeção SQL? XSS? CSRF? Segredos no diff? Verificações de autorização? Limitação de taxa? Ao tornar a segurança parte do processo de revisão, você distribui a responsabilidade pelo time e pega problemas antes que cheguem à produção.

O terceiro hábito é aprender com incidentes. Quando uma vulnerabilidade de segurança é encontrada — seja em sua base de código ou na de outro — trate-a como uma oportunidade de ensino. Realize um post-mortem que foque nas causas sistêmicas: por que a vulnerabilidade foi introduzida? Por que não foi pega na revisão? Que mudança de processo preveniria esta classe de vulnerabilidade no futuro? Post-mortems sem busca de culpados constroem uma cultura de segurança. Post-mortens movidos a culpa a destroem.

Codificação segura não é uma habilidade que você aprende uma vez e pronto. O cenário de ameaças evolui. As ferramentas evoluem. Seu próprio entendimento evolui. Todo desenvolvedor deveria investir tempo a cada trimestre aprendendo sobre novos vetores de ataque, novas técnicas de defesa e novas ferramentas. Leia a série de cheat sheets do OWASP. Siga pesquisadores de segurança nas plataformas onde eles publicam. Execute um teste de penetração contra sua própria aplicação. O objetivo não é alcançar segurança perfeita — isso não é possível. O objetivo é tornar sua aplicação um alvo mais difícil que o próximo, para que o atacante siga em frente.

Porque é a isso que se resume. Segurança não é sobre construir um sistema inquebrável. É sobre construir um sistema que não vale a pena quebrar comparado às alternativas. Toda vulnerabilidade que você fecha, todo segredo que você para de codificar, todo limite de taxa que você implementa torna outra pessoa um alvo mais atraente. Não seja a fruta mais baixa.