Back

Codificação Segura para Desenvolvedores JavaScript

Codificação Segura para Desenvolvedores JavaScript

O JavaScript é executado em qualquer lugar onde haja um navegador, o que o torna uma das superfícies mais atacadas no desenvolvimento de software. A maioria das vulnerabilidades não vem de explorações exóticas—elas vêm de padrões previsíveis no código do dia a dia. Este guia cobre as práticas de codificação segura em JavaScript que mais importam quando seu código é executado diretamente no navegador.

Principais Pontos

  • XSS baseado em DOM é uma das vulnerabilidades JavaScript mais comuns—evite passar dados não confiáveis para destinos como innerHTML, eval() ou document.write()
  • Use textContent em vez de innerHTML para dados fornecidos pelo usuário, e sanitize com DOMPurify quando realmente precisar renderizar HTML
  • Nunca use eval(), Function() ou setTimeout/setInterval baseados em string com entrada dinâmica
  • Implemente Content Security Policy (CSP) com nonces ou hashes e combine-o com Trusted Types para defesa em profundidade
  • Evite armazenar tokens de autenticação ou segredos em localStorage ou sessionStorage—prefira cookies HttpOnly para identificadores de sessão
  • Sempre valide event.origin ao manipular eventos postMessage
  • Audite sua árvore de dependências regularmente e use Subresource Integrity para scripts carregados via CDN

XSS Baseado em DOM: Uma das Vulnerabilidades JavaScript Mais Comuns

Prevenir XSS em JavaScript começa com o entendimento de onde ele realmente se origina. XSS baseado em DOM acontece quando seu código lê de uma fonte controlada pelo atacante—como location.hash, document.referrer ou URLSearchParams—e a escreve na página de forma insegura.

A regra fundamental: nunca passe dados não confiáveis para um destino que interpreta HTML ou executa código.

Destinos inseguros a evitar com dados dinâmicos:

// ❌ Todos estes podem executar scripts injetados
element.innerHTML = userInput
element.outerHTML = userInput
document.write(userInput)
eval(userInput)
setTimeout(userInput, 0)       // apenas na forma de string
new Function(userInput)()

Alternativas seguras:

// ✅ Trata o conteúdo como texto, nunca como marcação
element.textContent = userInput
element.innerText = userInput

Quando você realmente precisar renderizar HTML—um editor de texto rico, por exemplo—sanitize primeiro com DOMPurify:

// ✅ Renderização segura de rich-text
element.innerHTML = DOMPurify.sanitize(userInput)

A mesma lógica se aplica a setAttribute. Atributos dinâmicos como href, src e manipuladores de eventos (onclick, onload) podem executar JavaScript. Mantenha-se em atributos estáticos e não executáveis ao trabalhar com valores fornecidos pelo usuário.

Execução Dinâmica de Código É Sempre Insegura

eval(), Function() e setTimeout/setInterval baseados em string não são apenas má prática—são vetores diretos de injeção de código. Não há maneira segura de usá-los com entrada não confiável.

Se você está analisando dados, use JSON.parse(). Se você precisa de comportamento dinâmico, use estruturas de dados e lógica explícita em vez de geração de código em tempo de execução.

Content Security Policy como Camada de Defesa

Content Security Policy (CSP) limita quais scripts podem ser executados e de onde podem ser carregados. Uma CSP rigorosa usando nonces ou hashes—em vez de 'unsafe-inline'—reduz significativamente o raio de explosão de qualquer XSS que passe despercebido.

Combine CSP com Trusted Types em navegadores compatíveis para impor escritas DOM seguras no nível da API. O suporte dos navegadores para Trusted Types continua a expandir e pode ser acompanhado em webstatus.dev.

APIs Seguras do Navegador: Cookies e Armazenamento Client-Side

Cookies que contêm identificadores de sessão devem sempre ter HttpOnly (bloqueia acesso JavaScript), Secure (apenas HTTPS) e SameSite=Strict ou Lax (ajuda a mitigar CSRF). Configurá-los no lado do servidor é mais confiável do que fazê-lo a partir do JavaScript.

localStorage e sessionStorage são acessíveis a qualquer script na página. Evite armazenar tokens de autenticação, segredos de sessão ou dados sensíveis do usuário lá—uma vulnerabilidade XSS expõe imediatamente tudo no armazenamento.

Mensagens Cross-Origin com postMessage

postMessage é útil, mas fácil de usar incorretamente. Sempre valide a origin das mensagens recebidas antes de agir sobre seus dados:

window.addEventListener('message', (event) => {
  // ✅ Sempre valide a origem antes de processar
  if (event.origin !== 'https://trusted-origin.com') return

  handleMessage(event.data)
})

Ao enviar mensagens, evite usar '*' como targetOrigin a menos que você realmente não tenha um destino fixo. No lado receptor, sempre valide event.origin para garantir que a mensagem veio de um site confiável. Mais detalhes sobre uso seguro são abordados na documentação do postMessage.

Segurança da Cadeia de Suprimentos JavaScript

A segurança da cadeia de suprimentos JavaScript é uma preocupação crescente. Um único pacote comprometido ou malicioso pode afetar milhares de aplicações. Passos práticos:

  • Execute npm audit ou use Snyk para detectar vulnerabilidades conhecidas nas dependências
  • Faça commit do seu arquivo de lock (package-lock.json ou yarn.lock) e trate mudanças inesperadas como um sinal de alerta
  • Use hashes de Subresource Integrity (SRI) para quaisquer scripts carregados de uma CDN
  • Audite novos pacotes antes de adicioná-los—verifique contagens de download, atividade de manutenção e se o nome do pacote pode ser um typosquat

Referência Rápida: Padrões Seguros vs. Inseguros

InseguroAlternativa Segura
innerHTML = userInputtextContent = userInput
eval(str)JSON.parse(str)
setTimeout(str, n)setTimeout(fn, n)
Token em localStorageCookie HttpOnly
message sem verificação de origemValide event.origin primeiro

Conclusão

A maioria das vulnerabilidades JavaScript segue o mesmo padrão: dados não confiáveis alcançam uma API poderosa sem validação. Crie o hábito de perguntar “de onde vem esse valor e o que essa API pode fazer com ele?” Essa pergunta, aplicada consistentemente, detecta a maioria dos problemas antes que sejam lançados.

Perguntas Frequentes

Nem sempre, mas é inseguro sempre que você passa dados que se originam de entrada do usuário ou qualquer fonte externa. Se você precisar renderizar HTML dinâmico, sanitize-o primeiro com uma biblioteca como DOMPurify. Para conteúdo de texto simples, use textContent, que nunca interpreta marcação ou executa scripts.

localStorage é acessível a qualquer JavaScript executado na página. Se um atacante explorar uma vulnerabilidade XSS, ele pode ler tudo no armazenamento, incluindo seus tokens. Cookies HttpOnly são uma escolha mais segura porque o JavaScript não pode acessá-los de forma alguma, o que limita o dano de ataques client-side.

XSS refletido envolve um payload malicioso enviado ao servidor e ecoado de volta na resposta. XSS baseado em DOM nunca chega ao servidor. Em vez disso, o JavaScript client-side lê de uma fonte controlada pelo atacante, como o fragmento da URL, e o escreve na página de forma insegura. Ambos são perigosos, mas XSS baseado em DOM é mais difícil de detectar no lado do servidor.

CSP informa ao navegador quais fontes de script têm permissão para executar. Uma política rigorosa usando nonces ou hashes bloqueia scripts inline e scripts externos não autorizados de serem executados. Mesmo que um atacante injete marcação maliciosa na página, o navegador se recusa a executá-la porque não corresponde à política.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay