Helmet.js är ett Express-middleware som automatiskt sätter 13 HTTP-svarshuvuden och skyddar din Node.js-applikation mot XSS, clickjacking, MIME-sniffning och HTTPS-nedgradering. En enda rad kod, app.use(helmet()), aktiverar alla skydd på en gång. Den här guiden visar dig exakt hur du konfigurerar varje huvud, testar dem och driftsätter dem i produktion på 30 minuter.

HTTP-säkerhetshuvuden är instruktioner som servern skickar till webbläsaren och berättar hur den ska hantera innehåll. Utan dem kan angripare injicera skadliga skript, lura användare att klicka på osynliga knappar och stjäla sessionsdata. Helmet.js version 8.2.0 löser detta med ett enda npm-paket som kräver Node.js 16 eller senare och fungerar med alla Express-baserade ramverk.

Vad är HTTP-säkerhetshuvuden och varför de spelar roll

HTTP-säkerhetshuvuden är svarshuvuden som instruerar webbläsaren om hur den ska hantera en webbsidas innehåll. De fungerar som ett kontrakt: servern berättar vad som är tillåtet och webbläsaren följer dessa regler. Utan dem lämnar du webbläsaren att gissa, och angripare utnyttjar just det utrymmet.

Fyra av de vanligaste attackkategorierna motverkas direkt av säkerhetshuvuden:

  • Cross-Site Scripting (XSS): angripare injicerar skadlig JavaScript i din sida. Content Security Policy begränsar vilka skriptkällor webbläsaren accepterar.
  • Clickjacking: din sida bäddas in i en osynlig iframe på en angriparsida. X-Frame-Options och CSP:s frame-ancestors-direktiv blockerar det.
  • MIME-sniffning: webbläsaren gissar filtyp och exekverar en uppladdad fil som skript. X-Content-Type-Options: nosniff förhindrar det.
  • HTTPS-nedgradering: angripare omdirigerar trafik till HTTP via man-in-the-middle. Strict-Transport-Security tvingar webbläsaren att alltid använda HTTPS.

Helmet.js sätter 13 huvuden i ett enda anrop. Utan Helmet skulle du behöva sätta varje huvud manuellt i varje route-handler, vilket leder till inkonsekvent konfiguration och glömda skydd i nya endpoints.

OWASP:s projekt Secure Headers Project listar exakt vilka huvuden moderna webbapplikationer bör sätta, och Helmet.js täcker samtliga i deras rekommenderade lista. Det är därför Helmet.js är branschstandard för Node.js-applikationer i produktion. Du kan se hela OWASP-rekommendationen på owasp.org/www-project-secure-headers.

De 13 huvuden Helmet.js sätter som standard

Helmet.js version 8.2.0 aktiverar 13 HTTP-svarshuvuden i standardkonfigurationen. Tabellen nedan visar varje huvud, dess standardvärde och vad det skyddar mot.

HuvudStandardvärdeSkyddar mot
Content-Security-Policydefault-src ‘self’; script-src ‘self’; object-src ‘none’XSS, skriptinjektion
Cross-Origin-Opener-Policysame-originCross-origin fönsteråtkomst
Cross-Origin-Resource-Policysame-originCross-origin resursläsning
Origin-Agent-Cluster?1Processisolering
Referrer-Policyno-referrerURL-informationsläckage
Strict-Transport-Securitymax-age=15552000; includeSubDomainsHTTPS-nedgradering
X-Content-Type-OptionsnosniffMIME-sniffning
X-DNS-Prefetch-ControloffDNS-förläsning
X-Download-OptionsnoopenIE-filöppning
X-Frame-OptionsSAMEORIGINClickjacking
X-Permitted-Cross-Domain-PoliciesnoneAdobe Flash-policyer
X-Powered-ByBorttagenServerinformationsläckage
X-XSS-ProtectionInaktiverad (0)Äldre osäker XSS-filter

Standardkonfigurationen är säker och fungerar för de flesta appar. Du behöver bara anpassa CSP och HSTS för ditt specifika fall, vilket stegen längre ner i guiden visar.

Förutsättningar

Innan du börjar, kontrollera att du har följande installerat:

  • Node.js 16 eller senare (Helmet 8.x kräver Node 16+). Kör node --version för att kontrollera.
  • npm 8 eller senare. Kör npm --version.
  • Express.js (fungerar med Express 4.x och 5.x)
  • Grundläggande kunskaper om Express-middleware
  • En textredigerare och terminal

Guiden använder ES-moduler (import/export) med "type": "module" i package.json. Om du använder CommonJS (require()) fungerar Helmet.js precis lika bra, men syntaxen skiljer sig något.

Steg 1: Skapa Node.js-projektet

Börja med att skapa en ny projektkatalog och initiera ett npm-projekt. Den här strukturen håller konfigurationsfilerna organiserade och gör det enkelt att lägga till fler moduler senare.

mkdir helmet-demo && cd helmet-demo
npm init -y
npm pkg set type="module"
npm pkg set engines.node=">=16.0.0"

Skapa katalogstrukturen för projektet:

helmet-demo/
├── src/
│   ├── app.js          # Express-appkonfiguration
│   ├── routes/
│   │   └── api.js      # API-routes
│   └── middleware/
│       └── security.js # Helmet-konfiguration
├── public/
│   └── index.html      # Testsida
├── .env
└── package.json

Helmet.js kräver ingen databas eller extern tjänst, så projektet kan köras lokalt direkt efter installationen i steg 2.

Steg 2: Installera Helmet.js 8.2.0

Installera Helmet.js och Express med npm. Du behöver också dotenv för miljövariabler och ett testverktyg för att verifiera svarsrubrikerna.

npm install helmet express dotenv
npm install --save-dev nodemon

Kontrollera att du har rätt version:

npm list helmet
# Förväntat utdata:
# [email protected]
# └── [email protected]

Lägg till start-skript i package.json:

{
  "name": "helmet-demo",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node src/app.js",
    "dev": "nodemon src/app.js"
  },
  "engines": {
    "node": ">=16.0.0"
  }
}

Steg 3: Grundkonfiguration med Express

Skapa src/app.js med den grundläggande Express- och Helmet-konfigurationen. Nyckelregeln är att anropa app.use(helmet()) tidigt i middleware-kedjan, innan dina routes definieras. Om du sätter Helmet efter dina routes kan förfrågningar passera utan säkerhetshuvuden.

import express from 'express';
import helmet from 'helmet';
import { fileURLToPath } from 'url';
import path from 'path';
import 'dotenv/config';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 3000;

// Helmet MÅSTE anropas före alla routes
app.use(helmet());

// JSON-parser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Statiska filer
app.use(express.static(path.join(__dirname, '..', 'public')));

// Hälsokontroll
app.get('/health', (req, res) => {
  res.json({ status: 'OK', timestamp: new Date().toISOString() });
});

app.listen(PORT, () => {
  console.log(`Server kör på port ${PORT}`);
});

Starta servern och kontrollera att alla 13 huvuden sätts:

npm run dev

# Testa med curl i en annan terminal:
curl -I http://localhost:3000/health

Förväntat utdata från curl-kommandot:

HTTP/1.1 200 OK
Content-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
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none

Observera att X-Powered-By saknas i svaret. Det är ett avsiktligt borttagande. Utan Helmet avslöjar Express automatiskt X-Powered-By: Express, vilket berättar för angripare exakt vilken teknologi din server kör och underlättar riktade attacker. Helmet tar bort det huvud helt och hållet.

Steg 4: Anpassa Content Security Policy

Content Security Policy (CSP) är det kraftfullaste HTTP-säkerhetshuvudet och skyddar mot XSS-attacker genom att begränsa vilka resurser webbläsaren får ladda. Helmets standardkonfiguration är mycket restriktiv och blockerar allt som inte kommer från din egen server.

De vanligaste CSP-direktiven och deras funktion:

DirektivStandardvärdeBeskrivning
default-src‘self’Fallback för alla resurstyper
script-src‘self’JavaScript-källkoder
style-src‘self’ https: ‘unsafe-inline’CSS-källor (inkl. inline)
img-src‘self’ data:Bildkällor
font-src‘self’ https: data:Typsnittskällor
object-src‘none’Blockerar plugins (Flash etc.)
frame-ancestors‘self’Vem som får bädda in sidan
base-uri‘self’Begränsar base-taggens URI
form-action‘self’Formulärsändningsmål
upgrade-insecure-requestsAktiveratUppgraderar HTTP till HTTPS

Om din app laddar resurser från CDN, Google Fonts eller tredjepartstjänster behöver du utöka standardkonfigurationen. Skapa src/middleware/security.js med anpassad CSP:

import helmet from 'helmet';

const NODE_ENV = process.env.NODE_ENV || 'development';
const isDev = NODE_ENV === 'development';

export const helmetConfig = helmet({
  contentSecurityPolicy: {
    useDefaults: true,
    directives: {
      // Tillåt skript från din server och CDN
      scriptSrc: [
        "'self'",
        "https://cdn.jsdelivr.net",
        isDev ? "'unsafe-eval'" : null,
      ].filter(Boolean),

      // Tillåt stilar från Google Fonts
      styleSrc: [
        "'self'",
        "https://fonts.googleapis.com",
        "'unsafe-inline'", // Behövs för många CSS-ramverk
      ],

      // Tillåt typsnitt från Google Fonts
      fontSrc: [
        "'self'",
        "https://fonts.gstatic.com",
        "data:",
      ],

      // Tillåt bilder från din server och data-URIer
      imgSrc: ["'self'", "data:", "https:"],

      // Tillåt API-anrop till din egen backend
      connectSrc: ["'self'", "https://api.dinapp.se"],

      // Blockera alltid plugins
      objectSrc: ["'none'"],

      // Uppgradera insäkra förfrågningar (HTTP → HTTPS)
      upgradeInsecureRequests: isDev ? [] : null,
    },
  },
});

Importera sedan den anpassade konfigurationen i app.js:

import { helmetConfig } from './middleware/security.js';

// Ersätt app.use(helmet()) med:
app.use(helmetConfig);

CSP kan verifieras med Mozillas CSP Evaluator, som pekar ut svaga direktiv och potentiella kringgåenden. Länk till MDN:s dokumentation om Content-Security-Policy.

Steg 5: Strict-Transport-Security (HSTS) för HTTPS

Strict-Transport-Security (HSTS) berättar för webbläsaren att alltid använda HTTPS för din domän, även om användaren skriver http://. Helmets standardvärde är max-age=15552000; includeSubDomains, vilket motsvarar 180 dagar.

För produktion bör du höja max-age till 1 år (31536000 sekunder) och lägga till preload-direktivet om du vill registrera din domän i webbläsarnas inbyggda HSTS-listor. HSTS-preload innebär att webbläsare vet att använda HTTPS för din domän redan innan den första HTTP-förfrågan, vilket eliminerar den initiala osäkra anslutningen helt och hållet.

Krav för att registreras i HSTS Preload List:

  • max-age på minst 31536000 sekunder (1 år)
  • Direktivet includeSubDomains måste vara inkluderat
  • Direktivet preload måste finnas i headern
  • Alla subdomäner måste stödja HTTPS
export const helmetConfig = helmet({
  // HSTS-konfiguration för produktion
  strictTransportSecurity: {
    maxAge: 31536000,        // 1 år (31 536 000 sekunder)
    includeSubDomains: true, // Gäller alla subdomäner
    preload: true,           // Registreras i HSTS Preload List
  },

  // Övrig konfiguration...
  contentSecurityPolicy: { /* ... */ },
});

Viktigt: Aktivera aldrig HSTS-preload på en domän där alla subdomäner inte stöder HTTPS. Om en subdomän saknar TLS-certifikat kommer användare dit att se ett certifikatfel som inte kan kringgås, eftersom webbläsaren vägrar HTTP-anslutningen. Läs mer om HSTS på MDN:s HSTS-dokumentation.

Under lokal utveckling, inaktivera HSTS för att undvika problem:

export const helmetConfig = helmet({
  strictTransportSecurity: isDev ? false : {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
});

Steg 6: X-Frame-Options mot clickjacking

Clickjacking är en attack där din sida bäddas in i en osynlig iframe på en angriparsida. Angriparen lägger sedan en genomskinlig overlay ovanpå sin sida och lurar användare att klicka på knappar de inte ser, till exempel “Skicka betalning” eller “Godkänn behörighet”.

Helmet sätter X-Frame-Options: SAMEORIGIN som standard, vilket tillåter att sidan bäddas in i iframe:ar på samma domän men blockerar det på andra domäner. Tre möjliga värden:

  • DENY: Blockerar all inbäddning i iframe:ar, även på samma domän.
  • SAMEORIGIN: Tillåter inbäddning från samma domän (Helmets standard).
  • ALLOW-FROM uri: Tillåter inbäddning från specifik URI (dåligt stöd i moderna webbläsare).

Om din app aldrig behöver bäddas in i en iframe (vilket gäller de flesta backend-API:er och admin-paneler), sätt DENY:

export const helmetConfig = helmet({
  frameguard: {
    action: 'deny', // Blockerar ALL iframe-inbäddning
  },
});

Modern CSP med frame-ancestors-direktivet ger mer flexibel kontroll och är det föredragna alternativet i nya appar. Helmets standard-CSP innehåller redan frame-ancestors 'self', vilket ger samma skydd som SAMEORIGIN. Du kan lita på att standardkonfigurationen skyddar dig mot clickjacking utan ytterligare konfiguration.

Steg 7: X-Content-Type-Options mot MIME-sniffning

MIME-sniffning inträffar när webbläsaren ignorerar den Content-Type-header som servern skickar och istället gissar filtypen baserat på innehållet. En angripare som laddar upp en fil med tillägget .jpg men med JavaScript-innehåll kan få webbläsaren att exekvera den som ett skript, trots att servern skickade Content-Type: image/jpeg.

Helmet sätter X-Content-Type-Options: nosniff som standard. Det är ett enkelt huvud med ett enda giltigt värde, nosniff, och det behöver sällan konfigureras manuellt. Helmets standardinställning är korrekt och behöver inte ändras.

En förutsättning är att din server sätter korrekta Content-Type-headers för alla resurser. Om din server sänder fel Content-Type och du har nosniff aktiverat kan CSS- och JavaScript-filer vägras av webbläsaren. Det är ett konfigurationsfel på serversidan, inte ett fel i Helmet.

Verifiera att headern finns i svaret:

curl -I http://localhost:3000/health | grep X-Content-Type
# Förväntat utdata:
# X-Content-Type-Options: nosniff

Mer teknisk bakgrund finns på MDN:s dokumentation om X-Content-Type-Options.

Steg 8: Referrer-Policy för informationsskydd

När en användare klickar på en länk skickar webbläsaren en Referer-header till destinationssidan som avslöjar vilken URL användaren kom från. Om din URL innehåller känslig information, som ett sessionstoken i query-strängen (/reset-password?token=abc123), kan det läcka till tredjepartstjänster via Referer-headern.

Helmet sätter Referrer-Policy: no-referrer som standard, vilket innebär att inga Referer-data skickas till externa sidor. Det är det mest restriktiva alternativet och det säkraste valet för de flesta applikationer.

Om din app behöver webbplatsanalys eller att externa tjänster känner till ursprungssidan, använd ett av de mer tillåtande alternativen:

PolicySkickar Referer tillInnehåller URL-sökväg
no-referrerIngenNej
same-originSamma domänJa
strict-originAlla (HTTPS-ursprung)Nej
strict-origin-when-cross-originAlla (HTTPS-ursprung)Ja (samma origin)
no-referrer-when-downgradeHTTPS till HTTPSJa

För appar med Google Analytics eller liknande verktyg är strict-origin-when-cross-origin ett bra kompromissalternativ som delar ursprungsdomänen men inte URL-sökvägen med externa sidor:

export const helmetConfig = helmet({
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin',
  },
});

Steg 9: Permissions-Policy för webbläsarfunktioner

Permissions-Policy (tidigare Feature-Policy) kontrollerar vilka webbläsarfunktioner din sida och inbäddade iframes får använda. Det handlar om funktioner som kamera, mikrofon, geolokalisering, betalnings-API:er och fullskärmsläge. Utan en policy kan tredjepartsskript som laddas på din sida begära kameratillgång utan din vetskap.

Helmet inkluderar Permissions-Policy i sin standardkonfiguration. För att anpassa vilka funktioner som tillåts, lägg till permissionsPolicy-konfigurationen:

export const helmetConfig = helmet({
  permissionsPolicy: {
    features: {
      // Tillåt geolokalisering för din app
      geolocation: ['self'],

      // Blockera kamera och mikrofon (om de inte behövs)
      camera: [],
      microphone: [],

      // Tillåt fullskärm från din domän
      fullscreen: ['self'],

      // Blockera betalnings-API:er från tredjeparter
      payment: ['self'],

      // Inaktivera USB-åtkomst
      usb: [],

      // Inaktivera Motion-sensorer
      accelerometer: [],
      gyroscope: [],
    },
  },
});

Tomma arrayer ([]) blockerar funktionen helt, inklusive för inbäddade iframes. ['self'] tillåter funktionen för din sida men inte för iframes som laddas från externa domäner.

Steg 10: Cross-Origin-isolation för processisolering

Tre Cross-Origin-relaterade huvuden förstärker webbläsarens processisolering och minskar risken för sidokanal-attacker som Spectre och Meltdown, vilka kan läsa data ur andra tabbars minnesområde.

Helmet sätter alla tre som standard:

  • Cross-Origin-Opener-Policy: same-origin: Isolerar din sida i sin egen webbläsarprocess, vilket förhindrar att andra sidor kan komma åt ditt fönsterobjekt via window.open().
  • Cross-Origin-Resource-Policy: same-origin: Förhindrar att externa sidor inkluderar dina resurser (bilder, skript, data) via <img>– eller <script>-taggar.
  • Origin-Agent-Cluster: ?1: Begär att webbläsaren placerar din sida i ett eget agentnoder, vilket ger starkare minnesisoleringen.

Om din app tillhandahåller resurser som ska kunna bäddas in av externa sidor (till exempel bilder i en CDN eller widgets), behöver du ändra Cross-Origin-Resource-Policy:

export const helmetConfig = helmet({
  // Tillåt cross-origin resursinkludering (för publika CDN-resurser)
  crossOriginResourcePolicy: {
    policy: 'cross-origin',
  },

  // Behåll opener-isolering
  crossOriginOpenerPolicy: {
    policy: 'same-origin',
  },
});

Steg 11: CSP med nonces för inline-skript

Helmets standard-CSP blockerar alla inline-skript (<script>...</script>) och inline-stilar utan källangivelse. Det är det korrekta beteendet ur säkerhetssynpunkt, men det bryter äldre applikationer som använder inline-skript flitigt.

Lösningen är nonces: en engångskod som genereras för varje förfrågan och bifogas både i CSP-headern och i inline-skripttaggarna. Webbläsaren accepterar bara inline-skript med matchande nonce, vilket omintetgör XSS-attacker utan att kräva att du konverterar alla inline-skript till externa filer.

import crypto from 'crypto';
import helmet from 'helmet';

export function createHelmetWithNonce() {
  return [
    // Middleware 1: Generera nonce per förfrågan
    (req, res, next) => {
      res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
      next();
    },

    // Middleware 2: Konfigurera Helmet med nonce-funktion
    helmet({
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          scriptSrc: [
            "'self'",
            // Funktionen anropas per förfrågan och returnerar nonce-värdet
            (req, res) => `'nonce-${res.locals.cspNonce}'`,
          ],
          // Inaktivera unsafe-inline om det var aktiverat
          styleSrc: ["'self'", "https:"],
        },
      },
    }),
  ];
}

Uppdatera app.js för att använda nonce-factory:

import { createHelmetWithNonce } from './middleware/security.js';

// Spread array-av-middleware
app.use(...createHelmetWithNonce());

// I din template-motor, använd nonce:
app.get('/', (req, res) => {
  res.send(`
    
    
      
        Demo
      
      
        
        
      
    
  `);
});

Nonce-värdet ändras vid varje förfrågan, vilket gör att en angripare inte kan förbereda en attack med ett känt nonce-värde. Utan nonce tvingas angripare kringgå hela CSP, vilket är avsevärt svårare.

Steg 12: Testa och verifiera säkerhetshuvudena

Verifiera att alla headers sätts korrekt med curl och Mozillas Observatory-verktyg. En korrekt konfigurerad app ska inte avslöja serverinformation och ska ha alla kritiska säkerhetshuvuden på plats.

Lokal verifiering med curl:

# Kontrollera alla headers
curl -sI http://localhost:3000/ | sort

# Kontrollera specifika headers
curl -sI http://localhost:3000/ | grep -E \
  "Content-Security-Policy|X-Frame-Options|X-Content-Type|Strict-Transport|Referrer-Policy"

# Kontrollera att X-Powered-By är borttagen
curl -sI http://localhost:3000/ | grep -i powered
# Ska ge tomt utdata - ingen X-Powered-By

Kontrollera med Node.js built-in http-modul:

// test-headers.js
import http from 'http';

const options = {
  hostname: 'localhost',
  port: 3000,
  path: '/',
  method: 'GET',
};

const req = http.request(options, (res) => {
  const required = [
    'content-security-policy',
    'x-frame-options',
    'x-content-type-options',
    'referrer-policy',
    'cross-origin-opener-policy',
  ];

  console.log('Säkerhetshuvuden som hittades:');
  required.forEach((header) => {
    const value = res.headers[header];
    const status = value ? '✓' : '✗';
    console.log(`  ${status} ${header}: ${value || 'SAKNAS'}`);
  });

  const powered = res.headers['x-powered-by'];
  console.log(`\nX-Powered-By ${powered ? '✗ LÄCKER INFO: ' + powered : '✓ borttagen'}`);
});

req.end();

För produktion, kör Mozillas Observatory-scanner på observatory.mozilla.org. Den analyserar din app mot 12 säkerhetskriterier och ger ett betyg från F till A+. En korrekt Helmet-konfiguration ger vanligtvis betyg B eller A direkt, med möjlighet att nå A+ med HSTS-preload och strict nonce-baserad CSP.

5 vanliga misstag med Helmet.js

Helmet.js är enkelt att installera men lätt att konfigurera fel. De här felen ser vi mest i produktionsappar.

Misstag 1: Anropa helmet() efter routes

Om du anropar app.use(helmet()) efter att du definierat dina routes gäller säkerhetshuvudena inte för de routes som definierats före helmet-anropet. Express-middleware körs i den ordning du definierar dem. Helmet måste vara det första middleware i kedjan, direkt efter att du skapat Express-appen.

// FEL: Helmet efter routes
app.get('/api/users', handler);
app.use(helmet()); // Påverkar INTE /api/users

// RÄTT: Helmet före routes
app.use(helmet());
app.get('/api/users', handler);

Misstag 2: Aktivera HSTS-preload utan fullständigt HTTPS-stöd

HSTS-preload är irreversibelt i 1 år efter att du registrerat dig. Om en subdomän saknar HTTPS-certifikat och du har includeSubDomains: true och preload: true, låser du ut användare från den subdomänen permanent tills preload-listan uppdateras (vilket kan ta månader). Kontrollera att alla subdomäner har giltiga certifikat innan du aktiverar preload.

Misstag 3: Lägga till ‘unsafe-inline’ i script-src för att lösa CSP-fel

Det är lockande att lösa CSP-blockeringsfel genom att lägga till 'unsafe-inline' i script-src. Det omintetgör hela syftet med CSP och öppnar upp för XSS-attacker. Rätt lösning är antingen att flytta inline-skript till externa filer eller att implementera nonces (se steg 11).

// FEL: Omintetgör XSS-skyddet
scriptSrc: ["'self'", "'unsafe-inline'"],

// RÄTT: Använd nonces
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],

Misstag 4: Aktivera HSTS lokalt

Om du aktiverar HSTS under lokal utveckling (localhost) och sedan provar att komma åt appen via HTTP på samma port senare, kan webbläsaren vägra HTTP-anslutningen i upp till 180 dagar. Inaktivera alltid HSTS i development-miljön med en NODE_ENV-kontroll.

Misstag 5: Glömma att konfigurera CSP för Single-Page Applications

React, Vue och Angular-appar använder ofta eval() för hot-reloading under utveckling och kan behöva inline-stilar för CSS-in-JS-lösningar. Helmets standard-CSP blockerar båda. Utan en miljöspecifik CSP-konfiguration fungerar appen i produktion men inte under utveckling, eller vice versa.

// CSP för React/Vue i development
const devDirectives = isDev ? {
  scriptSrc: ["'self'", "'unsafe-eval'"], // Hot-reload kräver eval
  styleSrc: ["'self'", "'unsafe-inline'"], // CSS-in-JS
} : {};

export const helmetConfig = helmet({
  contentSecurityPolicy: {
    useDefaults: true,
    directives: {
      ...devDirectives,
      // Produktionsdirektiv skriver alltid över dev-direktivet
      ...(isDev ? {} : {
        scriptSrc: ["'self'"],
        styleSrc: ["'self'"],
      }),
    },
  },
});

Felsökning: 8 vanliga problem och lösningar

De här problemen dyker upp i produktionsmiljöer och kan vara svåra att spåra utan en systematisk approach.

Problem 1: Bilder från CDN laddas inte

Symtom: Bilder från Cloudflare, AWS S3 eller liknande visas inte. Webbläsarens konsol visar CSP-fel som “Refused to load the image because it violates the following Content Security Policy directive: img-src ‘self’ data:”. Lösning: Lägg till CDN-domänen i imgSrc:

imgSrc: ["'self'", "data:", "https://cdn.dinapp.se", "https://*.cloudfront.net"],

Problem 2: Tredjepartsfonter visas inte

Google Fonts, Adobe Fonts och liknande kräver att du tillåter deras CDN-domäner i både fontSrc och styleSrc. En vanlig miss är att bara lägga till domänen i ett av direktiven.

styleSrc: ["'self'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"], // Glöms ofta

Problem 3: WebSocket-anslutningar blockeras

WebSocket-anslutningar (Socket.io, native WebSocket) kräver att connectSrc inkluderar rätt protokoll (ws:// eller wss://).

connectSrc: ["'self'", "ws://localhost:3000", "wss://api.dinapp.se"],

Problem 4: Stripe eller Braintree-betalningar fungerar inte

Betaltjänster laddar iframe:ar och skript från externa domäner. Stripe kräver att du tillåter https://js.stripe.com och https://hooks.stripe.com i scriptSrc och frameSrc.

scriptSrc: ["'self'", "https://js.stripe.com"],
frameSrc: ["'self'", "https://js.stripe.com", "https://hooks.stripe.com"],
connectSrc: ["'self'", "https://api.stripe.com"],

Problem 5: React hot-reload (HMR) slutar fungera

Vite och Create React App använder eval() och inline-skript under hot-reload. Lösning: Detektera development-miljön och tillåt 'unsafe-eval' enbart i development (se steg 4 och pitfall 5 ovan).

Problem 6: X-Powered-By fortfarande synlig

Om X-Powered-By: Express fortfarande syns i svarsrubrikerna, kontrollera om du explicit satt headern någonstans i koden. Helmet tar bort den via Express inbyggda app.disable('x-powered-by'), men om din kod sätter om headern manuellt vinner den. Helmet förhindrar inte aktivt att du sätter om den.

Problem 7: CORS-fel efter Helmet-installation

Helmet påverkar inte CORS-headers direkt, men Cross-Origin-Resource-Policy: same-origin kan orsaka att API-svar blockeras när ett separat front-end-projekt försöker hämta data. Lösning för separata front-end/back-end-projekt:

import cors from 'cors';

// CORS-konfiguration FÖRE Helmet
app.use(cors({ origin: 'https://app.dinapp.se' }));

// Ändra CORP för API-routes
app.use('/api', helmet({
  crossOriginResourcePolicy: { policy: 'same-site' },
}));

Problem 8: CSP-rapportöversvämning i loggarna

Om du aktiverar CSP:s report-to-direktiv utan att ha en rapportmottagare konfigurerad, eller om din CSP är för restriktiv, kan du få tusentals CSP-rapporter per sekund. Aktivera Report-Only-läget under testning för att samla rapporter utan att blockera resurser:

export const helmetConfig = helmet({
  contentSecurityPolicy: {
    useDefaults: true,
    // Report-Only: Rapporterar men blockerar INTE
    reportOnly: isDev, // Sätt till false i produktion
    directives: {
      reportTo: 'csp-endpoint',
    },
  },
});

Avancerade tekniker

När du behärskar grundkonfigurationen finns ytterligare tekniker för att maximera säkerheten i komplexa applikationer.

Route-specifik Helmet-konfiguration: Olika delar av din app kan behöva olika CSP-regler. Admin-panelen kan ha en striktare CSP än den publika hemsidan.

import helmet from 'helmet';

const strictHelmet = helmet({
  contentSecurityPolicy: {
    useDefaults: true,
    directives: { scriptSrc: ["'self'"] },
  },
});

const relaxedHelmet = helmet({
  contentSecurityPolicy: {
    useDefaults: true,
    directives: {
      scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
      styleSrc: ["'self'", "https:", "'unsafe-inline'"],
    },
  },
});

// Strikt CSP för admin
app.use('/admin', strictHelmet);

// Mer tillåtande CSP för publika sidor med CDN-resurser
app.use('/public', relaxedHelmet);

CSP-hashing istället för nonces: Om inline-skript inte ändras mellan förfrågningar kan du använda SHA-256-hashen av skriptets innehåll istället för nonces. Beräkna hashen och lägg till den i CSP-konfigurationen:

import crypto from 'crypto';

// Beräkna SHA-256-hash av inline-skriptets innehåll
const inlineScript = "console.log('Hälsning');";
const hash = crypto.createHash('sha256')
  .update(inlineScript)
  .digest('base64');

// Lägg till hashen i scriptSrc
scriptSrc: ["'self'", `'sha256-${hash}'`],

Integration med loggning och övervakning: Sätt upp CSP-rapportering till en endpoint som loggar eller alertar på CSP-brott i produktion. Det hjälper dig identifiera XSS-attackförsök och felkonfigurerad CSP.

Komplett projekt med alla 12 steg

Här är den kompletta applikationen som integrerar alla steg i guiden. Kopiera och klistra in för ett fullt fungerande produktionsklart projekt.

// src/middleware/security.js - Komplett Helmet-konfiguration
import helmet from 'helmet';
import crypto from 'crypto';

const isDev = process.env.NODE_ENV === 'development';

export function createSecurityMiddleware() {
  const nonceMiddleware = (req, res, next) => {
    res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
    next();
  };

  const helmetMiddleware = helmet({
    // 1. Content Security Policy
    contentSecurityPolicy: {
      useDefaults: true,
      reportOnly: isDev,
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: [
          "'self'",
          isDev ? "'unsafe-eval'" : null,
          (req, res) => `'nonce-${res.locals.cspNonce}'`,
        ].filter(Boolean),
        styleSrc: ["'self'", "https:", "'unsafe-inline'"],
        imgSrc: ["'self'", "data:", "https:"],
        fontSrc: ["'self'", "https:", "data:"],
        connectSrc: ["'self'"],
        objectSrc: ["'none'"],
        frameAncestors: ["'self'"],
        baseUri: ["'self'"],
        formAction: ["'self'"],
        upgradeInsecureRequests: isDev ? null : [],
      },
    },

    // 2. HSTS
    strictTransportSecurity: isDev ? false : {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true,
    },

    // 3. Clickjacking
    frameguard: { action: 'sameorigin' },

    // 4. MIME-sniffning
    noSniff: true,

    // 5. Referrer
    referrerPolicy: { policy: 'strict-origin-when-cross-origin' },

    // 6. Cross-Origin isolation
    crossOriginOpenerPolicy: { policy: 'same-origin' },
    crossOriginResourcePolicy: { policy: 'same-origin' },

    // 7. DNS-prefetch
    dnsPrefetchControl: { allow: false },

    // 8. X-Powered-By tas bort automatiskt
    hidePoweredBy: true,
  });

  return [nonceMiddleware, helmetMiddleware];
}

// src/app.js - Komplett Express-app
import express from 'express';
import { createSecurityMiddleware } from './middleware/security.js';
import 'dotenv/config';

const app = express();
const PORT = process.env.PORT || 3000;

// Säkerhetsmiddleware FÖRST
app.use(...createSecurityMiddleware());

// Body parsers
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));

// Routes
app.get('/health', (req, res) => {
  res.json({
    status: 'OK',
    env: process.env.NODE_ENV,
    timestamp: new Date().toISOString(),
  });
});

app.get('/', (req, res) => {
  res.send(`


  
  
  Helmet.js Demo


  

Helmet.js skyddar den här sidan

`); }); // Felhantering app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Internt serverfel' }); }); app.listen(PORT, () => { console.log(`Säker server startar på http://localhost:${PORT}`); console.log(`Miljö: ${process.env.NODE_ENV || 'development'}`); });

Skapa en .env-fil för konfiguration:

PORT=3000
NODE_ENV=production

Starta och verifiera:

npm start
curl -sI http://localhost:3000/ | grep -c "security\|frame\|content\|referrer\|strict"
# Förväntat utdata: 5 eller fler matchningar

Relaterade artiklar

Fördjupa din Node.js-säkerhet

Vanliga frågor

Kan Helmet.js användas med NestJS och Fastify?

Ja. NestJS har inbyggt stöd för Helmet via app.use(helmet()) i main.ts. Fastify har ett eget paket, @fastify/helmet, som ger samma funktionalitet med Fastify-specifik middleware-integration. Konfigurationsoptionerna är identiska.

Hur påverkar Helmet.js prestanda?

Helmet.js lägger till en minimal mängd arbete per förfrågan, i praktiken mikrosekunder per request. Det arbetar enbart med HTTP-headers och utför inga externa anrop, databassökningar eller tung beräkning. I benchmarks märks inte Helmet-overhead i mätningar på applikationsnivå.

Ska jag använda Helmet i test-miljöer?

Det beror på testtypen. Integrationstester och end-to-end-tester bör köras med Helmet aktiverat för att verifiera att säkerhetskonfigurationen fungerar korrekt. Enhetstester av enskilda funktioner behöver sällan Helmet. Inaktivera HSTS och eventuellt CSP i testmiljöer för att förenkla testskrivning.

Räcker Helmet.js för att göra min app säker?

Nej. Helmet.js hanterar HTTP-säkerhetshuvuden och är ett viktigt lager i säkerhetsarbetet, men det ersätter inte indatavalidering, parametriserade databasfrågor, säker autentisering, rate limiting och regelbundna säkerhetsgranskningar. Se Helmet som ett av flera skyddslagret i en “defense in depth”-strategi.

Vilken är skillnaden mellan Helmet och en WAF (Web Application Firewall)?

Helmet sätter HTTP-svarshuvuden som instruerar webbläsaren om säkerhetspolicyer. En WAF analyserar inkommande trafik och blockerar skadliga förfrågningar på nätverksnivå innan de når din app. De kompletterar varandra. Helmet skyddar klientsidan, WAF:en skyddar serversidan.

Fungerar CSP med äldre webbläsare?

CSP Level 3 stöds av alla moderna webbläsare (Chrome 59+, Firefox 58+, Safari 15.4+, Edge 79+). Internet Explorer saknar stöd för CSP Level 2 och Level 3. Om du fortfarande stöder IE, kontrollera att din CSP inte innehåller direktiv som IE tolkar fel, och testa noggrant. Helmet sätter X-XSS-Protection till 0 (inaktiverat) för moderna webbläsare eftersom den äldre XSS-filterfunktionen kan utnyttjas av angripare.

Hur uppdaterar jag Helmet.js säkert?

Kör npm outdated helmet regelbundet för att kontrollera om uppdateringar finns. Läs release notes på helmetjs.github.io före uppgradering. Major-versioner kan ändra standardvärden för headers, vilket kan bryta din app om du förlitar dig på specifika standardvärden utan explicit konfiguration. Uppgradera i staging-miljön och testa noggrant med curl och Observatory-scanner innan du driftsätter i produktion.

Behöver jag Helmet om min app ligger bakom en omvänd proxy som Nginx?

Ja. Nginx kan sätta HSTS och X-Frame-Options, men CSP och nonce-hantering är applikationsspecifika och kräver att din Node.js-app hanterar dem. Det är dessutom bästa praxis att ha säkerhetshuvuden på applikationsnivå så att de gäller oavsett vilken infrastruktur som används. Helmet och Nginx-konfiguration är komplementära, inte alternativ.