{"id":139,"date":"2026-06-18T17:01:33","date_gmt":"2026-06-18T17:01:33","guid":{"rendered":"https:\/\/shattered.io\/pt\/2026\/06\/18\/helmet-js-nodejs-cabecalhos-seguranca\/"},"modified":"2026-06-18T17:02:50","modified_gmt":"2026-06-18T17:02:50","slug":"helmet-js-nodejs-cabecalhos-seguranca","status":"publish","type":"post","link":"https:\/\/shattered.io\/pt\/2026\/06\/18\/helmet-js-nodejs-cabecalhos-seguranca\/","title":{"rendered":"Helmet.js em Node.js: Cabe\u00e7alhos de Seguran\u00e7a em 12 Passos [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">O Express.js n\u00e3o inclui um \u00fanico cabe\u00e7alho de seguran\u00e7a por defeito. Quando uma app Node.js responde a um pedido HTTP sem configura\u00e7\u00e3o adicional, o browser recebe <strong>zero prote\u00e7\u00f5es<\/strong> contra XSS, clickjacking ou MIME sniffing. O Helmet.js resolve isso com uma linha de c\u00f3digo, instalando 15 middlewares de seguran\u00e7a que transformam completamente o perfil de risco da aplica\u00e7\u00e3o. Com mais de <strong>2 milh\u00f5es de transfer\u00eancias semanais<\/strong> no npm e 9.400 estrelas no GitHub, \u00e9 o padr\u00e3o de facto para seguran\u00e7a HTTP em Node.js.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Este guia mostra os 12 passos para configurar o Helmet.js numa app Express, desde a instala\u00e7\u00e3o at\u00e9 \u00e0 auditoria completa dos cabe\u00e7alhos em produ\u00e7\u00e3o. Em menos de 30 minutos, a aplica\u00e7\u00e3o passa de zero prote\u00e7\u00f5es a um perfil compat\u00edvel com a <strong>OWASP A05:2021 Security Misconfiguration<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"o-que-e-o-helmet-js-e-por-que-toda-app-node-js-precisa-dele\">O que \u00e9 o Helmet.js e Por Que Toda App Node.js Precisa Dele<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O Helmet.js \u00e9 uma biblioteca open source que funciona como middleware Express. Cada chamada a <code>app.use(helmet())<\/code> regista <strong>15 sub-middlewares independentes<\/strong>, cada um respons\u00e1vel por configurar um cabe\u00e7alho HTTP de seguran\u00e7a diferente. N\u00e3o existe magia: o Helmet simplesmente define cabe\u00e7alhos HTTP nas respostas antes de estas chegarem ao browser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O problema que justifica a sua exist\u00eancia \u00e9 simples: o Express.js foi desenhado para ser minimalista. A framework n\u00e3o imp\u00f5e opini\u00f5es sobre seguran\u00e7a, o que significa que uma app Express instalada de base n\u00e3o envia <code>Content-Security-Policy<\/code>, n\u00e3o ativa HSTS, e n\u00e3o instrui o browser a evitar MIME sniffing. Esta configura\u00e7\u00e3o padr\u00e3o est\u00e1 na origem de ataques reais. Segundo a <strong>OWASP Top 10 2021<\/strong>, a categoria A05 (Security Misconfiguration) inclui explicitamente a aus\u00eancia de cabe\u00e7alhos de seguran\u00e7a HTTP como vetor de ataque.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A popularidade do Helmet reflete a escala do problema. O npm regista mais de <strong>2 milh\u00f5es de transfer\u00eancias por semana<\/strong>, o que indica que a maioria dos projetos Express em produ\u00e7\u00e3o j\u00e1 depende desta biblioteca. A LogRocket descreve o Helmet como o wrapper de 15 sub-middlewares que nenhuma app Express devia dispensar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O impacto de n\u00e3o usar cabe\u00e7alhos de seguran\u00e7a \u00e9 concreto. Sem <code>Content-Security-Policy<\/code>, um atacante que consiga injetar um script malicioso na p\u00e1gina pode roubar sess\u00f5es de utilizadores, exfiltrar dados de formul\u00e1rios ou redirecionar para sites de phishing. Sem <code>X-Content-Type-Options: nosniff<\/code>, o browser pode interpretar um ficheiro de texto como JavaScript execut\u00e1vel. Sem <code>X-Frame-Options<\/code> ou <code>frame-ancestors<\/code>, a app fica vulner\u00e1vel a ataques de clickjacking em que a p\u00e1gina \u00e9 carregada dentro de um iframe invis\u00edvel.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cabecalhos-que-o-helmet-js-configura-por-defeito\">Cabe\u00e7alhos que o Helmet.js Configura por Defeito<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de escrever uma linha de c\u00f3digo, \u00e9 importante entender exatamente o que o Helmet faz. A tabela seguinte lista os cabe\u00e7alhos ativados por defeito com uma chamada simples a <code>helmet()<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Cabe\u00e7alho HTTP<\/th><th>Valor por defeito<\/th><th>Protege contra<\/th><\/tr><\/thead><tbody><tr><td>Content-Security-Policy<\/td><td>default-src &#8216;self&#8217;; upgrade-insecure-requests<\/td><td>XSS, inje\u00e7\u00e3o de recursos externos<\/td><\/tr><tr><td>Strict-Transport-Security<\/td><td>max-age=31536000; includeSubDomains<\/td><td>Downgrade de HTTPS para HTTP<\/td><\/tr><tr><td>X-Content-Type-Options<\/td><td>nosniff<\/td><td>MIME sniffing, execu\u00e7\u00e3o de conte\u00fado indevido<\/td><\/tr><tr><td>X-DNS-Prefetch-Control<\/td><td>off<\/td><td>Fugas DNS n\u00e3o autorizadas<\/td><\/tr><tr><td>Origin-Agent-Cluster<\/td><td>?1<\/td><td>Side-channel attacks entre origens<\/td><\/tr><tr><td>Referrer-Policy<\/td><td>no-referrer<\/td><td>Fuga de URL completo em cabe\u00e7alhos Referer<\/td><\/tr><tr><td>Cross-Origin-Opener-Policy<\/td><td>same-origin<\/td><td>Acesso cross-origin via window.opener<\/td><\/tr><tr><td>Cross-Origin-Resource-Policy<\/td><td>same-origin<\/td><td>Carregamento de recursos por origens externas<\/td><\/tr><tr><td>X-XSS-Protection<\/td><td>0 (desativado)<\/td><td>N\/A &#8211; desativado por ser contraproducente em browsers modernos<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">O <code>X-XSS-Protection<\/code> merece aten\u00e7\u00e3o especial: o Helmet <strong>desativa-o deliberadamente<\/strong> porque vers\u00f5es antigas deste mecanismo nos browsers podiam ser exploradas para introduzir XSS. O CSP moderno substitui-o com muito mais efic\u00e1cia e granularidade. A diretiva <code>frame-ancestors<\/code> dentro do CSP substitui tamb\u00e9m o antigo cabe\u00e7alho <code>X-Frame-Options<\/code>, com a vantagem de suportar m\u00faltiplas origens autorizadas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"pre-requisitos-e-versoes-recomendadas\">Pr\u00e9-requisitos e Vers\u00f5es Recomendadas<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Para seguir este tutorial, \u00e9 necess\u00e1rio ter instalado o seguinte:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ferramenta<\/th><th>Vers\u00e3o m\u00ednima<\/th><th>Notas<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>22.x LTS<\/td><td>Linha ativa com suporte at\u00e9 abril de 2027. O projeto lan\u00e7ou atualiza\u00e7\u00e3o HIGH em junho de 2026 para as linhas 22.x, 24.x e 26.x.<\/td><\/tr><tr><td>npm<\/td><td>10.x<\/td><td>Inclu\u00eddo com Node.js 22<\/td><\/tr><tr><td>Express<\/td><td>5.x<\/td><td>Est\u00e1vel desde 2025, com tratamento nativo de promessas ass\u00edncronas<\/td><\/tr><tr><td>Helmet.js<\/td><td>Vers\u00e3o mais recente via npm<\/td><td>Compat\u00edvel com Express 4.x e 5.x<\/td><\/tr><tr><td>curl<\/td><td>Qualquer vers\u00e3o<\/td><td>Para verifica\u00e7\u00e3o de cabe\u00e7alhos via linha de comandos<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Conhecimento necess\u00e1rio: JavaScript ass\u00edncrono com async\/await, conceito b\u00e1sico de middleware Express, e familiaridade com HTTP e cabe\u00e7alhos de resposta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-1-criar-o-projeto-node-js\">Passo 1: Criar o Projeto Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Comece por criar uma diretoria de projeto limpa e inicializar o package.json com as configura\u00e7\u00f5es adequadas para um projeto moderno em Node.js:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir api-segura-helmet\ncd api-segura-helmet\nnpm init -y\n\n# Verificar que o Node.js 22.x est\u00e1 ativo\nnode --version\n# Sa\u00edda esperada: v22.x.x\n\n# Converter para ES Modules (recomendado em 2026)\nnpm pkg set type=\"module\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O ficheiro <code>package.json<\/code> resultante deve ter <code>\"type\": \"module\"<\/code> para usar a sintaxe <code>import<\/code> nos exemplos seguintes. Se preferir CommonJS, substitua <code>import helmet from 'helmet'<\/code> por <code>const helmet = require('helmet')<\/code> em todos os exemplos. Ambas as sintaxes s\u00e3o suportadas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-2-instalar-o-express-e-o-helmet-js\">Passo 2: Instalar o Express e o Helmet.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Instalar as depend\u00eancias principais. O Helmet n\u00e3o tem depend\u00eancias externas, o que simplifica a cadeia de fornecimento de software e reduz a superf\u00edcie de ataque a zero depend\u00eancias transitivas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install express helmet\n\n# Verificar as vers\u00f5es instaladas\nnpm list express helmet\n\n# Instalar nodemon para desenvolvimento\nnpm install --save-dev nodemon\n\n# Adicionar scripts ao package.json\nnpm pkg set scripts.dev=\"nodemon src\/index.js\"\nnpm pkg set scripts.start=\"node src\/index.js\"\n\n# Criar estrutura de diretorias\nmkdir -p src\ntouch src\/index.js src\/helmet-config.js src\/routes.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirme que o Helmet foi instalado corretamente verificando a sua presen\u00e7a no <code>node_modules<\/code> e no <code>package.json<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Verificar que o pacote foi instalado\nls node_modules\/helmet\n\n# Verificar a entrada no package.json\ncat package.json | grep helmet<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-3-integracao-basica-com-express\">Passo 3: Integra\u00e7\u00e3o B\u00e1sica com Express<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A integra\u00e7\u00e3o mais simples consiste em registar o Helmet como primeiro middleware da aplica\u00e7\u00e3o. Desta forma, todos os pedidos recebem os cabe\u00e7alhos de seguran\u00e7a antes de qualquer outra l\u00f3gica ser executada. A ordem dos middlewares em Express \u00e9 sequencial e determinista, por isso a posi\u00e7\u00e3o do Helmet importa:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/index.js\nimport express from 'express';\nimport helmet from 'helmet';\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\n\n\/\/ Helmet deve ser o PRIMEIRO middleware registado\napp.use(helmet());\n\napp.use(express.json());\n\napp.get('\/', (req, res) => {\n  res.json({ status: 'ok', mensagem: 'API segura com Helmet.js' });\n});\n\napp.listen(PORT, () => {\n  console.log(`Servidor a correr na porta ${PORT}`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Inicie o servidor e verifique os cabe\u00e7alhos com <code>curl<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node src\/index.js &amp;\n\n# Verificar todos os cabe\u00e7alhos de resposta\ncurl -I http:\/\/localhost:3000\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A resposta deve incluir os seguintes cabe\u00e7alhos, confirmando que o Helmet est\u00e1 ativo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/1.1 200 OK\ncontent-security-policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests\ncross-origin-opener-policy: same-origin\ncross-origin-resource-policy: same-origin\norigin-agent-cluster: ?1\nreferrer-policy: no-referrer\nstrict-transport-security: max-age=31536000; includeSubDomains\nx-content-type-options: nosniff\nx-dns-prefetch-control: off<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Compare com o que Express envia <strong>sem<\/strong> Helmet: apenas <code>X-Powered-By: Express<\/code>, que al\u00e9m de n\u00e3o proteger nada, revela a tecnologia usada pela aplica\u00e7\u00e3o a potenciais atacantes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-4-configurar-o-content-security-policy-csp\">Passo 4: Configurar o Content-Security-Policy (CSP)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O Content-Security-Policy \u00e9 o cabe\u00e7alho mais complexo e mais importante que o Helmet configura. Diz ao browser quais origens s\u00e3o autorizadas a carregar scripts, estilos, imagens e outros recursos. Uma CSP bem configurada elimina praticamente todos os vetores de ataque XSS baseados em inje\u00e7\u00e3o de scripts externos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O CSP padr\u00e3o do Helmet usa <code>default-src 'self'<\/code>, o que bloqueia qualquer recurso externo. Para uma API REST pura que responde apenas em JSON, esta configura\u00e7\u00e3o \u00e9 mais do que suficiente. Para aplica\u00e7\u00f5es web com recursos externos (Google Fonts, CDNs, analytics), \u00e9 necess\u00e1rio personalizar as diretivas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/helmet-config.js\nexport const helmetConfig = {\n  contentSecurityPolicy: {\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\n        \"'self'\",\n        \/\/ Adicionar apenas CDNs que a app usa explicitamente\n        \"https:\/\/cdn.jsdelivr.net\"\n      ],\n      styleSrc: [\n        \"'self'\",\n        \"'unsafe-inline'\", \/\/ Necess\u00e1rio para muitos frameworks CSS\n        \"https:\/\/fonts.googleapis.com\"\n      ],\n      fontSrc: [\n        \"'self'\",\n        \"https:\/\/fonts.gstatic.com\"\n      ],\n      imgSrc: [\n        \"'self'\",\n        \"data:\",\n        \"https:\" \/\/ Permite imagens HTTPS de qualquer origem\n      ],\n      connectSrc: [\n        \"'self'\",\n        \"https:\/\/api.exemplo.pt\" \/\/ API externa autorizada\n      ],\n      objectSrc: [\"'none'\"],\n      frameAncestors: [\"'none'\"], \/\/ Equivalente a X-Frame-Options: DENY\n      baseUri: [\"'self'\"],\n      formAction: [\"'self'\"],\n      upgradeInsecureRequests: [],\n    },\n  },\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aplique a configura\u00e7\u00e3o personalizada no ficheiro principal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/index.js (atualizado)\nimport express from 'express';\nimport helmet from 'helmet';\nimport { helmetConfig } from '.\/helmet-config.js';\n\nconst app = express();\napp.use(helmet(helmetConfig));\n\/\/ resto da app...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Uma armadilha comum com CSP \u00e9 o bloqueio de recursos leg\u00edtimos em produ\u00e7\u00e3o. Para identificar viola\u00e7\u00f5es sem bloquear, use o modo de reporte antes de enfor\u00e7ar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Modo Report-Only: n\u00e3o bloqueia, apenas reporta viola\u00e7\u00f5es CSP\napp.use(\n  helmet({\n    contentSecurityPolicy: {\n      useDefaults: true,\n      reportOnly: true, \/\/ Muda header para Content-Security-Policy-Report-Only\n      directives: {\n        defaultSrc: [\"'self'\"],\n        reportTo: \"\/csp-report\",\n      },\n    },\n  })\n);\n\n\/\/ Endpoint para receber relat\u00f3rios CSP\napp.post('\/csp-report', express.json({ type: 'application\/csp-report' }), (req, res) => {\n  console.log('Viola\u00e7\u00e3o CSP:', JSON.stringify(req.body, null, 2));\n  res.status(204).end();\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-5-configurar-o-hsts-strict-transport-security\">Passo 5: Configurar o HSTS (Strict-Transport-Security)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O HSTS instrui o browser a usar exclusivamente HTTPS durante um per\u00edodo definido, mesmo que o utilizador tente aceder via HTTP. O Helmet define por defeito <code>max-age=31536000; includeSubDomains<\/code>, correspondendo a 365 dias. Qualquer tentativa de acesso HTTP durante este per\u00edodo \u00e9 automaticamente redirecionada para HTTPS pelo browser, sem contactar o servidor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para produ\u00e7\u00e3o com dom\u00ednio registado na HSTS preload list dos principais browsers (Chrome, Firefox, Edge), adicione a diretiva <code>preload<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    hsts: {\n      maxAge: 31536000,        \/\/ 1 ano em segundos\n      includeSubDomains: true, \/\/ Aplica a todos os subdom\u00ednios\n      preload: true,           \/\/ Para inclus\u00e3o na HSTS preload list dos browsers\n    },\n  })\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O HSTS s\u00f3 deve ser ativado quando o dom\u00ednio j\u00e1 serve HTTPS em todas as rotas. Uma app que n\u00e3o tenha TLS configurado n\u00e3o deve ativar HSTS, pois os browsers passar\u00e3o a rejeitar liga\u00e7\u00f5es HTTP durante o per\u00edodo de <code>max-age<\/code>. Para desativar o HSTS em desenvolvimento local:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    hsts: process.env.NODE_ENV === 'production'\n      ? { maxAge: 31536000, includeSubDomains: true, preload: true }\n      : false, \/\/ Desativado em desenvolvimento sem HTTPS\n  })\n);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-6-cabecalhos-cross-origin-e-referrer-policy\">Passo 6: Cabe\u00e7alhos Cross-Origin e Referrer-Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Os cabe\u00e7alhos cross-origin controlam como a aplica\u00e7\u00e3o interage com recursos de outras origens e como outras origens podem interagir com os recursos da app. O Helmet configura tr\u00eas deles por defeito, cada um com um objetivo espec\u00edfico:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Cabe\u00e7alho<\/th><th>Valor padr\u00e3o Helmet<\/th><th>Efeito pr\u00e1tico<\/th><\/tr><\/thead><tbody><tr><td>Cross-Origin-Opener-Policy<\/td><td>same-origin<\/td><td>Isola o contexto de navega\u00e7\u00e3o, impede acesso via window.opener de outras origens<\/td><\/tr><tr><td>Cross-Origin-Resource-Policy<\/td><td>same-origin<\/td><td>Impede que p\u00e1ginas de outras origens carreguem recursos desta app (imagens, scripts)<\/td><\/tr><tr><td>Cross-Origin-Embedder-Policy<\/td><td>Desativado<\/td><td>N\u00e3o ativo por defeito para n\u00e3o quebrar integra\u00e7\u00f5es de terceiros<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">O <code>Referrer-Policy: no-referrer<\/code> por defeito impede que o URL completo da p\u00e1gina atual seja enviado como referrer em pedidos para outras origens. Para apps que dependem de analytics via referrer, use uma pol\u00edtica menos restritiva mas ainda segura:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    referrerPolicy: {\n      \/\/ Envia referrer apenas para a mesma origem\n      \/\/ Para HTTPS externos, envia apenas a origem sem path\n      policy: 'strict-origin-when-cross-origin',\n    },\n    crossOriginResourcePolicy: {\n      \/\/ Permite que recursos sejam carregados por qualquer origem (CDN, etc.)\n      policy: 'cross-origin',\n    },\n    crossOriginOpenerPolicy: {\n      \/\/ Mais permissivo para pop-ups OAuth e autentica\u00e7\u00e3o de terceiros\n      policy: 'same-origin-allow-popups',\n    },\n  })\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para apps que usam <code>SharedArrayBuffer<\/code> ou precisam de isolamento de origem completo (necess\u00e1rio para mitiga\u00e7\u00f5es Spectre), ative o COEP:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    crossOriginEmbedderPolicy: {\n      policy: 'require-corp', \/\/ Requer CORP em todos os recursos carregados\n    },\n  })\n);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-7-remover-o-cabecalho-x-powered-by\">Passo 7: Remover o Cabe\u00e7alho X-Powered-By<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O Express envia por defeito <code>X-Powered-By: Express<\/code> em todas as respostas. Este cabe\u00e7alho n\u00e3o tem qualquer valor funcional e revela informa\u00e7\u00e3o sobre a stack tecnol\u00f3gica da aplica\u00e7\u00e3o. Um atacante que identifica que o servidor usa Express pode focar os ataques em vulnerabilidades espec\u00edficas de Express ou das vers\u00f5es Node.js associadas. O Helmet remove-o automaticamente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para confirmar que foi removido ap\u00f3s instalar o Helmet:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Sem Helmet - exp\u00f5e a tecnologia:\n# x-powered-by: Express\n\n# Com Helmet - cabe\u00e7alho ausente das respostas\ncurl -I http:\/\/localhost:3000\/ | grep -i powered\n# Sa\u00edda esperada: (nenhuma linha - cabe\u00e7alho removido com sucesso)\n\n# Verificar todos os cabe\u00e7alhos de seguran\u00e7a de uma vez\ncurl -I http:\/\/localhost:3000\/ | grep -iE \"(content-security|strict-transport|x-content|referrer|cross-origin|origin-agent)\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se o projeto usa o m\u00f3dulo HTTP nativo do Node.js sem Express, defina os cabe\u00e7alhos manualmente usando <code>res.setHeader()<\/code>. Para projetos Express que ainda n\u00e3o usam Helmet, a remo\u00e7\u00e3o do <code>X-Powered-By<\/code> pode ser feita isoladamente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Apenas remo\u00e7\u00e3o do X-Powered-By, sem o Helmet completo\napp.disable('x-powered-by');<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-8-configuracao-para-producao-vs-desenvolvimento\">Passo 8: Configura\u00e7\u00e3o para Produ\u00e7\u00e3o vs Desenvolvimento<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A configura\u00e7\u00e3o de seguran\u00e7a em desenvolvimento deve ser funcional mas mais permissiva do que em produ\u00e7\u00e3o. O padr\u00e3o recomendado \u00e9 criar um ficheiro de configura\u00e7\u00e3o \u00fanico que l\u00ea a vari\u00e1vel de ambiente <code>NODE_ENV<\/code> e adapta as pol\u00edticas em conformidade:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/helmet-config.js - vers\u00e3o completa produ\u00e7\u00e3o vs desenvolvimento\nconst isProd = process.env.NODE_ENV === 'production';\n\nexport const helmetConfig = {\n  contentSecurityPolicy: {\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: isProd\n        ? [\"'self'\"]\n        : [\"'self'\", \"'unsafe-eval'\", \"'unsafe-inline'\"], \/\/ Dev: permite eval para hot-reload\n      styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n      imgSrc: [\"'self'\", \"data:\", \"https:\"],\n      connectSrc: isProd\n        ? [\"'self'\", process.env.API_URL].filter(Boolean)\n        : [\"'self'\", \"ws:\/\/localhost:*\", \"http:\/\/localhost:*\"], \/\/ Dev: WebSocket para HMR\n      objectSrc: [\"'none'\"],\n      frameAncestors: [\"'none'\"],\n      baseUri: [\"'self'\"],\n      formAction: [\"'self'\"],\n      ...(isProd && { upgradeInsecureRequests: [] }),\n    },\n  },\n  hsts: isProd\n    ? { maxAge: 31536000, includeSubDomains: true, preload: true }\n    : false,\n  dnsPrefetchControl: { allow: false },\n  referrerPolicy: {\n    policy: isProd ? 'no-referrer' : 'no-referrer-when-downgrade',\n  },\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No ficheiro <code>.env<\/code> de produ\u00e7\u00e3o, defina sempre as vari\u00e1veis necess\u00e1rias antes de iniciar a aplica\u00e7\u00e3o:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .env.production\nNODE_ENV=production\nAPI_URL=https:\/\/api.exemplo.pt\nPORT=3000\n\n# .env.development\nNODE_ENV=development\nPORT=3000<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-9-helmet-com-cors-para-apis-rest\">Passo 9: Helmet com CORS para APIs REST<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O Helmet e o CORS (Cross-Origin Resource Sharing) resolvem problemas diferentes mas complementares. O Helmet protege a app contra ataques baseados em cabe\u00e7alhos de resposta. O CORS controla que origens externas podem fazer pedidos \u00e0 app. Os dois devem ser usados em conjunto, e a ordem de registo dos middlewares \u00e9 determinante:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install cors<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/index.js - Helmet + CORS para APIs REST\nimport express from 'express';\nimport helmet from 'helmet';\nimport cors from 'cors';\nimport { helmetConfig } from '.\/helmet-config.js';\n\nconst app = express();\n\n\/\/ 1. Helmet primeiro - define cabe\u00e7alhos de seguran\u00e7a em todas as respostas\napp.use(helmet(helmetConfig));\n\n\/\/ 2. CORS a seguir - define origens permitidas para pedidos cross-origin\nconst corsOptions = {\n  origin: process.env.NODE_ENV === 'production'\n    ? (process.env.ALLOWED_ORIGINS || '').split(',').filter(Boolean)\n    : ['http:\/\/localhost:3000', 'http:\/\/localhost:5173'], \/\/ Vite dev server\n  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n  allowedHeaders: ['Content-Type', 'Authorization'],\n  credentials: true, \/\/ Permite cookies cross-origin\n  maxAge: 600, \/\/ Cache do preflight OPTIONS por 10 minutos\n};\n\napp.options('*', cors(corsOptions)); \/\/ Responder a pedidos preflight\napp.use(cors(corsOptions));\n\n\/\/ 3. Parser do corpo com limite de tamanho\napp.use(express.json({ limit: '10kb' }));\napp.use(express.urlencoded({ extended: false, limit: '10kb' }));\n\napp.get('\/api\/v1\/status', (req, res) => {\n  res.json({ status: 'ok', timestamp: new Date().toISOString() });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => console.log(`API a correr na porta ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A ordem dos middlewares \u00e9 fundamental: o Helmet <strong>deve vir antes<\/strong> do CORS. Se invertir a ordem, os cabe\u00e7alhos CORS podem sobrescrever configura\u00e7\u00f5es do Helmet, criando inconsist\u00eancias na pol\u00edtica de seguran\u00e7a.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-10-helmet-com-fastify-e-nestjs\">Passo 10: Helmet com Fastify e NestJS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O Helmet n\u00e3o \u00e9 exclusivo do Express. Para projetos que usam Fastify ou NestJS, existem integra\u00e7\u00f5es espec\u00edficas que respeitam a arquitetura de plugins de cada framework.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fastify-com-fastify-helmet\">Fastify com @fastify\/helmet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No Fastify, o Helmet funciona como plugin e n\u00e3o como middleware Express. O pacote correto \u00e9 <code>@fastify\/helmet<\/code>, que deve ser registado com <code>await app.register()<\/code> e n\u00e3o com <code>app.use()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install @fastify\/helmet<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app-fastify.js\nimport Fastify from 'fastify';\nimport helmet from '@fastify\/helmet';\n\nconst fastify = Fastify({ logger: true });\n\n\/\/ Registar como plugin Fastify - N\u00c3O usar app.use() no Fastify\nawait fastify.register(helmet, {\n  contentSecurityPolicy: {\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\"'self'\"],\n      objectSrc: [\"'none'\"],\n    },\n  },\n  hsts: {\n    maxAge: 31536000,\n    includeSubDomains: true,\n  },\n});\n\nfastify.get('\/', async (request, reply) => {\n  return { status: 'ok' };\n});\n\nawait fastify.listen({ port: 3000 });\nconsole.log('Servidor Fastify a correr na porta 3000');<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"nestjs-com-helmet\">NestJS com Helmet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No NestJS com o adaptador Express por defeito, o Helmet \u00e9 aplicado como middleware global no ficheiro <code>main.ts<\/code>, antes de qualquer outro middleware ou m\u00f3dulo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ main.ts (NestJS com Express)\nimport { NestFactory } from '@nestjs\/core';\nimport { AppModule } from '.\/app.module';\nimport helmet from 'helmet';\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n\n  \/\/ Aplicar Helmet antes de qualquer outra configura\u00e7\u00e3o\n  app.use(helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc: [\"'self'\"],\n        scriptSrc: [\"'self'\"],\n        styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n        imgSrc: [\"'self'\", \"data:\", \"https:\"],\n        objectSrc: [\"'none'\"],\n        frameAncestors: [\"'none'\"],\n      },\n    },\n    hsts: {\n      maxAge: 31536000,\n      includeSubDomains: true,\n    },\n    referrerPolicy: { policy: 'no-referrer' },\n  }));\n\n  await app.listen(3000);\n  console.log('NestJS API com Helmet a correr na porta 3000');\n}\nbootstrap();<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-11-testar-os-cabecalhos-de-seguranca\">Passo 11: Testar os Cabe\u00e7alhos de Seguran\u00e7a<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ap\u00f3s configurar o Helmet, \u00e9 essencial verificar que os cabe\u00e7alhos est\u00e3o a ser enviados corretamente antes de publicar em produ\u00e7\u00e3o. Existem v\u00e1rias abordagens, desde testes manuais com <code>curl<\/code> at\u00e9 suites de testes autom\u00e1ticos integrados no pipeline de CI\/CD:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Listar todos os cabe\u00e7alhos de resposta ordenados\ncurl -I -s http:\/\/localhost:3000\/ | sort\n\n# Verificar CSP especificamente\ncurl -I -s http:\/\/localhost:3000\/ | grep -i content-security-policy\n\n# Verificar HSTS\ncurl -I -s http:\/\/localhost:3000\/ | grep -i strict-transport\n\n# Verificar que X-Powered-By foi removido (nenhuma sa\u00edda = correto)\ncurl -I -s http:\/\/localhost:3000\/ | grep -i powered\n\n# Verificar todos os cabe\u00e7alhos cross-origin\ncurl -I -s http:\/\/localhost:3000\/ | grep -i \"cross-origin\\|origin-agent\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para testes de integra\u00e7\u00e3o autom\u00e1ticos com Jest e Supertest:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ tests\/security-headers.test.js\nimport request from 'supertest';\nimport app from '..\/src\/index.js';\n\ndescribe('Cabe\u00e7alhos de Seguran\u00e7a HTTP', () => {\n  test('deve incluir Content-Security-Policy com default-src self', async () => {\n    const res = await request(app).get('\/');\n    expect(res.headers['content-security-policy']).toBeDefined();\n    expect(res.headers['content-security-policy']).toContain(\"default-src 'self'\");\n  });\n\n  test('deve incluir HSTS em produ\u00e7\u00e3o', async () => {\n    const res = await request(app).get('\/');\n    if (process.env.NODE_ENV === 'production') {\n      expect(res.headers['strict-transport-security']).toContain('max-age=31536000');\n      expect(res.headers['strict-transport-security']).toContain('includeSubDomains');\n    }\n  });\n\n  test('deve ter X-Content-Type-Options: nosniff', async () => {\n    const res = await request(app).get('\/');\n    expect(res.headers['x-content-type-options']).toBe('nosniff');\n  });\n\n  test('n\u00e3o deve expor X-Powered-By', async () => {\n    const res = await request(app).get('\/');\n    expect(res.headers['x-powered-by']).toBeUndefined();\n  });\n\n  test('deve ter Referrer-Policy configurado', async () => {\n    const res = await request(app).get('\/');\n    expect(res.headers['referrer-policy']).toBeDefined();\n  });\n\n  test('deve ter Cross-Origin-Opener-Policy', async () => {\n    const res = await request(app).get('\/');\n    expect(res.headers['cross-origin-opener-policy']).toBe('same-origin');\n  });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para apps em produ\u00e7\u00e3o acess\u00edveis via HTTPS, use ferramentas externas de auditoria: o <a href=\"https:\/\/owasp.org\/www-project-secure-headers\/\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Secure Headers Project<\/a> disponibiliza uma lista de refer\u00eancia de cabe\u00e7alhos recomendados, e a documenta\u00e7\u00e3o MDN detalha o comportamento espec\u00edfico de cada cabe\u00e7alho como o <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Security-Policy\" target=\"_blank\" rel=\"noopener noreferrer\">Content-Security-Policy<\/a> e o <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Strict-Transport-Security\" target=\"_blank\" rel=\"noopener noreferrer\">Strict-Transport-Security<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"passo-12-projeto-completo-com-api-express-segura\">Passo 12: Projeto Completo com API Express Segura<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O c\u00f3digo seguinte representa uma API Express completa com todas as boas pr\u00e1ticas de seguran\u00e7a integradas: Helmet configurado para produ\u00e7\u00e3o e desenvolvimento, CORS restritivo, limite de tamanho de pedidos, valida\u00e7\u00e3o de input, e tratamento de erros sem exposi\u00e7\u00e3o de informa\u00e7\u00e3o sens\u00edvel:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/index.js - Projeto completo e funcional\nimport express from 'express';\nimport helmet from 'helmet';\nimport cors from 'cors';\n\nconst app = express();\nconst isProd = process.env.NODE_ENV === 'production';\n\n\/\/ === SEGURAN\u00c7A: Helmet com configura\u00e7\u00e3o completa ===\napp.use(helmet({\n  contentSecurityPolicy: {\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: isProd ? [\"'self'\"] : [\"'self'\", \"'unsafe-inline'\"],\n      styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n      imgSrc: [\"'self'\", \"data:\", \"https:\"],\n      connectSrc: [\"'self'\"],\n      fontSrc: [\"'self'\"],\n      objectSrc: [\"'none'\"],\n      mediaSrc: [\"'none'\"],\n      frameSrc: [\"'none'\"],\n      frameAncestors: [\"'none'\"],\n      baseUri: [\"'self'\"],\n      formAction: [\"'self'\"],\n      ...(isProd && { upgradeInsecureRequests: [] }),\n    },\n  },\n  hsts: isProd\n    ? { maxAge: 31536000, includeSubDomains: true, preload: true }\n    : false,\n  dnsPrefetchControl: { allow: false },\n  referrerPolicy: { policy: 'no-referrer' },\n  crossOriginOpenerPolicy: { policy: 'same-origin' },\n  crossOriginResourcePolicy: { policy: 'same-origin' },\n  originAgentCluster: true,\n}));\n\n\/\/ === CORS: Origens autorizadas explicitamente ===\nconst allowedOrigins = isProd\n  ? (process.env.ALLOWED_ORIGINS || '').split(',').filter(Boolean)\n  : ['http:\/\/localhost:3000', 'http:\/\/localhost:5173'];\n\napp.options('*', cors({ origin: allowedOrigins, credentials: true }));\napp.use(cors({\n  origin: allowedOrigins,\n  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n  allowedHeaders: ['Content-Type', 'Authorization'],\n  credentials: true,\n  maxAge: 600,\n}));\n\n\/\/ === CORPO: Limitar tamanho e tipo ===\napp.use(express.json({ limit: '10kb' }));\napp.use(express.urlencoded({ extended: false, limit: '10kb' }));\n\n\/\/ === ROTAS ===\napp.get('\/api\/v1\/status', (req, res) => {\n  res.json({\n    status: 'ok',\n    ambiente: process.env.NODE_ENV || 'desenvolvimento',\n    timestamp: new Date().toISOString(),\n  });\n});\n\napp.post('\/api\/v1\/dados', (req, res) => {\n  const { nome, email } = req.body;\n\n  \/\/ Valida\u00e7\u00e3o de input no servidor (nunca confiar no cliente)\n  if (!nome || typeof nome !== 'string' || nome.length > 100) {\n    return res.status(400).json({ erro: 'Nome inv\u00e1lido' });\n  }\n  if (!email || typeof email !== 'string' || !email.includes('@') || email.length > 254) {\n    return res.status(400).json({ erro: 'Email inv\u00e1lido' });\n  }\n\n  res.json({ mensagem: 'Dados recebidos com seguran\u00e7a', id: Date.now() });\n});\n\n\/\/ === TRATAMENTO DE ERROS: Sem exposi\u00e7\u00e3o de informa\u00e7\u00e3o interna ===\napp.use((req, res) => {\n  res.status(404).json({ erro: 'Recurso n\u00e3o encontrado' });\n});\n\napp.use((err, req, res, next) => {\n  console.error('Erro interno:', err.message); \/\/ Log interno apenas\n  res.status(500).json({ erro: 'Erro interno do servidor' }); \/\/ Nunca expor stack trace\n});\n\nconst PORT = parseInt(process.env.PORT || '3000', 10);\napp.listen(PORT, '0.0.0.0', () => {\n  console.log(`API segura com Helmet.js a correr na porta ${PORT} [${process.env.NODE_ENV || 'desenvolvimento'}]`);\n});\n\nexport default app;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"erros-comuns-ao-usar-o-helmet-js-e-como-evita-los\">Erros Comuns ao Usar o Helmet.js e Como Evit\u00e1-los<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A maioria dos problemas com Helmet em produ\u00e7\u00e3o resulta de configura\u00e7\u00e3o demasiado restritiva ou de conflitos com outros middlewares. Estes s\u00e3o os erros mais frequentes reportados na comunidade Node.js:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Erro<\/th><th>Sintoma<\/th><th>Solu\u00e7\u00e3o<\/th><\/tr><\/thead><tbody><tr><td>CSP bloqueia scripts inline<\/td><td>&#8220;Refused to execute inline script&#8221; no console do browser<\/td><td>Usar nonces por pedido ou mover scripts para ficheiros .js externos<\/td><\/tr><tr><td>HSTS ativo sem HTTPS configurado<\/td><td>Browser recusa permanentemente liga\u00e7\u00f5es HTTP ao dom\u00ednio<\/td><td>S\u00f3 ativar HSTS depois de TLS configurado e testado<\/td><\/tr><tr><td>Helmet registado depois do CORS<\/td><td>Cabe\u00e7alhos inconsistentes ou ausentes em algumas rotas<\/td><td>Sempre colocar <code>app.use(helmet())<\/code> como primeiro middleware<\/td><\/tr><tr><td>CSP bloqueia CDN de terceiros<\/td><td>Recursos CSS\/JS n\u00e3o carregam, erros 404 em rede<\/td><td>Adicionar explicitamente os dom\u00ednios CDN ao CSP na diretiva correta<\/td><\/tr><tr><td>COEP ativo com iframes externos<\/td><td>Iframes de pagamento ou mapas n\u00e3o carregam<\/td><td>Desativar <code>crossOriginEmbedderPolicy<\/code> ou usar pol\u00edtica <code>credentialless<\/code><\/td><\/tr><tr><td>upgrade-insecure-requests em desenvolvimento<\/td><td>Safari bloqueia recursos HTTP locais<\/td><td>Remover esta diretiva em ambientes sem HTTPS configurado<\/td><\/tr><tr><td>frame-ancestors bloqueia ferramentas de preview<\/td><td>App n\u00e3o carrega em ferramentas como Storybook ou Figma preview<\/td><td>Adicionar origens de ferramentas de desenvolvimento em <code>NODE_ENV=development<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"como-usar-nonces-com-csp-para-scripts-inline-seguros\">Como Usar Nonces com CSP para Scripts Inline Seguros<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Quando scripts inline s\u00e3o indispens\u00e1veis, como configura\u00e7\u00e3o inicial de uma SPA ou vari\u00e1veis de ambiente passadas para o cliente, use nonces em vez de <code>'unsafe-inline'<\/code>. Um nonce \u00e9 um valor criptograficamente aleat\u00f3rio gerado por pedido que o browser valida antes de executar o script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import crypto from 'node:crypto';\n\n\/\/ Middleware para gerar nonce \u00fanico por pedido\napp.use((req, res, next) => {\n  res.locals.cspNonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\n\/\/ Helmet configurado com o nonce gerado por pedido\napp.use((req, res, next) => {\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc: [\"'self'\"],\n        scriptSrc: [\"'self'\", `'nonce-${res.locals.cspNonce}'`],\n        \/\/ 'unsafe-inline' N\u00c3O \u00e9 necess\u00e1rio com nonces\n      },\n    },\n  })(req, res, next);\n});\n\n\/\/ No template HTML, usar o nonce em todos os scripts inline:\n\/\/ &lt;script nonce=\"&lt;%= locals.cspNonce %&gt;\"&gt;\n\/\/   window.__CONFIG__ = { apiUrl: 'https:\/\/api.exemplo.pt' };\n\/\/ &lt;\/script&gt;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"resolucao-de-problemas-8-situacoes-frequentes\">Resolu\u00e7\u00e3o de Problemas: 8 Situa\u00e7\u00f5es Frequentes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Os problemas seguintes s\u00e3o os mais reportados em projetos Node.js que integram o Helmet.js em ambiente de produ\u00e7\u00e3o:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Helmet n\u00e3o est\u00e1 a ser aplicado em todas as rotas<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sintoma: algumas rotas retornam os cabe\u00e7alhos, outras n\u00e3o. Causa: o Helmet foi registado depois de algumas rotas. Solu\u00e7\u00e3o: registar <code>app.use(helmet())<\/code> antes de qualquer <code>app.get()<\/code> ou <code>app.use('\/rota', router)<\/code>. Em Express, os middlewares s\u00e3o executados pela ordem de registo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Erros ao iniciar com configura\u00e7\u00e3o personalizada<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Causa: diretivas CSP inv\u00e1lidas ou valores <code>undefined<\/code> provenientes de vari\u00e1veis de ambiente n\u00e3o definidas. Solu\u00e7\u00e3o: validar todas as vari\u00e1veis de ambiente antes de as passar ao Helmet:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Valida\u00e7\u00e3o defensiva de vari\u00e1veis de ambiente\nconst API_URL = process.env.API_URL;\nif (process.env.NODE_ENV === 'production' &amp;&amp; !API_URL) {\n  throw new Error('API_URL \u00e9 obrigat\u00f3ria em produ\u00e7\u00e3o. Verifique o ficheiro .env');\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Imagens de terceiros bloqueadas pelo CSP<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sintoma: imagens de CDNs n\u00e3o carregam. Solu\u00e7\u00e3o: adicionar o dom\u00ednio \u00e0 diretiva <code>imgSrc<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>imgSrc: [\"'self'\", \"data:\", \"https:\/\/cdn.exemplo.com\", \"https:\/\/storage.googleapis.com\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. Fontes do Google Fonts bloqueadas<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Solu\u00e7\u00e3o: adicionar as origens do Google \u00e0s diretivas <code>styleSrc<\/code> e <code>fontSrc<\/code> separadamente, pois os CSS e as fontes v\u00eam de dom\u00ednios diferentes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>styleSrc: [\"'self'\", \"https:\/\/fonts.googleapis.com\"],\nfontSrc: [\"'self'\", \"https:\/\/fonts.gstatic.com\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>5. CORS funciona em desenvolvimento mas n\u00e3o em produ\u00e7\u00e3o<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Causa: a lista de origens CORS em produ\u00e7\u00e3o l\u00ea <code>process.env.ALLOWED_ORIGINS<\/code> que n\u00e3o foi definido no servidor. Verifica\u00e7\u00e3o: <code>console.log('Origens:', process.env.ALLOWED_ORIGINS)<\/code> no arranque da app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>6. Pedidos de preflight OPTIONS a falhar com 404<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sintoma: pedidos PUT ou DELETE falham com erro CORS. Causa: o Express n\u00e3o tem rota para <code>OPTIONS<\/code>. Solu\u00e7\u00e3o: adicionar <code>app.options('*', cors(corsOptions))<\/code> antes das outras rotas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>7. Cabe\u00e7alhos duplicados com proxies reversos<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sintoma: <code>Strict-Transport-Security<\/code> aparece duas vezes nos cabe\u00e7alhos de resposta. Causa: tanto o Nginx como a app Express est\u00e3o a definir HSTS. Solu\u00e7\u00e3o: desativar HSTS no Helmet quando o proxy reverso j\u00e1 o configura:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Quando o Nginx ou Cloudflare j\u00e1 definem HSTS\napp.use(helmet({\n  hsts: false, \/\/ Desativar no Node.js para evitar duplica\u00e7\u00e3o\n}));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>8. fetch() a falhar em pedidos com credenciais cross-origin<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sintoma: pedidos com <code>credentials: 'include'<\/code> falham com erro CORS mesmo com o cabe\u00e7alho <code>Access-Control-Allow-Origin<\/code> presente. Causa: <code>Access-Control-Allow-Origin: *<\/code> (wildcard) n\u00e3o \u00e9 compat\u00edvel com credenciais. Solu\u00e7\u00e3o: especificar a origem exata no CORS e definir <code>credentials: true<\/code> tanto no servidor como no cliente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"dicas-avancadas-para-hardening-em-producao\">Dicas Avan\u00e7adas para Hardening em Produ\u00e7\u00e3o<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Com o Helmet b\u00e1sico configurado, estas s\u00e3o as otimiza\u00e7\u00f5es que separam uma app segura de uma app verdadeiramente protegida para produ\u00e7\u00e3o:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Auditoria autom\u00e1tica de cabe\u00e7alhos no pipeline CI\/CD.<\/strong> Adicione um passo no pipeline que verifica os cabe\u00e7alhos em staging antes de promover para produ\u00e7\u00e3o:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# Script de auditoria de cabe\u00e7alhos para CI\/CD\nURL=\"https:\/\/staging.exemplo.pt\"\nHEADERS=$(curl -Is \"$URL\")\nPASS=0\nFAIL=0\n\ncheck_header() {\n  local header=\"$1\"\n  if echo \"$HEADERS\" | grep -qi \"$header\"; then\n    echo \"PASS: $header presente\"\n    PASS=$((PASS + 1))\n  else\n    echo \"FAIL: $header em falta\"\n    FAIL=$((FAIL + 1))\n  fi\n}\n\ncheck_header \"content-security-policy\"\ncheck_header \"strict-transport-security\"\ncheck_header \"x-content-type-options\"\ncheck_header \"referrer-policy\"\ncheck_header \"cross-origin-opener-policy\"\n\n# X-Powered-By n\u00e3o deve estar presente\nif echo \"$HEADERS\" | grep -qi \"x-powered-by\"; then\n  echo \"FAIL: X-Powered-By est\u00e1 a expor a tecnologia\"\n  FAIL=$((FAIL + 1))\nelse\n  echo \"PASS: X-Powered-By removido\"\n  PASS=$((PASS + 1))\nfi\n\necho \"\"\necho \"Resultado: $PASS passaram, $FAIL falharam\"\n[ \"$FAIL\" -eq 0 ] || exit 1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Helmet com rate limiting por IP para endpoints de autentica\u00e7\u00e3o.<\/strong> A prote\u00e7\u00e3o de cabe\u00e7alhos n\u00e3o dispensa rate limiting em endpoints sens\u00edveis como login ou reset de password. Para uma configura\u00e7\u00e3o completa de autentica\u00e7\u00e3o segura, consulte o tutorial de <a href=\"\/autenticacao-dois-fatores-nodejs\/\">Autentica\u00e7\u00e3o de Dois Fatores em Node.js<\/a> e o guia de <a href=\"\/oauth2-nodejs-pkce\/\">OAuth 2.0 em Node.js com PKCE<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Monitoriza\u00e7\u00e3o de viola\u00e7\u00f5es CSP em produ\u00e7\u00e3o.<\/strong> Configure um endpoint de reporte CSP dedicado que envia alertas quando o browser deteta tentativas de inje\u00e7\u00e3o:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Endpoint para relat\u00f3rios CSP com alertas\napp.post('\/api\/v1\/csp-report',\n  express.json({ type: ['application\/json', 'application\/csp-report'] }),\n  (req, res) => {\n    const violacao = req.body['csp-report'] || req.body;\n\n    \/\/ Registar a viola\u00e7\u00e3o com contexto suficiente para investiga\u00e7\u00e3o\n    console.warn('Viola\u00e7\u00e3o CSP detetada:', {\n      documento: violacao['document-uri'],\n      diretiva: violacao['violated-directive'],\n      recurso: violacao['blocked-uri'],\n      ip: req.ip,\n      userAgent: req.get('User-Agent'),\n      timestamp: new Date().toISOString(),\n    });\n\n    \/\/ Integrar com sistema de alertas em produ\u00e7\u00e3o\n    \/\/ ex: Slack, PagerDuty, Sentry, etc.\n\n    res.status(204).end();\n  }\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para complementar a seguran\u00e7a ao n\u00edvel do transporte, consulte o guia de <a href=\"\/mtls-node-js-tls-13\/\">mTLS em Node.js: TLS 1.3 em 12 Passos<\/a> que cobre a configura\u00e7\u00e3o de TLS m\u00fatuo e certificados de cliente. Para criptografia ao n\u00edvel dos dados, os tutoriais de <a href=\"\/assinaturas-digitais-nodejs-ecdsa-ed25519\/\">Assinaturas Digitais em Node.js: ECDSA e Ed25519<\/a> mostram como garantir integridade e autenticidade das mensagens. Para refer\u00eancia completa sobre os cabe\u00e7alhos, o <a href=\"https:\/\/github.com\/helmetjs\/helmet\" target=\"_blank\" rel=\"noopener noreferrer\">reposit\u00f3rio oficial do Helmet.js no GitHub<\/a> documenta cada op\u00e7\u00e3o de configura\u00e7\u00e3o dispon\u00edvel.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cobertura-relacionada\">Cobertura Relacionada<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Para completar a estrat\u00e9gia de seguran\u00e7a da sua app Node.js, consulte:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/oauth2-nodejs-pkce\/\">OAuth 2.0 em Node.js com PKCE: 12 Passos [2026]<\/a> &#8211; autentica\u00e7\u00e3o de terceiros com PKCE e prote\u00e7\u00e3o contra CSRF<\/li>\n<li><a href=\"\/autenticacao-dois-fatores-nodejs\/\">Autentica\u00e7\u00e3o de Dois Fatores em Node.js: 12 Passos [2026]<\/a> &#8211; TOTP, QR codes e WebAuthn em Express<\/li>\n<li><a href=\"\/mtls-node-js-tls-13\/\">mTLS em Node.js: TLS 1.3 em 12 Passos [2026]<\/a> &#8211; certificados de cliente e TLS m\u00fatuo para APIs internas<\/li>\n<li><a href=\"\/assinaturas-digitais-nodejs-ecdsa-ed25519\/\">Assinaturas Digitais em Node.js: ECDSA e Ed25519 em 12 Passos [2026]<\/a> &#8211; verifica\u00e7\u00e3o de integridade com criptografia assim\u00e9trica<\/li>\n<li><a href=\"\/aes-256-vs-chacha20\/\">AES-256 vs ChaCha20: Qual Cifra Escolher [2026]<\/a> &#8211; compara\u00e7\u00e3o de algoritmos de encripta\u00e7\u00e3o sim\u00e9trica com benchmarks reais<\/li>\n<li><a href=\"\/security-hub\/\">Seguran\u00e7a Online: Guia Pr\u00e1tico<\/a> &#8211; fundamentos de seguran\u00e7a digital para developers<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"perguntas-frequentes-sobre-helmet-js-em-node-js\">Perguntas Frequentes sobre Helmet.js em Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O Helmet.js substitui um WAF (Web Application Firewall)?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e3o. O Helmet define cabe\u00e7alhos HTTP que instruem o browser a adotar comportamentos seguros, como n\u00e3o executar scripts n\u00e3o autorizados. Um WAF filtra tr\u00e1fego malicioso ao n\u00edvel da rede antes de chegar \u00e0 aplica\u00e7\u00e3o. S\u00e3o complementares: o Helmet protege do lado do browser, o WAF protege ao n\u00edvel da infraestrutura. Use os dois em produ\u00e7\u00e3o para defesa em profundidade.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O CSP do Helmet quebra todas as aplica\u00e7\u00f5es React, Vue ou Angular?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A configura\u00e7\u00e3o padr\u00e3o com <code>default-src 'self'<\/code> pode bloquear scripts inline gerados pelo bundler de SPAs. A abordagem recomendada \u00e9 usar o modo <code>Content-Security-Policy-Report-Only<\/code> durante uma semana em staging para identificar todas as viola\u00e7\u00f5es, depois ajustar as diretivas CSP com base nos relat\u00f3rios recebidos, antes de enfor\u00e7ar em produ\u00e7\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Preciso de atualizar o Helmet regularmente?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sim. As boas pr\u00e1ticas de cabe\u00e7alhos HTTP evoluem \u00e0 medida que browsers adicionam novas diretivas CSP ou novas pol\u00edticas de isolamento. Configure o Dependabot ou <code>npm audit<\/code> no CI\/CD para receber alertas autom\u00e1ticos de atualiza\u00e7\u00f5es. O Node.js lan\u00e7ou em junho de 2026 atualiza\u00e7\u00f5es de severidade HIGH para as linhas 22.x, 24.x e 26.x, ilustrando a import\u00e2ncia de manter depend\u00eancias atualizadas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O Helmet tem impacto na performance da app?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O impacto \u00e9 negligenci\u00e1vel. O Helmet define cabe\u00e7alhos HTTP de texto simples, uma opera\u00e7\u00e3o de microssegundos. Em compara\u00e7\u00e3o com o overhead de parsear JSON, validar autentica\u00e7\u00e3o ou aceder a bases de dados, o Helmet n\u00e3o \u00e9 mensur\u00e1vel em benchmarks realistas. A LogRocket confirma que o overhead do Helmet \u00e9 praticamente zero em aplica\u00e7\u00f5es Express em produ\u00e7\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O Helmet protege contra SQL Injection ou XSS no lado do servidor?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e3o diretamente. O Helmet age no n\u00edvel dos cabe\u00e7alhos HTTP de resposta, instruindo o browser a n\u00e3o executar scripts injetados via CSP. A prote\u00e7\u00e3o contra SQL Injection e XSS no servidor requer valida\u00e7\u00e3o e sanitiza\u00e7\u00e3o de input com bibliotecas como <code>zod<\/code> ou <code>joi<\/code>, mais o uso de queries parametrizadas para bases de dados. O Helmet \u00e9 a \u00faltima linha de defesa no browser, n\u00e3o a primeira linha no servidor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Posso desativar cabe\u00e7alhos espec\u00edficos sem desativar o Helmet completo?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sim. Qualquer sub-middleware pode ser desativado passando <code>false<\/code> na op\u00e7\u00e3o correspondente, enquanto os restantes continuam ativos:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(helmet({\n  dnsPrefetchControl: false,  \/\/ Desativa X-DNS-Prefetch-Control\n  originAgentCluster: false,  \/\/ Desativa Origin-Agent-Cluster\n  \/\/ Todos os outros 13 cabe\u00e7alhos continuam ativos\n}));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Devo usar Helmet numa API GraphQL ou apenas em APIs REST?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O Helmet \u00e9 relevante para qualquer servidor HTTP que responda a browsers, independentemente do protocolo da API. Para APIs GraphQL consumidas por browsers, o CSP e o CORS s\u00e3o especialmente importantes porque o GraphQL Playground e outros clientes web est\u00e3o sujeitos aos mesmos vetores de ataque que qualquer SPA. Para APIs consumidas exclusivamente por servidores em comunica\u00e7\u00f5es server-to-server, o HSTS e o CORS t\u00eam menos relev\u00e2ncia, mas os outros cabe\u00e7alhos continuam a valer a pena para defesa em profundidade.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O Helmet.js \u00e9 compat\u00edvel com TypeScript?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sim. O Helmet inclui defini\u00e7\u00f5es TypeScript nativas no pacote principal, sem necessidade de instalar <code>@types\/helmet<\/code> separadamente. A importa\u00e7\u00e3o <code>import helmet from 'helmet'<\/code> funciona diretamente em projetos TypeScript com resolu\u00e7\u00e3o de m\u00f3dulos <code>moduleResolution: node16<\/code> ou <code>bundler<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>O Express.js n\u00e3o inclui um \u00fanico cabe\u00e7alho de seguran\u00e7a por defeito. Quando uma app Node.js responde a um pedido HTTP sem configura\u00e7\u00e3o adicional, o browser recebe zero prote\u00e7\u00f5es contra XSS,\u2026<\/p>\n","protected":false},"author":7,"featured_media":140,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-139","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/139","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/comments?post=139"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/139\/revisions"}],"predecessor-version":[{"id":141,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/posts\/139\/revisions\/141"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media\/140"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/media?parent=139"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/categories?post=139"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/pt\/wp-json\/wp\/v2\/tags?post=139"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}