{"id":233,"date":"2026-06-18T16:41:41","date_gmt":"2026-06-18T16:41:41","guid":{"rendered":"https:\/\/shattered.io\/fr\/2026\/06\/18\/security-headers-nodejs\/"},"modified":"2026-06-18T16:48:42","modified_gmt":"2026-06-18T16:48:42","slug":"security-headers-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/fr\/2026\/06\/18\/security-headers-nodejs\/","title":{"rendered":"En-t\u00eates de S\u00e9curit\u00e9 HTTP en Node.js : 12 \u00c9tapes, 30 Min [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Les en-t\u00eates de s\u00e9curit\u00e9 HTTP constituent la premi\u00e8re ligne de d\u00e9fense d&#8217;une application web. Pourtant, selon l&#8217;<a href=\"https:\/\/owasp.org\/Top10\/\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP Top 10<\/a>, la mauvaise configuration de s\u00e9curit\u00e9 (A05) touche la majorit\u00e9 des applications test\u00e9es, et l&#8217;absence d&#8217;en-t\u00eates HTTP adapt\u00e9s reste l&#8217;une des failles les plus fr\u00e9quentes et les plus faciles \u00e0 corriger. En Node.js, la biblioth\u00e8que <strong>Helmet.js 8.1.0<\/strong> (plus de 2 millions de t\u00e9l\u00e9chargements hebdomadaires sur npm) permet d&#8217;ajouter 12 en-t\u00eates de s\u00e9curit\u00e9 en une seule ligne de code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ce tutoriel couvre chaque en-t\u00eate en d\u00e9tail, sa configuration avec Helmet, les pi\u00e8ges courants et la v\u00e9rification de votre score de s\u00e9curit\u00e9. \u00c0 la fin, vous disposerez d&#8217;un projet Express complet, configur\u00e9 selon les standards 2026, pr\u00eat pour la production.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"pourquoi-les-en-tetes-de-securite-sont-essentiels-en-2026\">Pourquoi les en-t\u00eates de s\u00e9curit\u00e9 sont essentiels en 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un en-t\u00eate HTTP de s\u00e9curit\u00e9 est une directive envoy\u00e9e par le serveur au navigateur du client. Il indique au navigateur comment traiter le contenu de la page : quelles sources de scripts autoriser, si la connexion doit \u00eatre chiffr\u00e9e, si la page peut \u00eatre embarqu\u00e9e dans une iframe, etc. Sans ces en-t\u00eates, le navigateur applique des comportements par d\u00e9faut souvent permissifs qui ouvrent la porte aux attaques les plus r\u00e9pandues.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les attaques directement li\u00e9es \u00e0 des en-t\u00eates manquants ou mal configur\u00e9s incluent : le cross-site scripting (XSS), le clickjacking, le MIME sniffing, les attaques de type downgrade HTTPS, et la fuite d&#8217;informations via le Referer. La <strong>directive A05 de l&#8217;OWASP Top 10 2021<\/strong> classe explicitement la mauvaise configuration de s\u00e9curit\u00e9 parmi les 5 risques les plus critiques pour les applications web. La r\u00e9f\u00e9rence compl\u00e8te se trouve dans la <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/HTTP_Headers_Cheat_Sheet.html\" rel=\"noopener noreferrer\" target=\"_blank\">HTTP Headers Cheat Sheet de l&#8217;OWASP<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En Node.js avec Express, les en-t\u00eates de s\u00e9curit\u00e9 ne sont pas activ\u00e9s par d\u00e9faut. Un serveur Express vierge renvoie des en-t\u00eates minimaux, dont le r\u00e9v\u00e9lateur <code>X-Powered-By: Express<\/code> qui annonce la technologie utilis\u00e9e aux attaquants. La solution standardis\u00e9e dans l&#8217;\u00e9cosyst\u00e8me Node.js est <strong>Helmet.js<\/strong>, un middleware qui configure automatiquement 12 en-t\u00eates de s\u00e9curit\u00e9 et supprime <code>X-Powered-By<\/code>. Avec plus de 2 millions de t\u00e9l\u00e9chargements hebdomadaires sur npm, Helmet est utilis\u00e9 dans la quasi-totalit\u00e9 des applications Express en production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La derni\u00e8re version stable, <strong>Helmet 8.1.0<\/strong> (sortie le 17 mars 2025), est celle que nous utilisons dans ce tutoriel. Elle apporte une configuration plus granulaire des en-t\u00eates Cross-Origin et un meilleur support des politiques de permissions modernes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequis-et-versions\">Pr\u00e9requis et versions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Avant de commencer, v\u00e9rifiez que votre environnement correspond aux versions suivantes :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Technologie<\/th><th>Version minimale<\/th><th>Version recommand\u00e9e 2026<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>18.x LTS<\/td><td>22.x LTS<\/td><\/tr><tr><td>npm<\/td><td>9.x<\/td><td>10.x<\/td><\/tr><tr><td>Express<\/td><td>4.18.x<\/td><td>5.x<\/td><\/tr><tr><td>Helmet.js<\/td><td>7.x<\/td><td>8.1.0<\/td><\/tr><tr><td>Syst\u00e8me d&#8217;exploitation<\/td><td>Linux \/ macOS \/ Windows<\/td><td>Ubuntu 24.04 LTS<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Connaissances requises : bases de JavaScript et de Node.js, utilisation basique de la ligne de commande, notions fondamentales de HTTP. Si vous d\u00e9butez avec l&#8217;authentification Node.js, consultez d&#8217;abord notre guide <a href=\"\/authentification-jwt-nodejs\/\">Authentification JWT en Node.js : 12 \u00e9tapes<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-1-initialiser-le-projet-node-js\">\u00c9tape 1 : Initialiser le projet Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cr\u00e9ez un dossier de travail et initialisez un nouveau projet Node.js avec npm :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir node-security-headers\ncd node-security-headers\nnpm init -y\nnpm install express helmet<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cette commande installe Express et Helmet dans leurs derni\u00e8res versions stables. V\u00e9rifiez les versions install\u00e9es :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm list express helmet<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie attendue :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node-security-headers@1.0.0\n\u251c\u2500\u2500 express@5.0.1\n\u2514\u2500\u2500 helmet@8.1.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cr\u00e9ez ensuite le fichier principal <code>app.js<\/code> avec une structure de base :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst app = express();\n\napp.get('\/', (req, res) => {\n  res.json({ message: 'Serveur Node.js sans s\u00e9curit\u00e9' });\n});\n\napp.listen(3000, () => {\n  console.log('Serveur d\u00e9marr\u00e9 sur http:\/\/localhost:3000');\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lancez le serveur et examinez les en-t\u00eates renvoy\u00e9s sans Helmet :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node app.js &\ncurl -I http:\/\/localhost:3000\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie typique d&#8217;un serveur Express sans Helmet :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/1.1 200 OK\nX-Powered-By: Express\nContent-Type: application\/json; charset=utf-8\nContent-Length: 42\nDate: Wed, 18 Jun 2026 10:00:00 GMT\nConnection: keep-alive<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On constate imm\u00e9diatement l&#8217;absence totale d&#8217;en-t\u00eates de s\u00e9curit\u00e9 et la pr\u00e9sence de <code>X-Powered-By: Express<\/code> qui expose la technologie utilis\u00e9e. Un attaquant qui r\u00e9cup\u00e8re cet en-t\u00eate peut cibler directement les vuln\u00e9rabilit\u00e9s connues de la version d&#8217;Express utilis\u00e9e.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-2-integrer-helmet-avec-la-configuration-par-defaut\">\u00c9tape 2 : Int\u00e9grer Helmet avec la configuration par d\u00e9faut<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;int\u00e9gration de base de <a href=\"https:\/\/helmetjs.github.io\/\" rel=\"noopener noreferrer\" target=\"_blank\">Helmet.js<\/a> requiert deux lignes de code. Modifiez <code>app.js<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\n\nconst app = express();\n\n\/\/ Helmet doit \u00eatre plac\u00e9 avant les routes\napp.use(helmet());\n\napp.get('\/', (req, res) => {\n  res.json({ message: 'Serveur Node.js s\u00e9curis\u00e9 avec Helmet' });\n});\n\napp.listen(3000, () => {\n  console.log('Serveur d\u00e9marr\u00e9 sur http:\/\/localhost:3000');\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Red\u00e9marrez et v\u00e9rifiez les en-t\u00eates avec Helmet actif :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node app.js &\ncurl -I http:\/\/localhost:3000\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie avec Helmet en configuration par d\u00e9faut :<\/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-Embedder-Policy: require-corp\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\nX-Download-Options: noopen\nX-Frame-Options: SAMEORIGIN\nX-Permitted-Cross-Domain-Policies: none\nX-XSS-Protection: 0\nContent-Type: application\/json; charset=utf-8<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En une ligne, <code>app.use(helmet())<\/code> a ajout\u00e9 12 en-t\u00eates de s\u00e9curit\u00e9 et supprim\u00e9 <code>X-Powered-By<\/code>. Le tableau suivant r\u00e9sume le r\u00f4le de chaque en-t\u00eate :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>En-t\u00eate HTTP<\/th><th>Valeur par d\u00e9faut Helmet 8.x<\/th><th>Protection contre<\/th><\/tr><\/thead><tbody><tr><td>Content-Security-Policy<\/td><td>default-src &#8216;self&#8217; + directives multiples<\/td><td>XSS, injection de code<\/td><\/tr><tr><td>Strict-Transport-Security<\/td><td>max-age=31536000; includeSubDomains<\/td><td>Downgrade HTTPS, SSL stripping<\/td><\/tr><tr><td>X-Frame-Options<\/td><td>SAMEORIGIN<\/td><td>Clickjacking<\/td><\/tr><tr><td>X-Content-Type-Options<\/td><td>nosniff<\/td><td>MIME sniffing<\/td><\/tr><tr><td>Referrer-Policy<\/td><td>no-referrer<\/td><td>Fuite d&#8217;URL sensibles<\/td><\/tr><tr><td>Cross-Origin-Embedder-Policy<\/td><td>require-corp<\/td><td>Cross-origin data leaks<\/td><\/tr><tr><td>Cross-Origin-Opener-Policy<\/td><td>same-origin<\/td><td>Cross-origin window attacks<\/td><\/tr><tr><td>Cross-Origin-Resource-Policy<\/td><td>same-origin<\/td><td>Spectre, cross-site reads<\/td><\/tr><tr><td>X-DNS-Prefetch-Control<\/td><td>off<\/td><td>Fuite DNS<\/td><\/tr><tr><td>X-Download-Options<\/td><td>noopen<\/td><td>Ex\u00e9cution automatique IE<\/td><\/tr><tr><td>X-Permitted-Cross-Domain-Policies<\/td><td>none<\/td><td>Flash, PDF cross-domain<\/td><\/tr><tr><td>X-XSS-Protection<\/td><td>0 (d\u00e9sactiv\u00e9 d\u00e9lib\u00e9r\u00e9ment)<\/td><td>N\/A (obsol\u00e8te, dangereux en mode actif)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-3-configurer-content-security-policy-csp\">\u00c9tape 3 : Configurer Content-Security-Policy (CSP)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La <strong>Content-Security-Policy<\/strong> (CSP) est l&#8217;en-t\u00eate le plus puissant et le plus complexe \u00e0 configurer. Elle d\u00e9finit les sources autoris\u00e9es pour chaque type de ressource : scripts, styles, images, polices, iframes, etc. Un CSP mal configur\u00e9 peut soit bloquer les ressources l\u00e9gitimes de votre application, soit laisser des failles XSS ouvertes. La <a href=\"https:\/\/developer.mozilla.org\/fr\/docs\/Web\/HTTP\/Headers\/Content-Security-Policy\" rel=\"noopener noreferrer\" target=\"_blank\">sp\u00e9cification MDN sur la Content-Security-Policy<\/a> liste l&#8217;ensemble des directives disponibles.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En pratique, les directives les plus importantes sont :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Directive CSP<\/th><th>R\u00f4le<\/th><th>Valeur s\u00e9curis\u00e9e recommand\u00e9e<\/th><\/tr><\/thead><tbody><tr><td>default-src<\/td><td>Source par d\u00e9faut pour tout type de ressource<\/td><td>&#8216;self&#8217;<\/td><\/tr><tr><td>script-src<\/td><td>Sources des scripts JavaScript<\/td><td>&#8216;self&#8217; + nonce pour scripts inline<\/td><\/tr><tr><td>style-src<\/td><td>Sources des feuilles de style CSS<\/td><td>&#8216;self&#8217; https:<\/td><\/tr><tr><td>img-src<\/td><td>Sources des images<\/td><td>&#8216;self&#8217; data: https:<\/td><\/tr><tr><td>font-src<\/td><td>Sources des polices de caract\u00e8res<\/td><td>&#8216;self&#8217; https: data:<\/td><\/tr><tr><td>connect-src<\/td><td>Sources pour fetch(), XHR, WebSocket<\/td><td>&#8216;self&#8217;<\/td><\/tr><tr><td>frame-ancestors<\/td><td>Qui peut embarquer la page dans une iframe<\/td><td>&#8216;none&#8217; ou &#8216;self&#8217;<\/td><\/tr><tr><td>object-src<\/td><td>Sources pour Flash, plugins<\/td><td>&#8216;none&#8217;<\/td><\/tr><tr><td>base-uri<\/td><td>Restreint la balise &lt;base&gt;<\/td><td>&#8216;self&#8217;<\/td><\/tr><tr><td>upgrade-insecure-requests<\/td><td>Force le chargement HTTPS des ressources HTTP<\/td><td>Actif en production<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Voici une configuration CSP r\u00e9aliste pour une application Express avec API REST et ressources statiques :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\n\nconst app = express();\n\napp.use(\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc: [\"'self'\"],\n        scriptSrc: [\"'self'\"],\n        styleSrc: [\"'self'\", 'https:', \"'unsafe-inline'\"],\n        imgSrc: [\"'self'\", 'data:', 'https:'],\n        fontSrc: [\"'self'\", 'https:', 'data:'],\n        connectSrc: [\"'self'\"],\n        frameAncestors: [\"'none'\"],\n        objectSrc: [\"'none'\"],\n        upgradeInsecureRequests: [],\n        reportUri: '\/csp-report',\n      },\n    },\n  })\n);\n\n\/\/ Endpoint pour recevoir les rapports de violations CSP\napp.post('\/csp-report', express.json({ type: 'application\/csp-report' }), (req, res) => {\n  console.log('Violation CSP d\u00e9tect\u00e9e:', JSON.stringify(req.body, null, 2));\n  res.status(204).end();\n});\n\napp.get('\/', (req, res) => {\n  res.json({ message: 'API s\u00e9curis\u00e9e avec CSP' });\n});\n\napp.listen(3000);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour les applications qui utilisent des CDN (Google Fonts, Bootstrap, etc.), ajoutez explicitement ces domaines dans les directives concern\u00e9es. Si votre front-end utilise des scripts inline ou des gestionnaires d&#8217;\u00e9v\u00e9nements inline, l&#8217;approche recommand\u00e9e en 2026 est d&#8217;utiliser des <strong>nonces CSP<\/strong> plut\u00f4t que <code>'unsafe-inline'<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"csp-en-mode-rapport-uniquement-recommande-pour-le-deploiement-initial\">CSP en mode rapport uniquement (recommand\u00e9 pour le d\u00e9ploiement initial)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pendant le d\u00e9ploiement initial, activez d&#8217;abord le mode rapport (<code>Content-Security-Policy-Report-Only<\/code>) pour d\u00e9tecter les violations sans bloquer les ressources l\u00e9gitimes :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    contentSecurityPolicy: {\n      reportOnly: true, \/\/ Observe les violations sans bloquer\n      directives: {\n        defaultSrc: [\"'self'\"],\n        scriptSrc: [\"'self'\"],\n        reportUri: '\/csp-report',\n      },\n    },\n  })\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Consultez les logs des violations pendant 24 \u00e0 48 heures, ajustez les directives pour les sources l\u00e9gitimes, puis basculez en mode enforcement en supprimant <code>reportOnly: true<\/code>. Cette approche \u00e9vite de bloquer des ressources l\u00e9gitimes en production.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-4-http-strict-transport-security-hsts\">\u00c9tape 4 : HTTP Strict Transport Security (HSTS)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;en-t\u00eate <strong>Strict-Transport-Security<\/strong> (HSTS) force le navigateur \u00e0 utiliser exclusivement HTTPS pour toutes les communications avec votre domaine pendant une dur\u00e9e d\u00e9finie. Une fois que le navigateur a re\u00e7u cet en-t\u00eate, il refuse toute connexion HTTP et convertit automatiquement toutes les requ\u00eates en HTTPS, m\u00eame si l&#8217;utilisateur tape <code>http:\/\/<\/code> dans la barre d&#8217;adresse.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">HSTS prot\u00e8ge contre les attaques de type <em>SSL stripping<\/em>, o\u00f9 un attaquant plac\u00e9 en position de man-in-the-middle intercepte la connexion initiale HTTP avant la redirection vers HTTPS. La configuration par d\u00e9faut de Helmet d\u00e9finit un HSTS de 365 jours (31 536 000 secondes) avec <code>includeSubDomains<\/code>. Pour la production, ajoutez <code>preload<\/code> pour soumettre votre domaine \u00e0 la liste de pr\u00e9chargement HSTS int\u00e9gr\u00e9e aux navigateurs :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    hsts: {\n      maxAge: 31536000,         \/\/ 365 jours en secondes\n      includeSubDomains: true,  \/\/ Applique HSTS \u00e0 tous les sous-domaines\n      preload: true,            \/\/ Soumet le domaine \u00e0 la liste HSTS preload\n    },\n  })\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Points critiques sur la configuration HSTS :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>N&#8217;activez jamais HSTS avant d&#8217;avoir un certificat SSL valide.<\/strong> Si votre HTTPS tombe en panne avec HSTS actif, les utilisateurs ne pourront plus acc\u00e9der au site pendant toute la dur\u00e9e de <code>maxAge<\/code>.<\/li><li><strong>Commencez avec un <code>maxAge<\/code> court<\/strong> (86 400 secondes = 1 jour) pendant les tests, puis augmentez progressivement jusqu&#8217;\u00e0 31 536 000.<\/li><li><strong><code>includeSubDomains<\/code><\/strong> : activez cette option uniquement si tous vos sous-domaines supportent HTTPS.<\/li><li><strong>L&#8217;option <code>preload<\/code><\/strong> est irr\u00e9versible \u00e0 court terme : une fois soumis, le retrait de votre domaine de la liste peut prendre des mois. Utilisez-la uniquement quand vous \u00eates certain de maintenir HTTPS ind\u00e9finiment.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Pour approfondir la configuration TLS, consultez notre comparatif <a href=\"\/tls-1-3-vs-tls-1-2\/\">TLS 1.3 vs TLS 1.2 : 40% plus rapide, 5 CVE<\/a> et notre tutoriel <a href=\"\/openssl-cles-certificats-tutoriel\/\">OpenSSL : cl\u00e9s et certificats en 12 \u00e9tapes<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-5-x-frame-options-et-protection-contre-le-clickjacking\">\u00c9tape 5 : X-Frame-Options et protection contre le clickjacking<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le <strong>clickjacking<\/strong> est une attaque o\u00f9 un attaquant embarque votre page dans une iframe invisible superpos\u00e9e \u00e0 une interface trompeuse. L&#8217;utilisateur pense cliquer sur un bouton inoffensif, mais clique en r\u00e9alit\u00e9 sur un \u00e9l\u00e9ment de votre application (confirmation de virement bancaire, validation de commande, modification de param\u00e8tres de s\u00e9curit\u00e9, etc.).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Helmet configure par d\u00e9faut <code>X-Frame-Options: SAMEORIGIN<\/code>, ce qui autorise l&#8217;embarquement dans une iframe uniquement depuis la m\u00eame origine. Les trois valeurs possibles sont :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Valeur X-Frame-Options<\/th><th>Effet<\/th><th>Cas d&#8217;usage recommand\u00e9<\/th><\/tr><\/thead><tbody><tr><td>DENY<\/td><td>Interdit toute iframe, quelle que soit l&#8217;origine<\/td><td>Pages de connexion, paiement, administration<\/td><\/tr><tr><td>SAMEORIGIN<\/td><td>Autorise uniquement la m\u00eame origine<\/td><td>Applications web standard<\/td><\/tr><tr><td>ALLOW-FROM uri<\/td><td>Autorise une URI sp\u00e9cifique (obsol\u00e8te)<\/td><td>Ne pas utiliser en 2026<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Pour une protection maximale sur les pages sensibles, utilisez <code>DENY<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Configuration globale DENY\napp.use(\n  helmet({\n    frameguard: {\n      action: 'deny',\n    },\n  })\n);\n\n\/\/ Ou par route sp\u00e9cifique pour les pages les plus sensibles\nconst sensitiveHelmet = helmet({ frameguard: { action: 'deny' } });\napp.use('\/login', sensitiveHelmet);\napp.use('\/payment', sensitiveHelmet);\napp.use('\/admin', sensitiveHelmet);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En 2026, la directive CSP <code>frame-ancestors<\/code> est pr\u00e9f\u00e9r\u00e9e \u00e0 <code>X-Frame-Options<\/code> pour les navigateurs modernes, car elle offre plus de flexibilit\u00e9 (plusieurs origines autoris\u00e9es simultan\u00e9ment). Helmet active les deux par d\u00e9faut pour garantir la compatibilit\u00e9 avec les navigateurs plus anciens. En cas de conflit, <code>frame-ancestors<\/code> a la priorit\u00e9 dans les navigateurs qui le supportent.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-6-x-content-type-options-et-mime-sniffing\">\u00c9tape 6 : X-Content-Type-Options et MIME sniffing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;attaque par <strong>MIME sniffing<\/strong> exploite le comportement de certains navigateurs anciens qui ignorent le type de contenu d\u00e9clar\u00e9 (<code>Content-Type<\/code>) et tentent de deviner le type r\u00e9el du fichier. Un attaquant peut t\u00e9l\u00e9charger un fichier apparemment inoffensif (image, document texte) qui contient en r\u00e9alit\u00e9 du code JavaScript ex\u00e9cutable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;en-t\u00eate <code>X-Content-Type-Options: nosniff<\/code> interdit au navigateur de faire du MIME sniffing et l&#8217;oblige \u00e0 respecter strictement le <code>Content-Type<\/code> d\u00e9clar\u00e9 par le serveur. Il s&#8217;agit de l&#8217;en-t\u00eate le plus simple \u00e0 d\u00e9ployer, avec un impact z\u00e9ro sur les fonctionnalit\u00e9s l\u00e9gitimes si vos routes Express d\u00e9clarent correctement leurs types MIME :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Activ\u00e9 par d\u00e9faut avec helmet()\n\/\/ Configuration explicite si n\u00e9cessaire :\napp.use(\n  helmet({\n    noSniff: true, \/\/ X-Content-Type-Options: nosniff\n  })\n);\n\n\/\/ Bonne pratique compl\u00e9mentaire : d\u00e9clarer explicitement le Content-Type\napp.get('\/api\/data', (req, res) => {\n  res.setHeader('Content-Type', 'application\/json');\n  res.json({ data: 'valeur' });\n});\n\napp.get('\/document.pdf', (req, res) => {\n  res.setHeader('Content-Type', 'application\/pdf');\n  res.sendFile('.\/documents\/rapport.pdf');\n});\n\napp.get('\/image.png', (req, res) => {\n  res.setHeader('Content-Type', 'image\/png');\n  res.sendFile('.\/public\/logo.png');\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-7-referrer-policy-et-protection-de-la-vie-privee\">\u00c9tape 7 : Referrer-Policy et protection de la vie priv\u00e9e<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;en-t\u00eate <strong>Referrer-Policy<\/strong> contr\u00f4le quelle information de l&#8217;URL courante est transmise dans l&#8217;en-t\u00eate <code>Referer<\/code> lorsque l&#8217;utilisateur clique sur un lien ou que votre page effectue des requ\u00eates externes. Sans cet en-t\u00eate, les URLs compl\u00e8tes (incluant les param\u00e8tres de requ\u00eate sensibles) peuvent \u00eatre transmises \u00e0 des serveurs tiers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sc\u00e9nario concret de fuite : votre application utilise des URLs comme <code>https:\/\/monapp.com\/reset-password?token=abc123secret<\/code>. Si la page de r\u00e9initialisation contient une image h\u00e9berg\u00e9e sur un serveur tiers (analytics, CDN), le token peut appara\u00eetre dans les logs du serveur tiers via l&#8217;en-t\u00eate <code>Referer<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Helmet configure par d\u00e9faut <code>Referrer-Policy: no-referrer<\/code>. Les valeurs courantes :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Valeur Referrer-Policy<\/th><th>Information transmise<\/th><th>Recommand\u00e9 pour<\/th><\/tr><\/thead><tbody><tr><td>no-referrer<\/td><td>Aucune information<\/td><td>API, pages avec tokens dans URL<\/td><\/tr><tr><td>strict-origin<\/td><td>Origine seule (HTTPS uniquement)<\/td><td>Bon compromis g\u00e9n\u00e9ral<\/td><\/tr><tr><td>strict-origin-when-cross-origin<\/td><td>URL compl\u00e8te en same-origin, origine seule en cross-origin<\/td><td>Apps avec analytics first-party<\/td><\/tr><tr><td>same-origin<\/td><td>URL compl\u00e8te uniquement en same-origin<\/td><td>Applications internes ferm\u00e9es<\/td><\/tr><tr><td>no-referrer-when-downgrade<\/td><td>URL compl\u00e8te sauf HTTPS vers HTTP<\/td><td>A \u00e9viter<\/td><\/tr><tr><td>unsafe-url<\/td><td>URL compl\u00e8te dans tous les cas<\/td><td>Ne jamais utiliser<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    referrerPolicy: {\n      \/\/ Pour une API REST pure : no-referrer (d\u00e9faut Helmet)\n      \/\/ Pour une app web avec analytics : strict-origin-when-cross-origin\n      policy: 'strict-origin-when-cross-origin',\n    },\n  })\n);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-8-permissions-policy\">\u00c9tape 8 : Permissions-Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;en-t\u00eate <strong>Permissions-Policy<\/strong> (anciennement Feature-Policy) contr\u00f4le quelles fonctionnalit\u00e9s du navigateur votre application est autoris\u00e9e \u00e0 utiliser, et si des iframes tierces peuvent en b\u00e9n\u00e9ficier. Il permet de d\u00e9sactiver des APIs potentiellement dangereuses ou inutiles : acc\u00e8s cam\u00e9ra, microphone, g\u00e9olocalisation, capteurs de l&#8217;appareil, API Payment, etc.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Helmet 8.x n&#8217;inclut pas de Permissions-Policy par d\u00e9faut. Configurez-le via un middleware personnalis\u00e9 selon les besoins r\u00e9els de votre application :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\n\nconst app = express();\napp.use(helmet());\n\n\/\/ Permissions-Policy : d\u00e9sactiver les APIs non utilis\u00e9es\napp.use((req, res, next) => {\n  res.setHeader(\n    'Permissions-Policy',\n    [\n      'camera=()',           \/\/ D\u00e9sactive l'acc\u00e8s cam\u00e9ra\n      'microphone=()',       \/\/ D\u00e9sactive l'acc\u00e8s microphone\n      'geolocation=()',      \/\/ D\u00e9sactive la g\u00e9olocalisation\n      'payment=()',          \/\/ D\u00e9sactive l'API Payment (si non marchand)\n      'usb=()',              \/\/ D\u00e9sactive l'acc\u00e8s USB\n      'interest-cohort=()', \/\/ D\u00e9sactive le tracking comportemental\n      'accelerometer=()',    \/\/ D\u00e9sactive les capteurs de mouvement\n      'gyroscope=()',\n      'magnetometer=()',\n    ].join(', ')\n  );\n  next();\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour une application e-commerce qui utilise r\u00e9ellement l&#8217;API Payment, adaptez la politique en autorisant uniquement depuis votre propre origine :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ E-commerce : autoriser les paiements depuis l'origine de l'app uniquement\napp.use((req, res, next) => {\n  res.setHeader(\n    'Permissions-Policy',\n    [\n      'camera=()',\n      'microphone=()',\n      'geolocation=()',\n      'payment=(self)',   \/\/ Autoris\u00e9 uniquement depuis votre propre origine\n      'usb=()',\n    ].join(', ')\n  );\n  next();\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-9-en-tetes-cross-origin-coep-coop-corp\">\u00c9tape 9 : En-t\u00eates Cross-Origin (COEP, COOP, CORP)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Depuis les d\u00e9couvertes des vuln\u00e9rabilit\u00e9s Spectre et Meltdown, les navigateurs ont introduit trois en-t\u00eates d&#8217;isolation cross-origin qui constituent une couche de protection suppl\u00e9mentaire contre l&#8217;extraction de donn\u00e9es entre origines diff\u00e9rentes. Helmet 8.x les active tous par d\u00e9faut :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Cross-Origin-Embedder-Policy (COEP)<\/strong> : Emp\u00eache un document d&#8217;embarquer des ressources cross-origin sans autorisation explicite. Valeur par d\u00e9faut : <code>require-corp<\/code>.<\/li><li><strong>Cross-Origin-Opener-Policy (COOP)<\/strong> : Isole le contexte de navigation, emp\u00eachant les pages cross-origin d&#8217;acc\u00e9der \u00e0 l&#8217;objet <code>window<\/code> de votre page. Valeur par d\u00e9faut : <code>same-origin<\/code>.<\/li><li><strong>Cross-Origin-Resource-Policy (CORP)<\/strong> : Emp\u00eache d&#8217;autres origines de lire vos ressources via des balises <code>&lt;img&gt;<\/code>, <code>&lt;script&gt;<\/code>, etc. Valeur par d\u00e9faut : <code>same-origin<\/code>.<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  helmet({\n    \/\/ Cross-Origin-Embedder-Policy\n    crossOriginEmbedderPolicy: {\n      policy: 'require-corp', \/\/ Stricte, peut casser les ressources CDN\n      \/\/ policy: 'unsafe-none', \/\/ Pour d\u00e9sactiver si vous utilisez des CDN\n    },\n\n    \/\/ Cross-Origin-Opener-Policy\n    crossOriginOpenerPolicy: {\n      policy: 'same-origin',\n      \/\/ policy: 'same-origin-allow-popups', \/\/ Si vous ouvrez des popups auth\n    },\n\n    \/\/ Cross-Origin-Resource-Policy\n    crossOriginResourcePolicy: {\n      policy: 'same-origin',\n      \/\/ policy: 'same-site', \/\/ Pour partager avec sous-domaines\n      \/\/ policy: 'cross-origin', \/\/ Pour ressources publiques (CDN, images publiques)\n    },\n  })\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Si votre application embarque des ressources tierces (images CDN, iframes YouTube, widgets tiers), <code>crossOriginEmbedderPolicy: 'require-corp'<\/code> peut bloquer ces ressources. Dans ce cas, d\u00e9sactivez-le avec <code>'unsafe-none'<\/code> en attendant que vos fournisseurs CDN supportent correctement CORS et CORP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-10-supprimer-x-powered-by-et-masquer-les-informations-serveur\">\u00c9tape 10 : Supprimer X-Powered-By et masquer les informations serveur<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;en-t\u00eate <code>X-Powered-By: Express<\/code> r\u00e9v\u00e8le aux attaquants la technologie utilis\u00e9e, facilitant le ciblage avec des exploits sp\u00e9cifiques \u00e0 Express ou Node.js. Helmet le supprime automatiquement via <code>app.disable('x-powered-by')<\/code>. Mais d&#8217;autres en-t\u00eates peuvent \u00e9galement r\u00e9v\u00e9ler des informations sur votre infrastructure :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Helmet supprime X-Powered-By automatiquement.\n\/\/ Pour supprimer d'autres en-t\u00eates r\u00e9v\u00e9lateurs :\napp.use((req, res, next) => {\n  \/\/ Supprimer les en-t\u00eates qui r\u00e9v\u00e8lent l'infrastructure\n  res.removeHeader('Server');\n  res.removeHeader('X-AspNet-Version');\n  res.removeHeader('X-AspNetMvc-Version');\n  next();\n});\n\n\/\/ V\u00e9rification post-configuration :\n\/\/ Aucun de ces en-t\u00eates ne doit appara\u00eetre en production\nconst forbiddenHeaders = ['x-powered-by', 'server', 'x-aspnet-version'];\napp.use((req, res, next) => {\n  const originalEnd = res.end;\n  res.end = function(...args) {\n    forbiddenHeaders.forEach(h => {\n      if (res.getHeader(h)) {\n        console.warn(`[SECURITE] En-t\u00eate r\u00e9v\u00e9lateur d\u00e9tect\u00e9: ${h}`);\n      }\n    });\n    originalEnd.apply(this, args);\n  };\n  next();\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour les applications derri\u00e8re un proxy inverse nginx, configurez \u00e9galement nginx pour masquer sa version :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># nginx.conf\nserver_tokens off;  # Supprime la version dans l'en-t\u00eate Server\n# Avec le module ngx_headers_more (si disponible) :\n# more_clear_headers Server;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-11-configuration-complete-pour-la-production\">\u00c9tape 11 : Configuration compl\u00e8te pour la production<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En production, la configuration Helmet doit \u00eatre plus stricte qu&#8217;en d\u00e9veloppement. Voici une configuration compl\u00e8te et annot\u00e9e, s\u00e9parant les param\u00e8tres de d\u00e9veloppement et de production :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\n\nconst app = express();\nconst isProduction = process.env.NODE_ENV === 'production';\n\n\/\/ Configuration Helmet unifi\u00e9e dev\/prod\napp.use(\n  helmet({\n    \/\/ Content-Security-Policy : plus strict en prod, mode rapport en dev\n    contentSecurityPolicy: isProduction\n      ? {\n          directives: {\n            defaultSrc: [\"'self'\"],\n            scriptSrc: [\"'self'\"],\n            scriptSrcAttr: [\"'none'\"],\n            styleSrc: [\"'self'\", 'https:'],\n            imgSrc: [\"'self'\", 'data:', 'https:'],\n            fontSrc: [\"'self'\", 'https:'],\n            connectSrc: [\"'self'\"],\n            frameSrc: [\"'none'\"],\n            objectSrc: [\"'none'\"],\n            baseUri: [\"'self'\"],\n            formAction: [\"'self'\"],\n            frameAncestors: [\"'none'\"],\n            upgradeInsecureRequests: [],\n            reportUri: '\/csp-violations',\n          },\n        }\n      : {\n          reportOnly: true, \/\/ Mode observation uniquement en d\u00e9veloppement\n          directives: {\n            defaultSrc: [\"'self'\"],\n            scriptSrc: [\"'self'\", \"'unsafe-inline'\"], \/\/ Plus permissif en dev\n            styleSrc: [\"'self'\", 'https:', \"'unsafe-inline'\"],\n            imgSrc: [\"'self'\", 'data:', 'https:', 'http:'],\n            reportUri: '\/csp-violations',\n          },\n        },\n\n    \/\/ HSTS : activ\u00e9 uniquement en production (HTTPS requis)\n    hsts: isProduction\n      ? {\n          maxAge: 31536000,\n          includeSubDomains: true,\n          preload: true,\n        }\n      : false,\n\n    \/\/ Anti-clickjacking : DENY pour pages sensibles, SAMEORIGIN par d\u00e9faut\n    frameguard: { action: isProduction ? 'deny' : 'sameorigin' },\n\n    \/\/ Toujours actifs en dev et prod\n    noSniff: true,\n    referrerPolicy: { policy: 'strict-origin-when-cross-origin' },\n    dnsPrefetchControl: { allow: false },\n    originAgentCluster: true,\n    permittedCrossDomainPolicies: { permittedPolicies: 'none' },\n    xssFilter: false, \/\/ D\u00e9sactiv\u00e9 intentionnellement (obsol\u00e8te et dangereux)\n\n    \/\/ Cross-Origin : actifs en prod, d\u00e9sactivables en dev si besoin\n    crossOriginEmbedderPolicy: isProduction ? { policy: 'require-corp' } : false,\n    crossOriginOpenerPolicy: { policy: 'same-origin' },\n    crossOriginResourcePolicy: { policy: isProduction ? 'same-origin' : 'cross-origin' },\n  })\n);\n\n\/\/ Permissions-Policy\napp.use((req, res, next) => {\n  res.setHeader(\n    'Permissions-Policy',\n    'camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=()'\n  );\n  next();\n});\n\n\/\/ Endpoint de rapport CSP\napp.post(\n  '\/csp-violations',\n  express.json({ type: 'application\/csp-report' }),\n  (req, res) => {\n    if (req.body?.['csp-report']) {\n      console.warn('[CSP Violation]', JSON.stringify(req.body['csp-report']));\n    }\n    res.status(204).end();\n  }\n);\n\napp.get('\/api\/health', (req, res) => {\n  res.json({ status: 'ok', env: process.env.NODE_ENV });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => {\n  console.log(`Serveur ${isProduction ? 'PRODUCTION' : 'DEV'} d\u00e9marr\u00e9 sur le port ${PORT}`);\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-12-tester-et-valider-vos-en-tetes-de-securite\">\u00c9tape 12 : Tester et valider vos en-t\u00eates de s\u00e9curit\u00e9<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/observatory.mozilla.org\/\" rel=\"noopener noreferrer\" target=\"_blank\">Mozilla Observatory<\/a> est l&#8217;outil de r\u00e9f\u00e9rence pour \u00e9valuer la qualit\u00e9 de vos en-t\u00eates de s\u00e9curit\u00e9. Il attribue un score de A+ \u00e0 F en analysant la pr\u00e9sence et la configuration de chaque en-t\u00eate. Une configuration Helmet correcte vise au minimum un score B, id\u00e9alement A ou A+.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour tester votre application en local, utilisez d&#8217;abord <code>curl<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># V\u00e9rifier la pr\u00e9sence de tous les en-t\u00eates de s\u00e9curit\u00e9\ncurl -I --silent https:\/\/votre-domaine.com | grep -iE \\\n  \"content-security|strict-transport|x-frame|x-content-type|referrer|permissions|cross-origin\"\n\n# Sortie attendue en production :\n# content-security-policy: default-src 'self';...\n# strict-transport-security: max-age=31536000; includeSubDomains; preload\n# x-frame-options: DENY\n# x-content-type-options: nosniff\n# referrer-policy: strict-origin-when-cross-origin\n# permissions-policy: camera=(), microphone=(), geolocation=()\n# cross-origin-embedder-policy: require-corp\n# cross-origin-opener-policy: same-origin\n# cross-origin-resource-policy: same-origin<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour des tests automatis\u00e9s dans votre pipeline CI\/CD, utilisez <code>supertest<\/code> avec Jest :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install --save-dev supertest jest<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ tests\/security-headers.test.js\nconst request = require('supertest');\nconst { app, server } = require('..\/app');\n\ndescribe('En-t\u00eates de s\u00e9curit\u00e9 HTTP', () => {\n  let response;\n\n  beforeAll(async () => {\n    process.env.NODE_ENV = 'production';\n    response = await request(app).get('\/api\/health');\n  });\n\n  afterAll(() => server.close());\n\n  test('Content-Security-Policy pr\u00e9sent et strict', () => {\n    const csp = response.headers['content-security-policy'];\n    expect(csp).toBeDefined();\n    expect(csp).toContain(\"default-src 'self'\");\n    expect(csp).not.toContain(\"'unsafe-eval'\");\n  });\n\n  test('X-Frame-Options: DENY', () => {\n    expect(response.headers['x-frame-options']).toBe('DENY');\n  });\n\n  test('X-Content-Type-Options: nosniff', () => {\n    expect(response.headers['x-content-type-options']).toBe('nosniff');\n  });\n\n  test('HSTS avec max-age 1 an minimum', () => {\n    const hsts = response.headers['strict-transport-security'];\n    expect(hsts).toContain('max-age=31536000');\n    expect(hsts).toContain('includeSubDomains');\n  });\n\n  test('Referrer-Policy configur\u00e9', () => {\n    expect(response.headers['referrer-policy']).toBe('strict-origin-when-cross-origin');\n  });\n\n  test('X-Powered-By absent', () => {\n    expect(response.headers['x-powered-by']).toBeUndefined();\n  });\n\n  test('Permissions-Policy pr\u00e9sent', () => {\n    const pp = response.headers['permissions-policy'];\n    expect(pp).toContain('camera=()');\n    expect(pp).toContain('microphone=()');\n  });\n\n  test('Cross-Origin-Opener-Policy configur\u00e9', () => {\n    expect(response.headers['cross-origin-opener-policy']).toBe('same-origin');\n  });\n});<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>NODE_ENV=production npx jest tests\/security-headers.test.js --verbose<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie attendue avec tous les tests passants :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PASS tests\/security-headers.test.js\n  En-t\u00eates de s\u00e9curit\u00e9 HTTP\n    \u2713 Content-Security-Policy pr\u00e9sent et strict (8ms)\n    \u2713 X-Frame-Options: DENY (1ms)\n    \u2713 X-Content-Type-Options: nosniff (1ms)\n    \u2713 HSTS avec max-age 1 an minimum (1ms)\n    \u2713 Referrer-Policy configur\u00e9 (1ms)\n    \u2713 X-Powered-By absent (1ms)\n    \u2713 Permissions-Policy pr\u00e9sent (1ms)\n    \u2713 Cross-Origin-Opener-Policy configur\u00e9 (1ms)\n\nTest Suites: 1 passed, 1 total\nTests:       8 passed, 8 total\nTime:        1.234s<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-pieges-courants-et-comment-les-eviter\">5 pi\u00e8ges courants et comment les \u00e9viter<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ces erreurs apparaissent dans la majorit\u00e9 des projets Node.js que nous avons audit\u00e9s. Chacune peut annuler l&#8217;effet des en-t\u00eates de s\u00e9curit\u00e9 m\u00eame quand Helmet est correctement install\u00e9.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"piege-1-placer-helmet-apres-les-routes\">Pi\u00e8ge 1 : Placer Helmet apr\u00e8s les routes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Helmet doit imp\u00e9rativement \u00eatre enregistr\u00e9 <strong>avant<\/strong> toutes les routes. S&#8217;il est plac\u00e9 apr\u00e8s, les r\u00e9ponses g\u00e9n\u00e9r\u00e9es par ces routes ne contiendront pas les en-t\u00eates de s\u00e9curit\u00e9.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ INCORRECT : Helmet apr\u00e8s les routes - les routes ci-dessus ne sont pas s\u00e9curis\u00e9es\napp.get('\/', (req, res) => res.send('Hello, monde!'));\napp.use(helmet()); \/\/ Trop tard, ne couvre pas les routes d\u00e9finies avant\n\n\/\/ CORRECT : Helmet avant toutes les routes\napp.use(helmet());\napp.get('\/', (req, res) => res.send('Hello, monde!'));<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"piege-2-csp-avec-unsafe-inline-dans-script-src\">Pi\u00e8ge 2 : CSP avec &#8216;unsafe-inline&#8217; dans script-src<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La valeur <code>'unsafe-inline'<\/code> dans <code>script-src<\/code> annule enti\u00e8rement la protection XSS du CSP. Elle permet l&#8217;ex\u00e9cution de tout script inline, pr\u00e9cis\u00e9ment ce que le CSP est cens\u00e9 bloquer. Un attaquant qui r\u00e9ussit \u00e0 injecter du HTML peut ex\u00e9cuter n&#8217;importe quel script.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ DANGEREUX : annule toute protection XSS\nscriptSrc: [\"'self'\", \"'unsafe-inline'\"], \/\/ Ne jamais utiliser en production\n\n\/\/ CORRECT : utiliser des nonces g\u00e9n\u00e9r\u00e9s dynamiquement\nconst crypto = require('crypto');\n\napp.use((req, res, next) => {\n  res.locals.nonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\n\/\/ Dans Helmet, utiliser le nonce g\u00e9n\u00e9r\u00e9\napp.use((req, res, next) => {\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        scriptSrc: [\"'self'\", `'nonce-${res.locals.nonce}'`],\n      },\n    },\n  })(req, res, next);\n});\n\n\/\/ Dans votre template HTML :\n\/\/ &lt;script nonce=\"&lt;%= nonce %&gt;\"&gt;\/* code inline s\u00e9curis\u00e9 *\/&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"piege-3-activer-hsts-sur-un-serveur-sans-https-valide\">Pi\u00e8ge 3 : Activer HSTS sur un serveur sans HTTPS valide<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Si vous activez HSTS sur un serveur sans certificat SSL valide (ou avec un certificat expir\u00e9), les navigateurs refuseront toute connexion pendant toute la dur\u00e9e de <code>maxAge<\/code>, sans possibilit\u00e9 de contournement. L&#8217;utilisateur voit <code>ERR_SSL_PROTOCOL_ERROR<\/code> et ne peut plus acc\u00e9der au site.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ DANGEREUX si HTTPS non fonctionnel\nhsts: { maxAge: 31536000, includeSubDomains: true, preload: true }\n\n\/\/ APPROCHE GRADUELLE recommand\u00e9e :\n\/\/ Semaine 1 : 1 jour, v\u00e9rification que tout fonctionne\nhsts: { maxAge: 86400 },\n\/\/ Semaine 2 : 1 mois\nhsts: { maxAge: 2592000 },\n\/\/ Mois 2 : 6 mois\nhsts: { maxAge: 15768000 },\n\/\/ Mois 3+ : 1 an + preload\nhsts: { maxAge: 31536000, includeSubDomains: true, preload: true }<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"piege-4-cors-wildcard-avec-credentials\">Pi\u00e8ge 4 : CORS wildcard avec credentials<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Un CORS configur\u00e9 avec <code>origin: '*'<\/code> et <code>credentials: true<\/code> est rejet\u00e9 par la sp\u00e9cification CORS et par les navigateurs modernes. Mais des configurations partiellement incorrectes peuvent cr\u00e9er des br\u00e8ches de s\u00e9curit\u00e9 r\u00e9elles.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const cors = require('cors');\n\n\/\/ INCORRECT : incompatible et potentiellement dangereux\napp.use(cors({ origin: '*', credentials: true }));\n\n\/\/ CORRECT : liste explicite et restrictive des origines\nconst allowedOrigins = [\n  'https:\/\/monapp.com',\n  'https:\/\/www.monapp.com',\n  process.env.NODE_ENV !== 'production' ? 'http:\/\/localhost:3000' : null,\n].filter(Boolean);\n\napp.use(cors({\n  origin: (origin, callback) => {\n    if (!origin || allowedOrigins.includes(origin)) {\n      callback(null, true);\n    } else {\n      callback(new Error(`Origine CORS non autoris\u00e9e: ${origin}`));\n    }\n  },\n  credentials: true,\n  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],\n  allowedHeaders: ['Content-Type', 'Authorization'],\n}));<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"piege-5-coep-qui-bloque-les-ressources-cdn\">Pi\u00e8ge 5 : COEP qui bloque les ressources CDN<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>Cross-Origin-Embedder-Policy: require-corp<\/code> exige que toutes les ressources tierces renvoient l&#8217;en-t\u00eate <code>Cross-Origin-Resource-Policy<\/code>. La plupart des CDN ne le font pas encore. R\u00e9sultat : images, scripts et styles CDN sont bloqu\u00e9s silencieusement, cassant l&#8217;apparence ou le fonctionnement de votre application.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Si vous utilisez des ressources CDN : d\u00e9sactiver COEP temporairement\napp.use(\n  helmet({\n    crossOriginEmbedderPolicy: false, \/\/ D\u00e9sactiv\u00e9 jusqu'\u00e0 ce que le CDN supporte CORP\n    \/\/ Ou utiliser 'unsafe-none' :\n    \/\/ crossOriginEmbedderPolicy: { policy: 'unsafe-none' },\n  })\n);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"guide-de-depannage-8-problemes-frequents\">Guide de d\u00e9pannage : 8 probl\u00e8mes fr\u00e9quents<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Probl\u00e8me<\/th><th>Sympt\u00f4me visible<\/th><th>Solution<\/th><\/tr><\/thead><tbody><tr><td>Polices Google Fonts bloqu\u00e9es<\/td><td>Console: &#8220;Refused to load font from &#8216;https:\/\/fonts.gstatic.com'&#8221;<\/td><td>Ajouter <code>'https:\/\/fonts.gstatic.com'<\/code> \u00e0 fontSrc et <code>'https:\/\/fonts.googleapis.com'<\/code> \u00e0 styleSrc<\/td><\/tr><tr><td>Scripts CDN (jQuery, Bootstrap) bloqu\u00e9s<\/td><td>Console: &#8220;Refused to load script from cdn.jsdelivr.net&#8221;<\/td><td>Ajouter le domaine CDN \u00e0 scriptSrc<\/td><\/tr><tr><td>Images h\u00e9berg\u00e9es en externe absentes<\/td><td>Images remplac\u00e9es par carr\u00e9s vides, erreur CSP console<\/td><td>Ajouter le domaine d&#8217;h\u00e9bergement \u00e0 imgSrc<\/td><\/tr><tr><td>Appels API cross-domain bloqu\u00e9s<\/td><td>fetch() \u00e9choue avec erreur CORS ou CSP<\/td><td>Ajouter le domaine API \u00e0 connectSrc<\/td><\/tr><tr><td>iframe partenaire (YouTube, Calendly) bloqu\u00e9e<\/td><td>iframe vide, erreur X-Frame-Options dans console<\/td><td>Utiliser frame-ancestors CSP pour autoriser l&#8217;origine sp\u00e9cifique<\/td><\/tr><tr><td>HSTS bloque l&#8217;acc\u00e8s HTTP en d\u00e9veloppement<\/td><td>ERR_SSL_PROTOCOL_ERROR sur localhost<\/td><td>D\u00e9sactiver hsts en d\u00e9veloppement : <code>hsts: false<\/code><\/td><\/tr><tr><td>Stripe.js ou PayPal SDK bloqu\u00e9<\/td><td>Formulaire de paiement non charg\u00e9<\/td><td>Ajouter <code>'https:\/\/js.stripe.com'<\/code> \u00e0 scriptSrc et frameSrc<\/td><\/tr><tr><td>Google Analytics bloqu\u00e9<\/td><td>Pas de donn\u00e9es analytics, erreur CSP<\/td><td>Ajouter <code>'https:\/\/www.googletagmanager.com'<\/code> \u00e0 scriptSrc et connectSrc<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Pour les int\u00e9grations tierces complexes (Stripe, Intercom, Crisp, HubSpot, etc.), la m\u00e9thode la plus fiable est :<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Activer CSP en mode <code>reportOnly: true<\/code><\/li><li>Observer les violations pendant 2 \u00e0 3 jours en conditions r\u00e9elles<\/li><li>Ajouter les domaines violateurs l\u00e9gitimes dans les directives appropri\u00e9es<\/li><li>V\u00e9rifier qu&#8217;il ne reste que des violations d&#8217;origines r\u00e9ellement externes et non sollicit\u00e9es<\/li><li>Passer en mode enforcement (<code>reportOnly: false<\/code>)<\/li><li>Continuer \u00e0 surveiller l&#8217;endpoint <code>\/csp-violations<\/code> en production<\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conseils-avances-pour-la-securite-en-production\">Conseils avanc\u00e9s pour la s\u00e9curit\u00e9 en production<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Nonces CSP dynamiques par requ\u00eate.<\/strong> G\u00e9n\u00e9rez un nonce unique pour chaque requ\u00eate HTTP et injectez-le dans vos scripts inline et templates HTML. C&#8217;est la seule m\u00e9thode permettant des scripts inline sans <code>'unsafe-inline'<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const crypto = require('crypto');\n\n\/\/ Middleware nonce : doit \u00eatre avant Helmet\napp.use((req, res, next) => {\n  res.locals.nonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\n\/\/ Helmet utilise le nonce de la requ\u00eate courante\napp.use((req, res, next) => {\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc: [\"'self'\"],\n        scriptSrc: [\"'self'\", `'nonce-${res.locals.nonce}'`],\n        styleSrc: [\"'self'\", `'nonce-${res.locals.nonce}'`],\n      },\n    },\n  })(req, res, next);\n});\n\n\/\/ Dans EJS :\n\/\/ &lt;script nonce=\"&lt;%= nonce %&gt;\"&gt;alert('script s\u00e9curis\u00e9');&lt;\/script&gt;\n\/\/ Dans Pug :\n\/\/ script(nonce=nonce) alert('script s\u00e9curis\u00e9');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>En-t\u00eates diff\u00e9renci\u00e9s par route selon la sensibilit\u00e9.<\/strong> Toutes les routes n&#8217;exigent pas le m\u00eame niveau de protection. Appliquez une politique plus stricte sur les zones sensibles :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ S\u00e9curit\u00e9 standard pour le dashboard\nconst standardHelmet = helmet({ frameguard: { action: 'sameorigin' } });\n\n\/\/ Haute s\u00e9curit\u00e9 pour les zones critiques\nconst criticalHelmet = [\n  helmet({ frameguard: { action: 'deny' } }),\n  (req, res, next) => {\n    \/\/ Emp\u00eacher la mise en cache des pages sensibles\n    res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');\n    res.setHeader('Pragma', 'no-cache');\n    res.setHeader('Expires', '0');\n    next();\n  },\n];\n\napp.get('\/dashboard', standardHelmet, dashboardController);\napp.get('\/login', criticalHelmet, loginController);\napp.post('\/transfer', criticalHelmet, transferController);\napp.get('\/admin', criticalHelmet, adminController);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Script de v\u00e9rification CI\/CD.<\/strong> Automatisez le contr\u00f4le des en-t\u00eates \u00e0 chaque d\u00e9ploiement pour \u00e9viter les r\u00e9gressions :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# scripts\/check-security-headers.sh\n# Usage : .\/check-security-headers.sh https:\/\/votre-domaine.com\n\nURL=\"${1:-https:\/\/votre-domaine.com}\"\nREQUIRED=(\n  \"content-security-policy\"\n  \"strict-transport-security\"\n  \"x-frame-options\"\n  \"x-content-type-options\"\n  \"referrer-policy\"\n  \"permissions-policy\"\n  \"cross-origin-opener-policy\"\n)\nFORBIDDEN=(\"x-powered-by\" \"x-aspnet-version\")\n\necho \"V\u00e9rification des en-t\u00eates de s\u00e9curit\u00e9 pour $URL\"\nHEADERS=$(curl -sI --max-time 10 \"$URL\" | tr '[:upper:]' '[:lower:]')\nFAILED=0\n\nfor h in \"${REQUIRED[@]}\"; do\n  if echo \"$HEADERS\" | grep -q \"^$h:\"; then\n    echo \"OK  $h\"\n  else\n    echo \"ERR MANQUANT: $h\"\n    FAILED=1\n  fi\ndone\n\nfor h in \"${FORBIDDEN[@]}\"; do\n  if echo \"$HEADERS\" | grep -q \"^$h:\"; then\n    echo \"ERR R\u00c9V\u00c9LATEUR D\u00c9TECT\u00c9: $h\"\n    FAILED=1\n  fi\ndone\n\n[ \"$FAILED\" -eq 0 ] && echo \"SUCC\u00c8S: Configuration s\u00e9curis\u00e9e\" && exit 0\necho \"ECHEC: Corrigez les probl\u00e8mes ci-dessus\" && exit 1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour aller plus loin dans la s\u00e9curisation de vos APIs, consultez notre guide <a href=\"\/owasp-top-10-nodejs\/\">OWASP Top 10 Node.js : 12 \u00e9tapes<\/a> qui couvre les 10 vuln\u00e9rabilit\u00e9s critiques au-del\u00e0 des en-t\u00eates, et notre tutoriel <a href=\"\/oauth2-nodejs\/\">OAuth2 en Node.js : 12 \u00e9tapes<\/a> pour l&#8217;authentification s\u00e9curis\u00e9e. La gestion s\u00e9curis\u00e9e des mots de passe est trait\u00e9e dans notre guide <a href=\"\/bcrypt-nodejs-hachage-mot-de-passe\/\">bcrypt en Node.js<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"projet-complet-structure-de-fichiers\">Projet complet : structure de fichiers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Voici la structure compl\u00e8te du projet avec tous les fichiers \u00e9voqu\u00e9s dans ce tutoriel :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node-security-headers\/\n\u251c\u2500\u2500 app.js                         # Point d'entr\u00e9e de l'application\n\u251c\u2500\u2500 config\/\n\u2502   \u2514\u2500\u2500 helmet.config.js           # Configuration Helmet centralis\u00e9e dev\/prod\n\u251c\u2500\u2500 middleware\/\n\u2502   \u251c\u2500\u2500 security.js                # Middlewares de s\u00e9curit\u00e9 personnalis\u00e9s\n\u2502   \u251c\u2500\u2500 csp-nonce.js               # G\u00e9n\u00e9ration des nonces CSP\n\u2502   \u2514\u2500\u2500 permissions-policy.js      # En-t\u00eate Permissions-Policy\n\u251c\u2500\u2500 routes\/\n\u2502   \u251c\u2500\u2500 api.js                     # Routes API prot\u00e9g\u00e9es\n\u2502   \u2514\u2500\u2500 csp-report.js              # Endpoint de rapport de violations CSP\n\u251c\u2500\u2500 tests\/\n\u2502   \u2514\u2500\u2500 security-headers.test.js   # Tests automatis\u00e9s des en-t\u00eates\n\u251c\u2500\u2500 scripts\/\n\u2502   \u2514\u2500\u2500 check-security-headers.sh  # Script de v\u00e9rification CI\/CD\n\u251c\u2500\u2500 .env.example                   # Variables d'environnement requises\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># .env.example\nNODE_ENV=production\nPORT=3000\nCSP_REPORT_URI=\/csp-violations\nALLOWED_ORIGINS=https:\/\/monapp.com,https:\/\/www.monapp.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;int\u00e9gralit\u00e9 du code source est accessible sur <a href=\"https:\/\/github.com\/helmetjs\/helmet\" rel=\"noopener noreferrer\" target=\"_blank\">le d\u00e9p\u00f4t officiel de Helmet.js<\/a> pour r\u00e9f\u00e9rence sur les options disponibles par version.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"couverture-connexe\">Couverture connexe<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pour compl\u00e9ter la s\u00e9curisation de vos applications Node.js, ces ressources couvrent les aspects compl\u00e9mentaires aux en-t\u00eates HTTP :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/owasp-top-10-nodejs\/\">OWASP Top 10 Node.js : S\u00e9curisez votre API en 12 \u00e9tapes<\/a> &#8211; les 10 vuln\u00e9rabilit\u00e9s les plus critiques selon l&#8217;OWASP<\/li><li><a href=\"\/authentification-jwt-nodejs\/\">Authentification JWT en Node.js : 12 \u00e9tapes<\/a> &#8211; s\u00e9curiser l&#8217;authentification avec JSON Web Tokens<\/li><li><a href=\"\/oauth2-nodejs\/\">OAuth2 en Node.js : 12 \u00e9tapes, 30 min<\/a> &#8211; d\u00e9l\u00e9gation d&#8217;autorisation et int\u00e9gration tierce<\/li><li><a href=\"\/openssl-cles-certificats-tutoriel\/\">OpenSSL : cl\u00e9s et certificats en 12 \u00e9tapes<\/a> &#8211; infrastructure \u00e0 cl\u00e9 publique et TLS<\/li><li><a href=\"\/tls-1-3-vs-tls-1-2\/\">TLS 1.3 vs TLS 1.2 : 40% plus rapide, 5 CVE<\/a> &#8211; choisir la bonne version TLS pour votre serveur<\/li><li><a href=\"\/bcrypt-nodejs-hachage-mot-de-passe\/\">bcrypt en Node.js : hachage de mots de passe<\/a> &#8211; stocker les mots de passe de mani\u00e8re s\u00e9curis\u00e9e<\/li><li><a href=\"\/hmac-sha256-nodejs\/\">HMAC-SHA256 en Node.js : 10 \u00e9tapes<\/a> &#8211; int\u00e9grit\u00e9 des messages et signatures l\u00e9g\u00e8res<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq-en-tetes-de-securite-http-en-node-js\">FAQ : En-t\u00eates de s\u00e9curit\u00e9 HTTP en Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"helmet-js-est-il-suffisant-pour-securiser-une-application-node-js\">Helmet.js est-il suffisant pour s\u00e9curiser une application Node.js ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non. Helmet g\u00e8re les en-t\u00eates HTTP de s\u00e9curit\u00e9, ce qui constitue une couche de d\u00e9fense parmi d&#8217;autres. La s\u00e9curit\u00e9 compl\u00e8te d&#8217;une application Node.js couvre \u00e9galement : l&#8217;authentification (JWT, sessions), la validation et l&#8217;\u00e9chappement des entr\u00e9es utilisateurs, la protection contre les injections (SQL, NoSQL, commandes), la gestion s\u00e9curis\u00e9e des d\u00e9pendances npm, la configuration du serveur, et la surveillance des logs. Les en-t\u00eates de s\u00e9curit\u00e9 sont indispensables mais ne remplacent pas la s\u00e9curit\u00e9 applicative.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"helmet-ralentit-il-les-performances-de-lapplication\">Helmet ralentit-il les performances de l&#8217;application ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;impact sur les performances est n\u00e9gligeable. Helmet ne fait qu&#8217;ajouter des cha\u00eenes de caract\u00e8res aux en-t\u00eates HTTP des r\u00e9ponses. Il ne r\u00e9alise aucune op\u00e9ration cryptographique, aucune requ\u00eate en base de donn\u00e9es, et aucun traitement CPU significatif. Le surco\u00fbt est inf\u00e9rieur \u00e0 0,1 ms par requ\u00eate sur du mat\u00e9riel standard. Le gain en s\u00e9curit\u00e9 est sans commune mesure avec ce co\u00fbt minimal.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"comment-gerer-le-csp-avec-un-framework-frontend-react-vue-angular\">Comment g\u00e9rer le CSP avec un framework frontend (React, Vue, Angular) ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Les frameworks frontend modernes compilent g\u00e9n\u00e9ralement le code en bundles statiques sans scripts inline, ce qui facilite la compatibilit\u00e9 avec un CSP strict. Les probl\u00e8mes courants viennent des polyfills inline inject\u00e9s par les bundlers. L&#8217;approche recommand\u00e9e est d&#8217;utiliser des nonces CSP inject\u00e9s dans l&#8217;HTML lors du rendu (SSR avec Next.js, Nuxt, etc.), ou des hashes CSP pour les scripts inline connus et statiques. Pour les SPA pures (sans SSR), la CSP doit \u00eatre configur\u00e9e au niveau du serveur statique (nginx ou Express static).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quelle-est-la-difference-entre-x-frame-options-et-frame-ancestors-csp\">Quelle est la diff\u00e9rence entre X-Frame-Options et frame-ancestors CSP ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>frame-ancestors<\/code> dans la CSP est la m\u00e9thode moderne : elle supporte plusieurs origines autoris\u00e9es simultan\u00e9ment et des patterns wildcards. <code>X-Frame-Options<\/code> est plus ancien, plus simple, et support\u00e9 par des navigateurs tr\u00e8s anciens qui ne comprennent pas encore la CSP. Helmet active les deux par d\u00e9faut pour une compatibilit\u00e9 maximale. En cas de conflit entre les deux en-t\u00eates, <code>frame-ancestors<\/code> a la priorit\u00e9 dans les navigateurs qui le supportent.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"dois-je-activer-hsts-si-mon-application-est-derriere-un-proxy-inverse\">Dois-je activer HSTS si mon application est derri\u00e8re un proxy inverse ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">HSTS doit \u00eatre \u00e9mis par le composant qui g\u00e8re la connexion TLS avec le client, c&#8217;est-\u00e0-dire votre proxy inverse (nginx, Apache, Cloudflare) dans la majorit\u00e9 des d\u00e9ploiements en production. Si votre proxy g\u00e8re le TLS et communique avec Node.js en HTTP interne, configurez HSTS dans nginx et d\u00e9sactivez-le dans Helmet avec <code>hsts: false<\/code>. Si Node.js g\u00e8re directement le TLS (rare en production), utilisez Helmet pour HSTS. \u00c9vitez d&#8217;avoir HSTS configur\u00e9 aux deux niveaux simultan\u00e9ment.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"comment-tester-mes-en-tetes-sans-deployer-en-production\">Comment tester mes en-t\u00eates sans d\u00e9ployer en production ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Utilisez <code>curl -I http:\/\/localhost:3000<\/code> pour v\u00e9rifier les en-t\u00eates en local, supertest pour les tests automatis\u00e9s dans votre pipeline CI\/CD, et ngrok ou Cloudflare Tunnel pour exposer temporairement votre serveur local \u00e0 des outils d&#8217;analyse en ligne. Mozilla Observatory et d&#8217;autres scanners n\u00e9cessitent un domaine public, mais peuvent \u00eatre utilis\u00e9s avec des domaines de staging avant chaque mise en production.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"que-faire-si-helmet-casse-mon-application-existante\">Que faire si Helmet casse mon application existante ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">D\u00e9sactivez d&#8217;abord le CSP (<code>contentSecurityPolicy: false<\/code>) et activez-le progressivement en mode <code>reportOnly: true<\/code>. Activez ensuite les autres en-t\u00eates un par un en v\u00e9rifiant les effets. Le coupable le plus fr\u00e9quent est COEP (<code>crossOriginEmbedderPolicy<\/code>) qui bloque les ressources CDN tierces. Si votre application embarque beaucoup de ressources externes, d\u00e9sactivez COEP temporairement avec <code>crossOriginEmbedderPolicy: false<\/code> et r\u00e9activez-le progressivement quand vos fournisseurs CDN supportent CORP.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"les-en-tetes-de-securite-protegent-ils-contre-les-attaques-xss-cote-serveur\">Les en-t\u00eates de s\u00e9curit\u00e9 prot\u00e8gent-ils contre les attaques XSS c\u00f4t\u00e9 serveur ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non, pas directement. Le CSP emp\u00eache l&#8217;ex\u00e9cution de code malveillant dans le navigateur du client, mais une injection r\u00e9ussie dans votre base de donn\u00e9es, vos fichiers de template ou vos emails reste possible sans validation serveur. Le CSP est une derni\u00e8re ligne de d\u00e9fense c\u00f4t\u00e9 navigateur. Pour une protection compl\u00e8te contre le XSS, combinez : validation des entr\u00e9es c\u00f4t\u00e9 serveur, \u00e9chappement contextuel des sorties dans les templates, et CSP strict. La validation HMAC des donn\u00e9es sensibles, trait\u00e9e dans notre guide <a href=\"\/hmac-sha256-nodejs\/\">HMAC-SHA256 en Node.js<\/a>, compl\u00e8te cette d\u00e9fense en profondeur.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Les en-t\u00eates de s\u00e9curit\u00e9 HTTP constituent la premi\u00e8re ligne de d\u00e9fense d&#8217;une application web. Pourtant, selon l&#8217;OWASP Top 10, la mauvaise configuration de s\u00e9curit\u00e9 (A05) touche la majorit\u00e9 des applications\u2026<\/p>\n","protected":false},"author":7,"featured_media":234,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,3],"tags":[],"class_list":["post-233","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-10","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/233","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/comments?post=233"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/233\/revisions"}],"predecessor-version":[{"id":235,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/233\/revisions\/235"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media\/234"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media?parent=233"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/categories?post=233"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/tags?post=233"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}