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: <script>alert("xss")</script>
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/setIntervalcom 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: DENYou a diretivaframe-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,SecureeSameSite=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
- MDN Web Docs: Content Security Policy (CSP) — Documentação completa sobre a diretiva CSP, exemplos de cabeçalhos e configuração avançada.
- OWASP Cross-Site Scripting (XSS) Prevention Cheat Sheet — Guia oficial do OWASP com regras de prevenção contra XSS, incluindo contextos de escape.
- DOMPurify — GitHub — Repositório oficial da biblioteca de sanitização DOMPurify, com exemplos de uso e opções de configuração.
- Helmet.js — Documentação — Middleware de segurança para Express.js, com configuração de CSP, X-Frame-Options e outros headers.
- React Documentation: Preventing XSS — Seção oficial do React sobre segurança, incluindo os riscos de dangerouslySetInnerHTML e boas práticas.
- OWASP ZAP — Ferramenta de Testes de Segurança — Ferramenta open-source para varredura de vulnerabilidades em aplicações web, com integração CI/CD.
- eslint-plugin-react — Regras de Segurança — Lista de regras do ESLint para React, incluindo jsx-no-script-url e no-danger-with-children.