{"id":245,"date":"2026-06-19T08:00:00","date_gmt":"2026-06-19T08:00:00","guid":{"rendered":"https:\/\/shattered.io\/it\/2026\/06\/19\/xss-prevention-nodejs\/"},"modified":"2026-06-19T17:02:13","modified_gmt":"2026-06-19T17:02:13","slug":"xss-prevention-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/it\/2026\/06\/19\/xss-prevention-nodejs\/","title":{"rendered":"XSS in Node.js: Prevenirlo in 12 Step [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Il <strong>Cross-Site Scripting (XSS)<\/strong> \u00e8 la vulnerabilit\u00e0 web pi\u00f9 segnalata nel 2025 secondo OWASP, presente nel 68% delle applicazioni testate. Con Node.js e Express.js, un singolo campo di input non protetto \u00e8 sufficiente affinch\u00e9 un aggressore inietti codice JavaScript malevolo, rubi i cookie di sessione degli utenti, esegua azioni per conto loro o installi malware. Questa guida ti mostra come prevenire l&#8217;XSS in Node.js in 12 step pratici, con codice funzionante, i pacchetti npm giusti e una checklist completa per la produzione.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cosa-sono-gli-attacchi-xss-e-perche-node-js-e-a-rischio\">Cosa Sono gli Attacchi XSS e Perch\u00e9 Node.js \u00e8 a Rischio<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un attacco <strong>Cross-Site Scripting (XSS)<\/strong> si verifica quando un aggressore riesce a iniettare codice JavaScript in una pagina web visualizzata da altri utenti. Il browser della vittima esegue quel codice perch\u00e9 lo percepisce come parte legittima della pagina. Il danno pu\u00f2 variare dal furto di cookie di sessione (e quindi dell&#8217;accesso all&#8217;account) alla reindirizzazione su siti di phishing, all&#8217;esecuzione di keylogger nel browser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js e Express.js non sono intrinsecamente pi\u00f9 vulnerabili di altri framework, ma la natura dinamica di JavaScript, la facilit\u00e0 con cui si concatenano stringhe HTML e la mancanza di un sistema di template sicuro di default espongono molti sviluppatori a rischi evitabili. Un tipico errore \u00e8 restituire direttamente all&#8217;utente dati non sanitizzati prelevati dal database o dai parametri della richiesta HTTP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Esistono tre categorie principali di XSS, ciascuna con caratteristiche e vettori di attacco distinti. Comprenderle \u00e8 il primo passo verso una protezione efficace.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Tipo di XSS<\/th><th>Come Funziona<\/th><th>Persistenza<\/th><th>Esempio<\/th><\/tr><\/thead><tbody><tr><td><strong>Stored XSS<\/strong><\/td><td>Il payload viene salvato nel database e servito a tutti gli utenti<\/td><td>Permanente<\/td><td>Commento con &lt;script&gt; in un forum<\/td><\/tr><tr><td><strong>Reflected XSS<\/strong><\/td><td>Il payload viene incluso nella richiesta e rimbalzato nella risposta<\/td><td>Temporanea<\/td><td>Parametro URL con codice malevolo<\/td><\/tr><tr><td><strong>DOM-Based XSS<\/strong><\/td><td>Il payload modifica il DOM lato client tramite JavaScript<\/td><td>Variabile<\/td><td>document.write(location.hash)<\/td><\/tr><tr><td><strong>Blind XSS<\/strong><\/td><td>Il payload viene eseguito in aree non visibili all&#8217;aggressore<\/td><td>Permanente<\/td><td>Campo di log visualizzato dall&#8217;amministratore<\/td><\/tr><tr><td><strong>mXSS (Mutation XSS)<\/strong><\/td><td>Il browser muta il markup sanitizzato in codice eseguibile<\/td><td>Variabile<\/td><td>Parsing anomalo di tag annidati<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisiti-per-seguire-il-tutorial\">Prerequisiti per Seguire il Tutorial<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di iniziare, assicurati di avere installato nel tuo ambiente di sviluppo:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Node.js 20.x LTS<\/strong> o superiore (verificare con <code>node --version<\/code>)<\/li><li><strong>npm 10.x<\/strong> o superiore (verificare con <code>npm --version<\/code>)<\/li><li><strong>Express.js 4.x<\/strong> installato nel progetto<\/li><li>Un editor di testo come VS Code con il plugin ESLint configurato<\/li><li>Conoscenza base di JavaScript e del protocollo HTTP<\/li><li>Terminale con accesso a riga di comando<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Il tutorial usa Node.js 20 LTS perch\u00e9 \u00e8 la versione supportata a lungo termine attiva nel 2026 e include moduli di sicurezza maturi. Puoi seguire le stesse istruzioni su Node.js 22.x senza modifiche sostanziali al codice.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-creare-il-progetto-e-riprodurre-la-vulnerabilita-xss\">Step 1: Creare il Progetto e Riprodurre la Vulnerabilit\u00e0 XSS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di implementare la protezione, \u00e8 utile vedere concretamente come si manifesta una vulnerabilit\u00e0 XSS. Creiamo un&#8217;applicazione Express.js volutamente vulnerabile per capire il problema dal vivo.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir xss-demo &amp;&amp; cd xss-demo\nnpm init -y\nnpm install express ejs\n\n# Struttura del progetto\nxss-demo\/\n\u251c\u2500\u2500 server.js\n\u251c\u2500\u2500 views\/\n\u2502   \u2514\u2500\u2500 index.ejs\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Crea un file <code>server.js<\/code> con un semplice endpoint vulnerabile per vedere l&#8217;attacco in azione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js - VERSIONE VULNERABILE (solo per dimostrazione, non usare in produzione)\nconst express = require('express');\nconst app = express();\n\napp.use(express.urlencoded({ extended: true }));\napp.use(express.json());\n\n\/\/ VULNERABILE: riflette il parametro \"q\" senza sanitizzazione alcuna\napp.get('\/search', (req, res) => {\n  const query = req.query.q || '';\n  \/\/ MAI fare cos\u00ec: concatenare HTML con input utente non sanitizzato\n  res.send(`&lt;h1&gt;Risultati per: ${query}&lt;\/h1&gt;`);\n});\n\n\/\/ VULNERABILE: salva e restituisce commenti HTML senza sanitizzazione\nconst comments = [];\napp.post('\/comment', (req, res) => {\n  comments.push(req.body.text); \/\/ salva HTML grezzo\n  res.json({ ok: true });\n});\n\napp.get('\/comments', (req, res) => {\n  \/\/ MAI fare cos\u00ec: renderizzare HTML grezzo dall'utente\n  res.send(comments.map(c => `&lt;p&gt;${c}&lt;\/p&gt;`).join(''));\n});\n\napp.listen(3000, () => console.log('Server vulnerabile attivo sulla porta 3000'));\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se avvii questo server e visiti <code>http:\/\/localhost:3000\/search?q=&lt;script&gt;alert('XSS')&lt;\/script&gt;<\/code>, vedrai un popup di alert nel browser. Questo dimostra che il codice JavaScript iniettato viene eseguito nel contesto della tua applicazione. Nei passi seguenti correggeremo ogni singola vulnerabilit\u00e0 con strumenti professionali.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-2-installare-le-dipendenze-di-sicurezza-per-la-prevenzione-xss\">Step 2: Installare le Dipendenze di Sicurezza per la Prevenzione XSS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;ecosistema npm offre diversi pacchetti specifici per prevenire l&#8217;XSS in Node.js. Ogni pacchetto ha un ruolo preciso e vanno usati in combinazione per una protezione a livelli.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Installa le dipendenze di sicurezza principali\nnpm install helmet sanitize-html express-validator xss express-session\n\n# Dipendenze di sviluppo per l'analisi statica del codice\nnpm install --save-dev eslint eslint-plugin-security nodemon\n\n# Verifica le vulnerabilit\u00e0 note nelle dipendenze\nnpm audit --audit-level=moderate<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Pacchetto npm<\/th><th>Funzione<\/th><th>Quando Usarlo<\/th><\/tr><\/thead><tbody><tr><td><strong>helmet<\/strong><\/td><td>Imposta le HTTP security headers inclusa la CSP<\/td><td>Sempre, in ogni app Express.js<\/td><\/tr><tr><td><strong>sanitize-html<\/strong><\/td><td>Sanitizza HTML consentendo solo tag sicuri configurabili<\/td><td>Quando si accettano input HTML ricchi (blog, commenti)<\/td><\/tr><tr><td><strong>express-validator<\/strong><\/td><td>Valida e sanitizza i campi della richiesta HTTP<\/td><td>Validazione di form, API e parametri URL<\/td><\/tr><tr><td><strong>xss<\/strong><\/td><td>Filtro XSS basato su whitelist per HTML<\/td><td>Alternativa leggera a sanitize-html<\/td><\/tr><tr><td><strong>express-session<\/strong><\/td><td>Gestione sessioni con cookie sicuri<\/td><td>Applicazioni con autenticazione utente<\/td><\/tr><tr><td><strong>eslint-plugin-security<\/strong><\/td><td>Rileva pattern di codice vulnerabili a XSS durante lo sviluppo<\/td><td>In ogni progetto come controllo statico<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Esegui <code>npm audit<\/code> come parte della pipeline CI\/CD per rilevare vulnerabilit\u00e0 note prima del deploy. GitHub Dependabot e Snyk possono automatizzare questo controllo su ogni pull request, avvisandoti quando viene pubblicata una patch di sicurezza per le dipendenze del progetto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-configurare-helmet-js-con-la-content-security-policy\">Step 3: Configurare Helmet.js con la Content Security Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Helmet.js<\/strong> \u00e8 il middleware Express pi\u00f9 importante per la sicurezza delle intestazioni HTTP. Con una singola invocazione, imposta oltre 11 header che istruiscono il browser su come gestire le risorse della pagina, riducendo drasticamente la superficie d&#8217;attacco per XSS e altri attacchi lato client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La <strong>Content Security Policy (CSP)<\/strong> \u00e8 lo strumento pi\u00f9 efficace contro l&#8217;XSS a livello di browser: definisce esplicitamente quali fonti di script, stili e altri contenuti sono autorizzate. Anche se un aggressore riesce ad iniettare un tag <code>&lt;script&gt;<\/code>, il browser lo blocca se la fonte non \u00e8 nella whitelist CSP. La CSP riduce l&#8217;impatto di XSS anche quando la sanitizzazione fallisce.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js - Configurazione Helmet con CSP basata su nonce\nconst express = require('express');\nconst helmet = require('helmet');\nconst crypto = require('crypto');\n\nconst app = express();\n\n\/\/ Genera un nonce crittograficamente sicuro per ogni richiesta HTTP\napp.use((req, res, next) => {\n  res.locals.nonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\n\/\/ Configurazione Helmet con CSP dinamica basata su nonce\napp.use((req, res, next) => {\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc: [\"'self'\"],\n        scriptSrc: [\n          \"'self'\",\n          \/\/ Consenti solo script con il nonce corretto per questa richiesta\n          (req, res) => `'nonce-${res.locals.nonce}'`,\n        ],\n        styleSrc:    [\"'self'\", \"'unsafe-inline'\"],\n        imgSrc:      [\"'self'\", \"data:\", \"https:\"],\n        connectSrc:  [\"'self'\"],\n        fontSrc:     [\"'self'\"],\n        objectSrc:   [\"'none'\"],     \/\/ blocca plugin Flash e oggetti incorporati\n        frameSrc:    [\"'none'\"],     \/\/ blocca iframe non autorizzati (anti-clickjacking)\n        baseUri:     [\"'self'\"],     \/\/ impedisce injection del tag &lt;base&gt;\n        formAction:  [\"'self'\"],     \/\/ limita le destinazioni dei form\n        upgradeInsecureRequests: [], \/\/ forza HTTPS per le risorse HTTP\n      },\n      reportOnly: false,\n    },\n    xXssProtection: false, \/\/ disabilita l'header obsoleto X-XSS-Protection\n    crossOriginEmbedderPolicy: false,\n  })(req, res, next);\n});\n\napp.use(express.json({ limit: '10kb' }));\napp.use(express.urlencoded({ extended: false, limit: '10kb' }));\n\napp.listen(3000, () => console.log('Server con Helmet attivo sulla porta 3000'));\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Una nota importante: <code>xXssProtection: false<\/code> non \u00e8 un errore di configurazione. L&#8217;header <code>X-XSS-Protection<\/code> \u00e8 stato rimosso da Chrome, Firefox e Edge perch\u00e9 creava nuovi vettori di attacco invece di prevenirli. La documentazione ufficiale di Helmet consiglia esplicitamente di disabilitarlo. La protezione moderna si basa esclusivamente su CSP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-4-validare-linput-con-express-validator\">Step 4: Validare l&#8217;Input con express-validator<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La <strong>validazione dell&#8217;input<\/strong> \u00e8 la prima linea di difesa: rifiuta i dati che non corrispondono al formato atteso prima ancora che raggiungano la logica dell&#8217;applicazione. <code>express-validator<\/code> \u00e8 la libreria standard per Express e si integra perfettamente con i middleware di route.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il principio fondamentale \u00e8 chiaro: non esiste un &#8220;input sicuro&#8221; di default. Ogni dato proveniente dall&#8217;esterno, inclusi parametri URL, header HTTP, cookie e body della richiesta, va trattato come potenzialmente malevolo. Questo vale anche per i dati provenienti da API di terze parti o dal proprio database.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const { body, query, param, validationResult } = require('express-validator');\n\n\/\/ Middleware centralizzato per gestire gli errori di validazione\nconst handleValidation = (req, res, next) => {\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    return res.status(400).json({\n      errors: errors.array().map(e => ({\n        field: e.path,\n        message: e.msg\n      }))\n    });\n  }\n  next();\n};\n\n\/\/ Route con validazione completa per un commento utente\napp.post('\/comment',\n  [\n    body('text')\n      .trim()                              \/\/ rimuovi spazi iniziali e finali\n      .notEmpty().withMessage('Il testo \u00e8 obbligatorio')\n      .isLength({ min: 1, max: 500 }).withMessage('Massimo 500 caratteri')\n      .escape(),                           \/\/ converte &lt;, &gt;, &amp;, \", ' in entit\u00e0 HTML\n\n    body('author')\n      .trim()\n      .notEmpty().withMessage('Il nome \u00e8 obbligatorio')\n      .isAlphanumeric('it-IT', { ignore: ' -' })\n      .withMessage('Solo lettere, numeri, spazi e trattini')\n      .isLength({ min: 2, max: 50 }),\n\n    body('website')\n      .optional()\n      .isURL({ protocols: ['https'], require_tld: true })\n      .withMessage('Solo URL HTTPS validi'), \/\/ blocca javascript: e vbscript:\n  ],\n  handleValidation,\n  (req, res) => {\n    const { text, author } = req.body;\n    \/\/ text \u00e8 gi\u00e0 escapato grazie a .escape()\n    res.json({ ok: true, comment: { text, author } });\n  }\n);\n\n\/\/ Route con validazione del parametro URL numerico\napp.get('\/user\/:id',\n  [\n    param('id')\n      .isInt({ min: 1 }).withMessage('ID non valido')\n      .toInt(),                            \/\/ converte in numero intero sicuro\n  ],\n  handleValidation,\n  (req, res) => {\n    const userId = req.params.id; \/\/ \u00e8 gi\u00e0 un intero, non una stringa\n    res.json({ userId });\n  }\n);\n\n\/\/ Validazione parametri di ricerca con sanitizzazione personalizzata\napp.get('\/search',\n  [\n    query('q')\n      .trim()\n      .isLength({ max: 200 }).withMessage('Ricerca troppo lunga')\n      .customSanitizer(value => value.replace(\/[&lt;&gt;\"'`]\/g, '')),\n  ],\n  handleValidation,\n  (req, res) => {\n    res.json({ results: [], query: req.query.q });\n  }\n);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il metodo <code>.escape()<\/code> di express-validator converte i caratteri speciali HTML in entit\u00e0 sicure: <code>&lt;<\/code> diventa <code>&amp;lt;<\/code>, <code>&gt;<\/code> diventa <code>&amp;gt;<\/code>, <code>&amp;<\/code> diventa <code>&amp;amp;<\/code>. Questo approccio \u00e8 ideale per i campi di testo puro. Se invece devi accettare HTML formattato (grassetto, liste, link), usa <code>sanitize-html<\/code> come mostrato nel passo successivo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-5-sanitizzare-lhtml-con-sanitize-html\">Step 5: Sanitizzare l&#8217;HTML con sanitize-html<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Alcune applicazioni, come blog o editor di contenuti, devono accettare HTML dagli utenti. In questi casi l&#8217;escaping totale non \u00e8 praticabile: rimuoverebbe tutti i tag legittimi come <code>&lt;b&gt;<\/code>, <code>&lt;i&gt;<\/code> o <code>&lt;a&gt;<\/code>. La soluzione \u00e8 la <strong>sanitizzazione<\/strong>: mantieni solo i tag e gli attributi dalla whitelist, rimuovi tutto il resto incluso qualsiasi JavaScript.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const sanitizeHtml = require('sanitize-html');\n\n\/\/ Whitelist permissiva per editor di testo ricco (articoli del blog)\nconst RICH_TEXT_OPTIONS = {\n  allowedTags: [\n    'b', 'i', 'em', 'strong', 'u', 's',\n    'p', 'br', 'ul', 'ol', 'li',\n    'h1', 'h2', 'h3', 'blockquote',\n    'a', 'code', 'pre'\n  ],\n  allowedAttributes: {\n    'a': ['href', 'title', 'rel'],\n    'code': ['class'],\n    'pre': ['class']\n  },\n  allowedSchemes: ['https', 'mailto'],  \/\/ blocca javascript: e vbscript:\n  allowedSchemesByTag: {\n    'a': ['https', 'mailto']\n  },\n  disallowedTagsMode: 'discard',\n  enforceHtmlBoundary: true,\n  transformTags: {\n    'a': (tagName, attribs) => ({\n      tagName: 'a',\n      attribs: {\n        href: attribs.href || '#',\n        title: attribs.title || '',\n        rel: 'noopener noreferrer nofollow',\n        target: '_blank'\n      }\n    })\n  }\n};\n\n\/\/ Whitelist restrittiva per commenti degli utenti\nconst COMMENT_OPTIONS = {\n  allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],\n  allowedAttributes: {},\n  allowedSchemes: [],\n};\n\n\/\/ Route che accetta HTML ricco per articoli\napp.post('\/article',\n  body('content').trim().notEmpty().isLength({ max: 50000 }),\n  handleValidation,\n  (req, res) => {\n    const rawHtml = req.body.content;\n\n    \/\/ Sanitizza prima di salvare nel database\n    const cleanHtml = sanitizeHtml(rawHtml, RICH_TEXT_OPTIONS);\n\n    \/\/ Controlla che la sanitizzazione non abbia prodotto contenuto vuoto\n    if (cleanHtml.trim().length === 0 &amp;&amp; rawHtml.trim().length > 0) {\n      return res.status(400).json({ error: 'Contenuto non valido o interamente rimosso dalla sanitizzazione' });\n    }\n\n    res.json({ ok: true, bytesSaved: cleanHtml.length });\n  }\n);\n\n\/\/ Route per commenti con sanitizzazione minimale\napp.post('\/comment-html',\n  body('text').trim().notEmpty().isLength({ max: 500 }),\n  handleValidation,\n  (req, res) => {\n    const clean = sanitizeHtml(req.body.text, COMMENT_OPTIONS);\n    res.json({ ok: true, text: clean });\n  }\n);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Un aspetto critico spesso trascurato: la sanitizzazione va applicata sia al momento della scrittura nel database che al momento della lettura. Sanitizzare solo all&#8217;uscita significa che dati corrotti nel database (magari inseriti prima di implementare la protezione) vengono comunque serviti all&#8217;utente. La best practice \u00e8 sanitizzare in entrata e applicare output encoding in uscita.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-6-implementare-loutput-encoding-contestuale\">Step 6: Implementare l&#8217;Output Encoding Contestuale<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;<strong>output encoding<\/strong> \u00e8 diverso dalla sanitizzazione: non rimuove i dati potenzialmente pericolosi, li trasforma in una rappresentazione sicura per il contesto specifico in cui vengono visualizzati. Il contesto \u00e8 fondamentale: il carattere <code>&lt;<\/code> in HTML richiede <code>&amp;lt;<\/code>, ma in un attributo JavaScript o in un URL richiede un encoding completamente diverso.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Funzione di escaping HTML manuale (utile senza dipendenze esterne)\nfunction escapeHtml(text) {\n  if (typeof text !== 'string') return '';\n  return text\n    .replace(\/&amp;\/g, '&amp;amp;')     \/\/ deve essere PRIMA degli altri replace\n    .replace(\/&lt;\/g, '&amp;lt;')\n    .replace(\/&gt;\/g, '&amp;gt;')\n    .replace(\/\"\/g, '&amp;quot;')\n    .replace(\/'\/g, '&amp;#x27;')\n    .replace(\/\\\/\/g, '&amp;&#x2F;');    \/\/ protezione aggiuntiva per contesti inline\n}\n\n\/\/ Escaping sicuro per incorporare dati JSON in HTML\n\/\/ (es. passare dati dal server al client via &lt;script&gt;)\nfunction safeJsonEmbed(data) {\n  return JSON.stringify(data)\n    .replace(\/&lt;\/g, '\\\\u003c')      \/\/ &lt; potrebbe chiudere tag &lt;script&gt;\n    .replace(\/&gt;\/g, '\\\\u003e')\n    .replace(\/&amp;\/g, '\\\\u0026')\n    .replace(\/'\/g, '\\\\u0027');\n}\n\n\/\/ Validazione degli URL per evitare \"javascript:\" e \"vbscript:\"\nfunction sanitizeUrl(url) {\n  if (!url) return '#';\n  try {\n    const parsed = new URL(url);\n    if (!['https:', 'http:', 'mailto:'].includes(parsed.protocol)) {\n      return '#'; \/\/ protocollo pericoloso\n    }\n    return parsed.href;\n  } catch {\n    return '#'; \/\/ URL non valido\n  }\n}\n\n\/\/ Esempio di route sicura con EJS\n\/\/ EJS: &lt;%= variabile %&gt; applica escaping HTML automatico\n\/\/ EJS: &lt;%- variabile %&gt; restituisce HTML grezzo (usare SOLO con contenuto gi\u00e0 sanitizzato)\napp.get('\/profile\/:username', (req, res) => {\n  const user = {\n    name: req.params.username,\n    bio: '&lt;script&gt;alert(\"XSS\")&lt;\/script&gt; Sviluppatore',\n    website: 'javascript:alert(1)',\n    jsonData: { role: 'user', count: 42 }\n  };\n\n  res.render('profile', {\n    name: user.name,                     \/\/ EJS escaper\u00e0 con &lt;%= name %&gt;\n    bio: escapeHtml(user.bio),           \/\/ doppio escaping per sicurezza\n    website: sanitizeUrl(user.website),  \/\/ '#' per URL pericolosi\n    \/\/ Per JSON inline in script tag:\n    jsonEmbed: safeJsonEmbed(user.jsonData)\n    \/\/ Usare nel template: &lt;script nonce=\"&lt;%= nonce %&gt;\"&gt;var d=&lt;%-jsonEmbed%&gt;&lt;\/script&gt;\n  });\n});\n\n\/\/ Sempre usa res.json() per le API REST: imposta Content-Type: application\/json\n\/\/ e serializza correttamente, prevenendo injection di HTML nella risposta\napp.get('\/api\/profile', (req, res) => {\n  res.json({ name: 'Mario', role: 'admin' });\n});\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-7-proteggere-i-cookie-con-httponly-e-secure\">Step 7: Proteggere i Cookie con HttpOnly e Secure<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;obiettivo principale di molti attacchi XSS \u00e8 il furto dei cookie di sessione. Una volta che un aggressore ottiene il cookie <code>session_id<\/code>, pu\u00f2 impersonare l&#8217;utente senza conoscerne le credenziali. Il flag <strong>HttpOnly<\/strong> istruisce il browser a non esporre il cookie a JavaScript: anche se un payload XSS viene eseguito, non pu\u00f2 leggere i cookie di sessione.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const session = require('express-session');\nconst crypto = require('crypto');\n\n\/\/ Configurazione sicura di express-session\napp.use(session({\n  \/\/ Il segreto va salvato in una variabile d'ambiente, mai nel codice sorgente\n  secret: process.env.SESSION_SECRET || crypto.randomBytes(64).toString('hex'),\n\n  \/\/ Il prefisso __Host- impone: Secure=true, nessun attributo Domain, Path=\/\n  \/\/ Questo impedisce a sottodomini compromessi di sovrascrivere il cookie\n  name: '__Host-session',\n\n  resave: false,\n  saveUninitialized: false,\n\n  cookie: {\n    httpOnly: true,       \/\/ JavaScript non pu\u00f2 leggere questo cookie\n    secure: true,         \/\/ invia il cookie solo su HTTPS\n    sameSite: 'strict',   \/\/ blocca l'invio cross-site (protezione CSRF)\n    maxAge: 7200000,      \/\/ 2 ore in millisecondi\n    path: '\/',\n  }\n}));\n\n\/\/ Prevenzione session fixation: rigenera il session ID dopo il login\napp.post('\/login', async (req, res) => {\n  const { username, password } = req.body;\n  const user = await verificaCredenziali(username, password);\n\n  if (!user) {\n    return res.status(401).json({ error: 'Credenziali non valide' });\n  }\n\n  \/\/ Rigenera il session ID per prevenire session fixation attacks\n  req.session.regenerate((err) => {\n    if (err) return res.status(500).json({ error: 'Errore sessione' });\n    req.session.userId = user.id;\n    req.session.role = user.role;\n    req.session.loginAt = Date.now();\n    res.json({ ok: true });\n  });\n});\n\n\/\/ Logout sicuro: distruggi la sessione e pulisci il cookie\napp.post('\/logout', (req, res) => {\n  req.session.destroy((err) => {\n    res.clearCookie('__Host-session');\n    res.json({ ok: true });\n  });\n});\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il flag <code>sameSite: 'strict'<\/code> aggiunge una difesa contro gli attacchi CSRF (Cross-Site Request Forgery) in aggiunta alla protezione XSS: il browser non invia il cookie nelle richieste cross-site, rendendo pi\u00f9 difficile per un aggressore sfruttare una sessione attiva da un sito diverso. Per le API REST che devono essere chiamate da altri domini, usa <code>sameSite: 'none'<\/code> con <code>secure: true<\/code> e implementa token CSRF espliciti.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-8-configurare-la-csp-con-nonce-per-script-inline\">Step 8: Configurare la CSP con Nonce per Script Inline<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La direttiva <code>'unsafe-inline'<\/code> nella CSP neutralizza gran parte della protezione contro XSS perch\u00e9 consente qualsiasi script inline. La soluzione moderna si chiama <strong>nonce<\/strong>: un valore casuale unico generato per ogni richiesta HTTP. Solo gli script che includono il nonce corretto nell&#8217;attributo <code>nonce<\/code> vengono eseguiti dal browser. Un payload XSS iniettato non conosce il nonce e viene bloccato.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Middleware completo per CSP con nonce e report URI\napp.use((req, res, next) => {\n  const nonce = crypto.randomBytes(16).toString('base64url');\n  res.locals.nonce = nonce;\n\n  \/\/ Imposta la CSP per questa specifica risposta HTTP\n  const cspDirectives = [\n    `default-src 'self'`,\n    `script-src 'self' 'nonce-${nonce}'`,   \/\/ solo script con questo nonce univoco\n    `style-src 'self' 'unsafe-inline'`,\n    `img-src 'self' data: https:`,\n    `font-src 'self'`,\n    `connect-src 'self'`,\n    `frame-ancestors 'none'`,               \/\/ blocca il sito in iframe\n    `object-src 'none'`,\n    `base-uri 'self'`,                      \/\/ blocca injection del tag &lt;base&gt;\n    `form-action 'self'`,                   \/\/ limita dove possono inviare i form\n    `upgrade-insecure-requests`,\n    `report-uri \/csp-report`,               \/\/ ricevi le violazioni CSP\n  ].join('; ');\n\n  res.setHeader('Content-Security-Policy', cspDirectives);\n  next();\n});\n\n\/\/ Nel template EJS, usa il nonce per gli script inline autorizzati:\n\/\/ &lt;script nonce=\"&lt;%= nonce %&gt;\"&gt;\n\/\/   console.log('Script autorizzato dalla CSP con nonce corretto');\n\/\/ &lt;\/script&gt;\n\n\/\/ Endpoint per ricevere le violazioni CSP dal browser\napp.post('\/csp-report',\n  express.json({ type: 'application\/csp-report' }),\n  (req, res) => {\n    const report = req.body['csp-report'] || req.body;\n    \/\/ Logga le violazioni per il monitoraggio della sicurezza\n    console.warn('[CSP VIOLATION]', {\n      blockedUri: report['blocked-uri'],\n      violatedDirective: report['violated-directive'],\n      documentUri: report['document-uri'],\n      ts: new Date().toISOString()\n    });\n    res.status(204).send();\n  }\n);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Durante lo sviluppo, usa <code>Content-Security-Policy-Report-Only<\/code> invece di <code>Content-Security-Policy<\/code>: la CSP viene valutata e le violazioni vengono segnalate al <code>report-uri<\/code> ma gli script non vengono bloccati. Questo ti permette di identificare tutte le risorse da aggiungere alla whitelist prima di attivare la CSP in produzione, evitando di rompere funzionalit\u00e0 legittime.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-9-prevenire-il-dom-based-xss-nel-frontend\">Step 9: Prevenire il DOM-Based XSS nel Frontend<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il <strong>DOM-Based XSS<\/strong> \u00e8 la categoria pi\u00f9 insidiosa perch\u00e9 avviene interamente nel browser, senza che il server processi mai il payload. Il codice JavaScript lato client legge dati da fonti non sicure (hash dell&#8217;URL, <code>localStorage<\/code>, <code>postMessage<\/code>) e li scrive nel DOM tramite metodi pericolosi chiamati &#8220;sink&#8221;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le sorgenti di dati non sicure pi\u00f9 comuni nel DOM sono: <code>location.href<\/code>, <code>location.hash<\/code>, <code>location.search<\/code>, <code>document.referrer<\/code>, <code>window.name<\/code>, <code>localStorage<\/code> e i messaggi <code>postMessage<\/code>. I sink pericolosi principali sono: <code>innerHTML<\/code>, <code>outerHTML<\/code>, <code>document.write()<\/code>, <code>eval()<\/code> e <code>setTimeout<\/code> con argomento stringa.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ public\/js\/safe-dom.js - Gestione sicura del DOM\n\n\/\/ PERICOLOSO - Non fare mai questo:\n\/\/ document.getElementById('output').innerHTML = userInput;\n\/\/ document.write(userInput);\n\n\/\/ SICURO - textContent per testo semplice (non interpreta HTML):\nfunction displayText(elementId, text) {\n  const el = document.getElementById(elementId);\n  if (!el) return;\n  el.textContent = text; \/\/ completamente sicuro, nessun HTML interpretato\n}\n\n\/\/ SICURO - Creazione programmatica di elementi DOM:\nfunction createComment(author, text, timestamp) {\n  const article = document.createElement('article');\n\n  const authorEl = document.createElement('strong');\n  authorEl.textContent = author; \/\/ textContent \u00e8 sempre sicuro\n\n  const textEl = document.createElement('p');\n  textEl.textContent = text;\n\n  const timeEl = document.createElement('time');\n  timeEl.textContent = new Date(timestamp).toLocaleDateString('it-IT');\n  timeEl.setAttribute('datetime', timestamp); \/\/ setAttribute \u00e8 sicuro\n\n  article.appendChild(authorEl);\n  article.appendChild(textEl);\n  article.appendChild(timeEl);\n  return article;\n}\n\n\/\/ SICURO - Validazione degli URL prima di usarli come href:\nfunction createSafeLink(url, text) {\n  let safeUrl = '#';\n  try {\n    const parsed = new URL(url);\n    if (['https:', 'mailto:'].includes(parsed.protocol)) {\n      safeUrl = parsed.href;\n    }\n  } catch { \/* URL non valido: usa fallback '#' *\/ }\n\n  const link = document.createElement('a');\n  link.href = safeUrl;\n  link.textContent = text;\n  link.rel = 'noopener noreferrer';\n  return link;\n}\n\n\/\/ SICURO - Gestione dei messaggi postMessage con verifica origine:\nwindow.addEventListener('message', (event) => {\n  const ALLOWED_ORIGINS = ['https:\/\/app.example.it'];\n  if (!ALLOWED_ORIGINS.includes(event.origin)) {\n    console.warn('postMessage da origine non autorizzata:', event.origin);\n    return; \/\/ ignora messaggi da origini sconosciute\n  }\n  if (event.data?.type === 'DISPLAY_TEXT') {\n    displayText('content-area', event.data.value);\n  }\n});\n\n\/\/ Se hai assolutamente bisogno di inserire HTML nel DOM,\n\/\/ usa DOMPurify caricato come modulo con &lt;script src nonce=\"...\"&gt;:\n\/\/ import DOMPurify from 'dompurify';\n\/\/ element.innerHTML = DOMPurify.sanitize(contentFromServer);\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-10-abilitare-i-trusted-types-per-protezione-a-livello-di-browser\">Step 10: Abilitare i Trusted Types per Protezione a Livello di Browser<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I <strong>Trusted Types<\/strong> sono una API browser moderna (supportata da Chrome 83+, Edge e in sperimentazione su Firefox) che elimina alla radice il DOM-Based XSS rendendo obbligatorio l&#8217;uso di valori &#8220;fidati&#8221; per i sink pericolosi. Se il codice tenta di assegnare una stringa normale a <code>innerHTML<\/code>, il browser lancia un errore invece di eseguirla. Questa tecnica \u00e8 raccomandata da Google nel suo hardening interno.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Aggiunta alla CSP (lato server):\n\/\/ require-trusted-types-for 'script'; trusted-types dompurify default\n\n\/\/ Lato server, aggiungi alla direttiva CSP:\nconst cspWithTrustedTypes = [\n  `default-src 'self'`,\n  `script-src 'self' 'nonce-${nonce}'`,\n  `require-trusted-types-for 'script'`,        \/\/ abilita Trusted Types\n  `trusted-types dompurify default`,            \/\/ policy autorizzate\n  \/\/ ... altre direttive\n].join('; ');\n\n\/\/ public\/js\/trusted-types-init.js\nif (window.trustedTypes &amp;&amp; window.trustedTypes.createPolicy) {\n  \/\/ Crea una policy che usa DOMPurify per sanitizzare HTML\n  window.__policy = window.trustedTypes.createPolicy('dompurify', {\n    createHTML: (input) => DOMPurify.sanitize(input, {\n      RETURN_TRUSTED_TYPE: true\n    }),\n    createScriptURL: (url) => {\n      \/\/ Verifica che lo script provenga dalla nostra origine\n      const parsed = new URL(url, location.origin);\n      if (parsed.origin !== location.origin) {\n        throw new Error('URL script non autorizzato');\n      }\n      return url;\n    }\n  });\n\n  \/\/ Funzione helper per innerHTML sicuro con Trusted Types\n  window.safeInnerHTML = function(element, html) {\n    element.innerHTML = window.__policy.createHTML(html);\n  };\n} else {\n  \/\/ Fallback per browser che non supportano ancora Trusted Types\n  window.safeInnerHTML = function(element, html) {\n    element.innerHTML = DOMPurify.sanitize(html);\n  };\n}\n\n\/\/ Migrazione del codice esistente:\n\/\/ Prima: container.innerHTML = serverData;          \/\/ PERICOLOSO\n\/\/ Dopo:  safeInnerHTML(container, serverData);      \/\/ SICURO con TT + DOMPurify\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types richiede un refactoring graduale del codice frontend. Il modo pi\u00f9 efficace per adottarli \u00e8 attivare prima la modalit\u00e0 <code>report-only<\/code> nella CSP per identificare tutti i sink <code>innerHTML<\/code> esistenti, correggerli uno per uno, poi passare alla modalit\u00e0 enforcement. Una volta completata la migrazione, \u00e8 praticamente impossibile introdurre accidentalmente nuovi sink XSS nel codice.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-11-testare-la-protezione-xss-con-payload-reali\">Step 11: Testare la Protezione XSS con Payload Reali<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Implementare le difese non \u00e8 sufficiente: \u00e8 necessario verificare che funzionino contro payload XSS reali. Il testing va eseguito in un ambiente di staging con gli stessi header e configurazioni della produzione.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># ============================================================\n# Test automatico con curl - verifica che i payload XSS vengano bloccati\n# ============================================================\n\necho \"=== Verifica header di sicurezza ===\"\ncurl -sI \"http:\/\/localhost:3000\/\" | grep -iE \"content-security|x-frame|x-content-type|strict-transport\"\n\necho \"\"\necho \"=== Test Reflected XSS ===\"\n# Payload XSS comuni - nessuno deve essere presente nella risposta senza escaping\nPAYLOADS=(\n  \"%3Cscript%3Ealert(1)%3C\/script%3E\"\n  \"%3Cimg+src%3Dx+onerror%3Dalert(1)%3E\"\n  \"%3Csvg+onload%3Dalert(1)%3E\"\n  \"javascript%3Aalert(1)\"\n  \"%27%3E%3Cscript%3Ealert(1)%3C\/script%3E\"\n)\n\nfor payload in \"${PAYLOADS[@]}\"; do\n  response=$(curl -s \"http:\/\/localhost:3000\/search?q=$payload\")\n  if echo \"$response\" | grep -qiE \"&lt;script|onerror=|onload=|javascript:\"; then\n    echo \"VULNERABILE: $payload\"\n  else\n    echo \"PROTETTO OK: $(echo $payload | cut -c1-40)...\"\n  fi\ndone\n\necho \"\"\necho \"=== Test validazione input ===\"\n# Testa che l'input oltre il limite venga rifiutato\ncurl -s -X POST http:\/\/localhost:3000\/comment \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"text\": \"\", \"author\": \"Mario\"}' | python3 -m json.tool\n\necho \"\"\necho \"=== Verifica npm audit ===\"\nnpm audit --audit-level=high 2&gt;&amp;1 | tail -5\n\necho \"\"\necho \"=== Verifica CSP nel browser (usa Chrome DevTools) ===\"\necho \"Apri DevTools &gt; Network &gt; Headers e verifica:\"\necho \"  Content-Security-Policy contiene: default-src 'self'\"\necho \"  X-Frame-Options: SAMEORIGIN\"\necho \"  X-Content-Type-Options: nosniff\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per un testing pi\u00f9 sistematico, integra <strong>OWASP ZAP<\/strong> nella pipeline CI\/CD. ZAP include uno scanner XSS automatico che testa centinaia di payload contro tutti i punti di ingresso dell&#8217;applicazione. Un&#8217;alternativa \u00e8 usare <strong>Burp Suite Community<\/strong> per il testing manuale guidato durante le fasi di sviluppo. Esegui almeno un penetration test manuale all&#8217;anno per le applicazioni in produzione.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-12-configurare-il-logging-per-rilevare-attacchi-xss\">Step 12: Configurare il Logging per Rilevare Attacchi XSS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La prevenzione \u00e8 fondamentale, ma il rilevamento \u00e8 altrettanto importante. Un sistema di logging che registra i tentativi di XSS ti permette di identificare gli aggressori, le tecniche usate e se le difese stanno reggendo nel tempo.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Middleware di rilevamento e logging dei tentativi XSS\nconst XSS_PATTERNS = [\n  \/&lt;script[^&gt;]*&gt;\/i,\n  \/javascript\\s*:\/i,\n  \/on\\w+\\s*=\/i,          \/\/ onerror=, onload=, onclick=, ecc.\n  \/&lt;img[^&gt;]+onerror\/i,\n  \/&lt;svg[^&gt;]+onload\/i,\n  \/document\\.cookie\/i,\n  \/window\\.location\/i,\n];\n\nfunction detectXssAttempt(req, res, next) {\n  \/\/ Controlla tutti i possibili vettori di input\n  const valuesToCheck = [\n    ...Object.values(req.query),\n    ...Object.values(req.params),\n    ...(req.body ? Object.values(req.body) : []),\n    req.headers['user-agent'] || '',\n    req.headers['referer'] || '',\n  ].filter(v => typeof v === 'string');\n\n  for (const value of valuesToCheck) {\n    for (const pattern of XSS_PATTERNS) {\n      if (pattern.test(value)) {\n        \/\/ Logga il tentativo con tutti i dettagli rilevanti\n        console.warn('[XSS ATTEMPT]', JSON.stringify({\n          ip: req.ip || req.headers['x-forwarded-for'],\n          method: req.method,\n          path: req.path,\n          pattern: pattern.source,\n          value: value.substring(0, 200),\n          ts: new Date().toISOString()\n        }));\n        \/\/ In produzione con WAF: blocca la richiesta\n        \/\/ return res.status(400).json({ error: 'Input non valido' });\n        break;\n      }\n    }\n  }\n  next();\n}\n\n\/\/ Applica il middleware prima di tutti i route handler\napp.use(detectXssAttempt);\n\n\/\/ Endpoint per ricevere violazioni CSP e registrarle\napp.post('\/csp-report',\n  express.json({ type: 'application\/csp-report' }),\n  (req, res) => {\n    const r = req.body['csp-report'] || req.body;\n    console.warn('[CSP VIOLATION]', JSON.stringify({\n      blockedUri: r['blocked-uri'],\n      violatedDirective: r['violated-directive'],\n      documentUri: r['document-uri'],\n      ts: new Date().toISOString()\n    }));\n    res.status(204).send();\n  }\n);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In produzione, centralizza i log su una piattaforma SIEM come Wazuh, Elasticsearch o AWS CloudWatch. I log strutturati in formato JSON si integrano con questi sistemi e permettono di creare alert automatici quando il numero di tentativi XSS supera una soglia configurabile. Questo \u00e8 particolarmente utile per rilevare attacchi automatizzati e scansioni di vulnerabilit\u00e0.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"progetto-completo-server-express-js-con-protezione-xss-integrata\">Progetto Completo: Server Express.js con Protezione XSS Integrata<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ecco il file <code>server.js<\/code> completo e funzionante che integra tutte le misure discusse nei 12 step. Puoi usarlo come base per le tue applicazioni Express.js in produzione.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js - Protezione XSS completa per Express.js [2026]\n'use strict';\n\nconst express   = require('express');\nconst helmet    = require('helmet');\nconst sanitize  = require('sanitize-html');\nconst { body, query, validationResult } = require('express-validator');\nconst session   = require('express-session');\nconst crypto    = require('crypto');\n\nconst app = express();\nconst IS_PROD = process.env.NODE_ENV === 'production';\n\n\/\/ 1. Nonce crittografico per ogni richiesta\napp.use((req, res, next) => {\n  res.locals.nonce = crypto.randomBytes(16).toString('base64url');\n  next();\n});\n\n\/\/ 2. Helmet con CSP basata su nonce\napp.use((req, res, next) => {\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc:  [\"'self'\"],\n        scriptSrc:   [\"'self'\", (req, res) => `'nonce-${res.locals.nonce}'`],\n        styleSrc:    [\"'self'\", \"'unsafe-inline'\"],\n        imgSrc:      [\"'self'\", \"data:\", \"https:\"],\n        connectSrc:  [\"'self'\"],\n        fontSrc:     [\"'self'\"],\n        objectSrc:   [\"'none'\"],\n        frameSrc:    [\"'none'\"],\n        baseUri:     [\"'self'\"],\n        formAction:  [\"'self'\"],\n        upgradeInsecureRequests: [],\n      },\n    },\n    xXssProtection: false,\n  })(req, res, next);\n});\n\n\/\/ 3. Body parsing con limiti espliciti\napp.use(express.json({ limit: '10kb' }));\napp.use(express.urlencoded({ extended: false, limit: '10kb' }));\n\n\/\/ 4. Sessioni sicure\napp.use(session({\n  secret: process.env.SESSION_SECRET || crypto.randomBytes(64).toString('hex'),\n  name:   '__Host-session',\n  resave: false,\n  saveUninitialized: false,\n  cookie: {\n    httpOnly: true,\n    secure:   IS_PROD,\n    sameSite: 'strict',\n    maxAge:   7200000\n  }\n}));\n\n\/\/ 5. Whitelist per sanitizzazione HTML commenti\nconst COMMENT_OPTS = {\n  allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],\n  allowedAttributes: {},\n  allowedSchemes: [],\n};\n\n\/\/ 6. Gestione validazione centralizzata\nconst validate = (req, res, next) => {\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });\n  next();\n};\n\n\/\/ 7. Route: commento sicuro (Stored XSS prevention)\napp.post('\/api\/comments',\n  [\n    body('text').trim().notEmpty().isLength({ max: 500 }),\n    body('author').trim().isAlphanumeric('it-IT', { ignore: ' -' }).isLength({ min: 2, max: 50 }),\n  ],\n  validate,\n  (req, res) => {\n    const clean = sanitize(req.body.text, COMMENT_OPTS);\n    res.json({ ok: true, text: clean, author: req.body.author });\n  }\n);\n\n\/\/ 8. Route: ricerca sicura (Reflected XSS prevention)\napp.get('\/api\/search',\n  [\n    query('q').trim().isLength({ max: 200 })\n              .customSanitizer(v => v.replace(\/[&lt;&gt;\"'`]\/g, '')),\n  ],\n  validate,\n  (req, res) => res.json({ query: req.query.q, results: [] })\n);\n\n\/\/ 9. CSP Report endpoint\napp.post('\/csp-report',\n  express.json({ type: 'application\/csp-report' }),\n  (req, res) => {\n    const r = req.body['csp-report'] || req.body;\n    console.warn('[CSP]', r['violated-directive'], r['blocked-uri']);\n    res.status(204).send();\n  }\n);\n\n\/\/ 10. Health check\napp.get('\/health', (req, res) => res.json({ status: 'ok', secure: true }));\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => console.log(`Server XSS-protected attivo sulla porta ${PORT}`));\nmodule.exports = app;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"errori-comuni-nella-prevenzione-xss-in-node-js\">Errori Comuni nella Prevenzione XSS in Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Anche gli sviluppatori esperti commettono errori nella prevenzione XSS. Ecco i 6 errori pi\u00f9 frequenti che portano a vulnerabilit\u00e0 in produzione.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Errore 1: Sanitizzare solo l&#8217;input, non l&#8217;output.<\/strong> La regola fondamentale della sicurezza web \u00e8 &#8220;sanitizza in uscita, valida in ingresso&#8221;. Anche se sanitizzi al momento dell&#8217;inserimento nel database, devi codificare i dati correttamente al momento della visualizzazione, perch\u00e9 il contesto cambia: HTML, JavaScript, CSS e URL richiedono encoding diversi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Errore 2: Usare innerHTML con dati dinamici.<\/strong> Questo \u00e8 il sink XSS pi\u00f9 comune nel codice frontend. Anche se i dati provengono dal tuo database, potrebbero essere stati compromessi da un attacco precedente. Usa sempre <code>textContent<\/code> o DOMPurify prima di assegnare a <code>innerHTML<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Errore 3: Dimenticare i dati provenienti dagli header HTTP.<\/strong> <code>req.headers['referer']<\/code>, <code>req.headers['user-agent']<\/code> e altri header possono contenere payload XSS. Se li mostri in un pannello di amministrazione HTML senza escaping, hai una vulnerabilit\u00e0 Blind XSS che un aggressore pu\u00f2 sfruttare per comprometterti l&#8217;account amministratore.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Errore 4: Usare &#8216;unsafe-inline&#8217; nella CSP per comodit\u00e0.<\/strong> Molti sviluppatori aggiungono <code>'unsafe-inline'<\/code> alla CSP per far funzionare rapidamente script inline esistenti, neutralizzando completamente la protezione CSP contro XSS. La soluzione \u00e8 migrare gli script inline a file separati o usare nonce crittografici.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Errore 5: Non aggiornare le dipendenze di sicurezza.<\/strong> I pacchetti <code>sanitize-html<\/code>, <code>helmet<\/code> e <code>express-validator<\/code> vengono aggiornati regolarmente per correggere bypass XSS scoperti. Imposta Dependabot o Renovate per aggiornamenti automatici delle patch di sicurezza e integra <code>npm audit<\/code> nella CI\/CD.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Errore 6: Trattare i dati JSON come intrinsecamente sicuri.<\/strong> Quando un template HTML incorpora dati JSON direttamente (<code>&lt;script&gt;var d = {\"user\": \"...\"}&lt;\/script&gt;<\/code>), i caratteri <code>&lt;<\/code> nei valori possono chiudere il tag <code>&lt;\/script&gt;<\/code> e iniettare HTML. Usa sempre <code>JSON.stringify(data).replace(\/&lt;\/g, '\\\\u003c')<\/code> quando incorpori JSON in pagine HTML.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"troubleshooting-8-problemi-frequenti-e-le-loro-soluzioni\">Troubleshooting: 8 Problemi Frequenti e le Loro Soluzioni<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Il payload XSS viene ancora eseguito dopo la sanitizzazione<\/strong> &#8211; Causa pi\u00f9 probabile: stai usando <code>&lt;%-<\/code> in EJS (HTML grezzo) invece di <code>&lt;%=<\/code> (con escaping automatico). Controlla tutti i template e cerca <code>&lt;%-<\/code>: ogni occorrenza \u00e8 potenzialmente pericolosa a meno che il contenuto non sia gi\u00e0 stato sanitizzato con <code>sanitize-html<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Helmet blocca risorse CSS o JavaScript legittimi<\/strong> &#8211; La CSP configurata non include i domini di risorse esterne (CDN, Google Fonts). Attiva prima <code>reportOnly: true<\/code> nella CSP per vedere tutte le violazioni senza bloccare le risorse. Poi aggiungi i domini necessari alle direttive appropriate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. La CSP blocca script di analytics o tag manager<\/strong> &#8211; Strumenti come Google Analytics iniettano script inline. Soluzione: usa il nonce per autorizzare gli script specifici del tag manager, oppure caricali da file esterni con il dominio nella whitelist <code>scriptSrc<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. sanitize-html rimuove tag HTML necessari al layout<\/strong> &#8211; La configurazione predefinita \u00e8 molto restrittiva. Aggiungi i tag necessari alla lista <code>allowedTags<\/code> nella tua configurazione personalizzata. Evita di usare <code>sanitizeHtml.defaults.allowedTags<\/code> come base perch\u00e9 include molti tag non necessari.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>5. I cookie non vengono impostati come HttpOnly in ambiente di staging<\/strong> &#8211; La configurazione <code>secure: true<\/code> richiede HTTPS. Se il server si trova dietro un reverse proxy (Nginx, Cloudflare), aggiungi <code>app.set('trust proxy', 1)<\/code> prima della configurazione della sessione e verifica che il proxy imposti correttamente l&#8217;header <code>X-Forwarded-Proto: https<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>6. Gli errori 403 appaiono dopo l&#8217;attivazione di Helmet sui form<\/strong> &#8211; La direttiva <code>formAction 'self'<\/code> blocca i form che inviano dati verso domini diversi. Aggiungi i domini di destinazione dei form alla direttiva <code>formAction<\/code> nella configurazione Helmet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>7. express-validator non blocca un pattern XSS specifico<\/strong> &#8211; express-validator valida e sanitizza ma non \u00e8 progettato come motore di rilevamento XSS. Usa <code>.escape()<\/code> per l&#8217;escaping di base su campi di testo, e <code>sanitize-html<\/code> per campi che devono accettare HTML. Non cercare di bloccare XSS con regex personalizzate: \u00e8 quasi impossibile coprire tutti i vettori.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>8. Il nonce CSP non funziona con framework SPA (React, Vue, Angular)<\/strong> &#8211; I framework SPA generano script durante la build senza conoscere il nonce del server. Soluzione: carica i bundle JavaScript come file statici con <code>&lt;script src&gt;<\/code> dalla tua origine (gi\u00e0 nella whitelist). Usa <code>'strict-dynamic'<\/code> nella CSP per consentire agli script fidati di caricare altri script in modo dinamico.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"consigli-avanzati-per-la-sicurezza-xss-in-produzione\">Consigli Avanzati per la Sicurezza XSS in Produzione<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Subresource Integrity (SRI):<\/strong> Quando carichi librerie JavaScript da CDN esterni, aggiungi l&#8217;attributo <code>integrity<\/code> al tag <code>&lt;script&gt;<\/code>. Il browser calcola il checksum SHA-384 del file e lo confronta con quello specificato; se non corrisponde, blocca lo script. Questo protegge contro la compromissione del CDN o attacchi di supply chain. Genera il checksum con: <code>curl -s https:\/\/cdn.example.com\/lib.js | openssl dgst -sha384 -binary | openssl base64 -A<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Cross-Origin policies:<\/strong> Gli header <code>Cross-Origin-Opener-Policy: same-origin<\/code> e <code>Cross-Origin-Embedder-Policy: require-corp<\/code> isolano il processo del browser, riducendo la superficie d&#8217;attacco per attacchi XSS combinati con Spectre. Aggiungili alla risposta HTTP quando la tua applicazione non richiede accesso a risorse cross-origin non autorizzate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Permissions Policy:<\/strong> L&#8217;header <code>Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=()<\/code> disabilita API browser potenzialmente abusabili da payload XSS. Anche se un aggressore esegue codice nella pagina, non pu\u00f2 accedere alla geolocalizzazione, alla fotocamera o ai dati di pagamento dell&#8217;utente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>WAF come livello aggiuntivo:<\/strong> Soluzioni come Cloudflare WAF o AWS WAF includono regole aggiornate per rilevare payload XSS noti prima che raggiungano l&#8217;applicazione. Un WAF non sostituisce la difesa nell&#8217;applicazione, ma riduce il rumore di attacchi automatizzati. Il costo di Cloudflare WAF parte da circa 20 euro al mese, significativamente inferiore al costo medio di una violazione dati.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Analisi statica del codice:<\/strong> Integra <code>eslint-plugin-security<\/code> e <code>semgrep<\/code> nella pipeline CI\/CD per rilevare automaticamente i sink pericolosi (<code>innerHTML<\/code>, <code>eval<\/code>, <code>document.write<\/code>) nel codice JavaScript durante lo sviluppo, prima del commit. Semgrep ha regole specifiche per Node.js XSS disponibili nel suo registro pubblico.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"domande-frequenti-sulla-prevenzione-xss-in-node-js\">Domande Frequenti sulla Prevenzione XSS in Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Qual \u00e8 la differenza tra XSS e SQL Injection?<\/strong><br>L&#8217;XSS inietta codice JavaScript eseguito nel browser della vittima. La SQL Injection inietta codice SQL eseguito nel database del server. Entrambi nascono dalla mancata validazione dell&#8217;input, ma hanno vettori e conseguenze diverse. Un&#8217;applicazione pu\u00f2 essere vulnerabile a entrambi contemporaneamente. Per la prevenzione SQL Injection in Node.js, consulta la nostra guida dedicata.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Helmet.js \u00e8 sufficiente per prevenire XSS?<\/strong><br>No. Helmet imposta gli header HTTP di sicurezza che istruiscono il browser, ma da solo non sanitizza i dati. La protezione completa richiede tre livelli: validazione dell&#8217;input (express-validator), sanitizzazione dell&#8217;output (sanitize-html, escaping), e policy di sicurezza del browser (Helmet con CSP).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>React e Vue proteggono automaticamente dall&#8217;XSS?<\/strong><br>Parzialmente. React e Vue escapano automaticamente il contenuto reso con JSX e le direttive di binding standard (<code>{variabile}<\/code> e <code>v-bind<\/code>). Tuttavia, le funzionalit\u00e0 per HTML grezzo (<code>dangerouslySetInnerHTML<\/code> in React, <code>v-html<\/code> in Vue) bypassano questa protezione. Usa sempre DOMPurify con queste funzionalit\u00e0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Come prevenire XSS negli editor di testo ricco (TinyMCE, Quill)?<\/strong><br>Sanitizza l&#8217;HTML lato server con <code>sanitize-html<\/code> prima del salvataggio e prima della visualizzazione. Non fidarti mai della sanitizzazione inclusa nell&#8217;editor: un aggressore pu\u00f2 inviare richieste HTTP dirette all&#8217;API, bypassando completamente il frontend e il suo editor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Il WAF sostituisce la protezione XSS nell&#8217;applicazione?<\/strong><br>No. Un WAF \u00e8 un complemento utile ma non un sostituto. I WAF possono essere aggirati con tecniche di offuscamento del payload (encoding Unicode, frammentazione, varianti HTML5). La difesa primaria \u00e8 nell&#8217;applicazione: validazione, sanitizzazione e CSP. Il WAF aggiunge uno strato perimetrale contro gli attacchi automatizzati di massa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Come gestire l&#8217;XSS in un&#8217;API REST che restituisce solo JSON?<\/strong><br>Le API REST con <code>Content-Type: application\/json<\/code> sono meno vulnerabili all&#8217;XSS diretto perch\u00e9 il browser non interpreta JSON come HTML. Tuttavia, se il frontend JavaScript usa i dati JSON per costruire HTML tramite <code>innerHTML<\/code>, la vulnerabilit\u00e0 esiste nell&#8217;applicazione client. Sanitizza i dati sia lato server che lato client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Come testare l&#8217;XSS in modo sicuro senza danneggiare l&#8217;applicazione?<\/strong><br>Usa un ambiente di staging isolato con dati di test. Esegui OWASP ZAP in modalit\u00e0 spider + active scan oppure Burp Suite Community. Per i test manuali, i payload di base come <code>&lt;script&gt;alert(1)&lt;\/script&gt;<\/code>, <code>&lt;img src=x onerror=alert(1)&gt;<\/code> e <code>javascript:alert(1)<\/code> sono sufficienti per verificare i casi pi\u00f9 comuni. Documenta ogni test e il suo risultato.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ogni quanto aggiornare le librerie di sicurezza?<\/strong><br>Le patch di sicurezza vanno applicate entro 72 ore dalla pubblicazione. Per gli aggiornamenti minor e patch regolari, automatizza con Dependabot o Renovate. Per gli aggiornamenti major che possono introdurre breaking changes, testa in staging prima del deploy. Esegui <code>npm audit<\/code> almeno settimanalmente o ad ogni pull request.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"copertura-correlata\">Copertura Correlata<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Per approfondire la sicurezza delle applicazioni Node.js:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/it\/sql-injection-nodejs\/\">SQL Injection in Node.js: Come Prevenirla in 12 Step<\/a> &#8211; tecniche analoghe per proteggere il database da injection<\/li><li><a href=\"\/it\/validazione-input-nodejs\/\">Validazione Input in Node.js: Zod, Joi e express-validator in 12 Step<\/a> &#8211; validazione avanzata dell&#8217;input utente<\/li><li><a href=\"\/it\/protezione-csrf-nodejs\/\">Protezione CSRF in Node.js: 12 Step<\/a> &#8211; difesa contro Cross-Site Request Forgery complementare all&#8217;XSS<\/li><li><a href=\"\/it\/owasp-top-10-nodejs-2026\/\">OWASP Top 10 2025 in Node.js: 10 Vulnerabilit\u00e0, 12 Difese<\/a> &#8211; panoramica completa di tutte le vulnerabilit\u00e0 OWASP<\/li><li><a href=\"\/it\/rate-limiting-nodejs\/\">Rate Limiting in Node.js: API Sicura in 12 Step<\/a> &#8211; protezione contro brute-force e abuso delle API<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"risorse-esterne-autorevoli\">Risorse Esterne Autorevoli<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/owasp.org\/www-community\/attacks\/xss\/\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP XSS: definizione completa e tipologie di attacco<\/a><\/li><li><a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cross_Site_Scripting_Prevention_Cheat_Sheet.html\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP XSS Prevention Cheat Sheet<\/a><\/li><li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CSP\" rel=\"noopener noreferrer\" target=\"_blank\">MDN: Content Security Policy (CSP), guida completa<\/a><\/li><li><a href=\"https:\/\/helmetjs.github.io\/\" rel=\"noopener noreferrer\" target=\"_blank\">Helmet.js: documentazione ufficiale e configurazione<\/a><\/li><li><a href=\"https:\/\/expressjs.com\/en\/advanced\/best-practice-security\/\" rel=\"noopener noreferrer\" target=\"_blank\">Express.js Security Best Practices (documentazione ufficiale)<\/a><\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Il Cross-Site Scripting (XSS) \u00e8 la vulnerabilit\u00e0 web pi\u00f9 segnalata nel 2025 secondo OWASP, presente nel 68% delle applicazioni testate. Con Node.js e Express.js, un singolo campo di input non\u2026<\/p>\n","protected":false},"author":4,"featured_media":246,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-245","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/245","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/comments?post=245"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/245\/revisions"}],"predecessor-version":[{"id":247,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/245\/revisions\/247"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media\/246"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media?parent=245"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/categories?post=245"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/tags?post=245"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}