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.
| Huvud | Standardvärde | Skyddar mot |
|---|---|---|
| Content-Security-Policy | default-src ‘self’; script-src ‘self’; object-src ‘none’ | XSS, skriptinjektion |
| Cross-Origin-Opener-Policy | same-origin | Cross-origin fönsteråtkomst |
| Cross-Origin-Resource-Policy | same-origin | Cross-origin resursläsning |
| Origin-Agent-Cluster | ?1 | Processisolering |
| Referrer-Policy | no-referrer | URL-informationsläckage |
| Strict-Transport-Security | max-age=15552000; includeSubDomains | HTTPS-nedgradering |
| X-Content-Type-Options | nosniff | MIME-sniffning |
| X-DNS-Prefetch-Control | off | DNS-förläsning |
| X-Download-Options | noopen | IE-filöppning |
| X-Frame-Options | SAMEORIGIN | Clickjacking |
| X-Permitted-Cross-Domain-Policies | none | Adobe Flash-policyer |
| X-Powered-By | Borttagen | Serverinformationsläckage |
| X-XSS-Protection | Inaktiverad (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 --versionfö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:
| Direktiv | Standardvärde | Beskrivning |
|---|---|---|
| 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-requests | Aktiverat | Uppgraderar 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
includeSubDomainsmåste vara inkluderat - Direktivet
preloadmå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:
| Policy | Skickar Referer till | Innehåller URL-sökväg |
|---|---|---|
| no-referrer | Ingen | Nej |
| same-origin | Samma domän | Ja |
| strict-origin | Alla (HTTPS-ursprung) | Nej |
| strict-origin-when-cross-origin | Alla (HTTPS-ursprung) | Ja (samma origin) |
| no-referrer-when-downgrade | HTTPS till HTTPS | Ja |
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
- OWASP Top 10 i Node.js: 12 steg, 30 min [2026] – Täcker alla tio OWASP-risker med Node.js-kod.
- Node.js Indatavalidering: 12 steg mot injektionsattacker [2026] – Validering med Zod och Joi mot injektionsattacker.
- OAuth 2.0 med PKCE i Node.js: 12 steg, 30 min [2026] – Implementera säker OAuth-autentisering.
- ECDH i Node.js: Elliptisk Kurva Diffie-Hellman i 12 steg [2026] – Kryptografisk nyckelutväxling i Node.js.
- BLAKE3 i Node.js: 5x Snabbare Hash än SHA-256 i 12 Steg [2026] – Nästa generations hashfunktion i Node.js.
- OpenSSL 3.5: nycklar och certifikat i 12 steg [2026] – TLS-certifikat och nycklar med OpenSSL.
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.



