Segurança no frontend: XSS, CSP e sanitização

1. Introdução à Segurança no Frontend com React

O frontend moderno deixou de ser uma camada passiva de apresentação para se tornar um ambiente de execução ativo, repleto de lógica de negócios, manipulação de estado e comunicação com APIs. Com isso, o navegador se tornou um alvo crítico para ataques. Diferente das vulnerabilidades server-side, onde o atacante explora falhas no servidor, as ameaças client-side atuam diretamente no código executado pelo usuário, podendo roubar tokens, cookies, dados sensíveis e até sequestrar sessões inteiras.

No ecossistema JavaScript + React, os riscos são específicos. O JSX escapa automaticamente valores inseridos entre chaves {}, o que já protege contra a maioria dos ataques de injeção. No entanto, mecanismos como dangerouslySetInnerHTML, props dinâmicas que aceitam URLs ou eventos, e a renderização condicional de conteúdo não confiável abrem brechas perigosas. As principais ameaças incluem Cross-Site Scripting (XSS), injeção de scripts via atributos HTML, roubo de tokens armazenados em localStorage e vazamento de dados sensíveis por meio de APIs inseguras.

2. Cross-Site Scripting (XSS) — Tipos e Mecanismos

XSS é a vulnerabilidade mais comum no frontend. Ela ocorre quando um atacante consegue injetar código JavaScript malicioso em uma página que será executado no navegador de outras vítimas. Existem três tipos principais:

XSS Refletido: o payload malicioso viaja pela URL ou query string e é refletido imediatamente na resposta do servidor. Exemplo: uma página de busca que exibe o termo pesquisado sem sanitização.

XSS Armazenado (Persistente): o ataque é armazenado no servidor (banco de dados, comentários, formulários) e executado toda vez que um usuário acessa a página. É o mais perigoso, pois afeta todos os visitantes.

XSS Baseado em DOM: a vulnerabilidade está no código JavaScript do cliente, que manipula o DOM de forma insegura com APIs como innerHTML, document.write ou eval. O payload nunca chega ao servidor.

Em React, o exemplo clássico de risco é o uso de dangerouslySetInnerHTML. Se você recebe HTML de uma fonte externa e o insere diretamente, está abrindo uma porta para XSS:

function Comentario({ conteudo }) {
  // Perigoso! Se conteudo contiver <script>alert('XSS')</script>, será executado
  return <div dangerouslySetInnerHTML={{ __html: conteudo }} />;
}

Outro ponto crítico é a renderização condicional de atributos como href ou src com dados não validados:

function LinkPerfil({ url }) {
  // Se url for "javascript:alert('XSS')", o navegador executa o código
  return <a href={url}>Perfil</a>;
}

3. Sanitização de Dados no Frontend e Backend (Node.js)

A sanitização deve ocorrer em ambas as camadas: servidor e cliente. No Node.js, a biblioteca DOMPurify combinada com jsdom permite limpar HTML recebido antes de armazená-lo:

const { JSDOM } = require('jsdom');
const DOMPurify = require('dompurify')(new JSDOM('').window);

function sanitizarHTML(html) {
  return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong'] });
}

app.post('/comentario', (req, res) => {
  const htmlLimpo = sanitizarHTML(req.body.conteudo);
  // Armazena htmlLimpo no banco de dados
});

No React, ao precisar renderizar HTML sanitizado, integre o DOMPurify diretamente:

import DOMPurify from 'dompurify';

function ComentarioSeguro({ conteudo }) {
  const sanitizado = DOMPurify.sanitize(conteudo);
  return <div dangerouslySetInnerHTML={{ __html: sanitizado }} />;
}

Para sanitização de inputs simples (escapar HTML entities), use a biblioteca he:

const he = require('he');
const textoEscapado = he.encode('<script>alert("xss")</script>');
// Resultado: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;

Nunca confie em dados do usuário. Sanitize no servidor antes de persistir e revalide no cliente para evitar que dados maliciosos cheguem ao backend.

4. Content Security Policy (CSP) — Implementação e Configuração

CSP é um cabeçalho HTTP que instrui o navegador sobre quais fontes de conteúdo são confiáveis. Ele bloqueia a execução de scripts inline, eval(), e carregamento de recursos de domínios não autorizados — uma defesa eficaz mesmo se um XSS for introduzido.

Existem dois modos: Content-Security-Policy (bloqueia e reporta) e Content-Security-Policy-Report-Only (apenas reporta, sem bloquear — útil para testes).

Diretivas essenciais:
- default-src: fallback para todas as diretivas não especificadas
- script-src: controla quais scripts podem ser executados
- style-src: origens permitidas para CSS
- img-src: origens para imagens
- connect-src: origens para requisições AJAX/Fetch

No Express.js (Node.js), configure o CSP com o pacote helmet:

const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'nonce-abc123'"], // nonce para scripts inline
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", "https://trusted-cdn.com"],
    connectSrc: ["'self'", "https://api.meusite.com"],
    reportUri: '/csp-report'
  }
}));

Em aplicações React com bundlers (CRA, Vite, Next.js), use nonce ou hash para scripts inline gerados pelo bundle. No Next.js, por exemplo:

// next.config.js
const { createSecureHeaders } = require('next-secure-headers');

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: createSecureHeaders({
          contentSecurityPolicy: {
            directives: {
              scriptSrc: ["'self'", "'nonce-{nonce}'"],
              // ...
            }
          }
        })
      }
    ];
  }
};

5. Mitigação de Riscos com Ferramentas e Bibliotecas

DOMPurify: além da sanitização básica, oferece hooks para personalizar o processo e allowlists para tags e atributos permitidos. Pode ser usado tanto no Node.js quanto no navegador.

Helmet.js: middleware para Express que configura automaticamente headers de segurança como X-XSS-Protection, X-Content-Type-Options, X-Frame-Options e CSP. Reduz drasticamente a superfície de ataque.

React como framework "escapado por padrão": o JSX escapa automaticamente strings entre chaves, mas isso não protege contra dangerouslySetInnerHTML, atributos dinâmicos (href, src) ou dados injetados via props de componentes de terceiros.

Ferramentas de auditoria: eslint-plugin-react com regras como jsx-no-script-url e no-danger-with-children ajuda a detectar padrões inseguros. O Lighthouse (Chrome DevTools) também verifica headers de segurança e práticas de CSP.

6. Secure Coding Practices no Ecossistema React + Node.js

  • Evite eval(), new Function(), setTimeout/setInterval com strings: essas APIs executam código arbitrário e são vetores clássicos de XSS. Prefira funções anônimas ou arrow functions.

  • Validação de dados de APIs externas: ao consumir APIs de terceiros, valide e sanitize os dados antes de renderizá-los. Nunca assuma que uma API confiável retornará dados seguros.

  • Proteção contra Clickjacking: use o header X-Frame-Options: DENY ou a diretiva frame-ancestors 'none' no CSP para impedir que sua aplicação seja carregada em iframes de terceiros.

  • Gerenciamento de tokens e cookies: armazene tokens de autenticação em cookies HttpOnly, Secure e SameSite=Strict. Evite localStorage para tokens sensíveis, pois ele é acessível via JavaScript e vulnerável a XSS. Para maior segurança, use memória (variáveis em módulo) com refresh tokens em cookie.

7. Testes de Segurança e Monitoramento Contínuo

Testes automatizados podem simular ataques XSS. Com Jest e React Testing Library:

import { render, screen } from '@testing-library/react';
import ComentarioSeguro from './ComentarioSeguro';

test('não executa script injetado', () => {
  const payload = '<img src=x onerror="alert(\'XSS\')">';
  render(<ComentarioSeguro conteudo={payload} />);
  // Verifica se o conteúdo foi sanitizado (tag img removida ou atributo onerror removido)
  expect(screen.queryByRole('img')).toBeNull();
});

Ferramentas como OWASP ZAP e Burp Suite podem ser integradas em pipelines CI/CD para varredura automática. Para monitoramento em produção, Sentry e LogRocket capturam erros de CSP e tentativas de XSS, permitindo resposta rápida a incidentes.

Checklist final para deploy:
- [ ] CSP configurado e testado (modo report-only primeiro)
- [ ] Todos os inputs sanitizados no servidor e no cliente
- [ ] Headers HTTP de segurança ativos (Helmet.js)
- [ ] Scripts inline com nonce ou hash
- [ ] Cookies configurados com HttpOnly, Secure, SameSite
- [ ] ESLint com regras de segurança ativadas

8. Conclusão e Próximos Passos

A segurança no frontend não é opcional — é uma responsabilidade contínua. Os três pilares apresentados — sanitização rigorosa, CSP bem configurado e boas práticas de código — formam uma defesa em camadas que protege sua aplicação React + Node.js contra XSS e outras ameaças.

Para aprofundamento, consulte o OWASP Cheat Sheet, a documentação de segurança do React e o guia de CSP da MDN. Nos próximos artigos desta série, exploraremos deploy seguro, monitoramento de aplicações e gerenciamento de estado com foco em segurança.

Referências