{"id":102,"date":"2026-06-17T20:42:19","date_gmt":"2026-06-17T20:42:19","guid":{"rendered":"https:\/\/shattered.io\/se\/2026\/06\/17\/owasp-top-10-nodejs\/"},"modified":"2026-06-17T20:43:55","modified_gmt":"2026-06-17T20:43:55","slug":"owasp-top-10-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/se\/owasp-top-10-nodejs\/","title":{"rendered":"OWASP Top 10 i Node.js: 12 steg, 30 min [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">OWASP Top 10 \u00e4r den mest citerade s\u00e4kerhetsstandarden f\u00f6r webbapplikationer. Den senaste versionen, OWASP Top 10:2025, publicerades i november 2025 och identifierar tio kritiska s\u00e5rbarhetskategorier som drabbar virtuellt alla testade applikationer. I den h\u00e4r guiden implementerar du skydd mot samtliga tio kategorier i en Node.js\/Express.js-applikation, steg f\u00f6r steg, p\u00e5 30 minuter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Konsekvenserna av att ignorera OWASP \u00e4r m\u00e4tbara: Broken Access Control, som toppar listan, finns i n\u00e4ra 100 procent av testade applikationer. Security Misconfiguration (A02:2025) rapporterades i samtliga testade appar. Cryptographic Failures (A04:2025) har kartlagts i \u00f6ver 1,6 miljoner f\u00f6rekomster i offentliga databaser. Den h\u00e4r handledningen ger dig ett komplett Node.js-projekt med fungerande mot\u00e5tg\u00e4rder mot varje kategori.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vad-ar-owasp-top-102025\">Vad \u00e4r OWASP Top 10:2025?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">OWASP (Open Worldwide Application Security Project) \u00e4r en ideell organisation som publicerar \u00f6ppen s\u00e4kerhetsforskning. Deras Top 10-lista \u00e4r en bred sammanst\u00e4llning av de vanligaste och farligaste s\u00e4kerhetsriskerna mot webbapplikationer, baserad p\u00e5 bidragen fr\u00e5n hundratals organisationer och miljontals testade applikationer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">OWASP Top 10:2025 \u00e4r en uppdatering av 2021 \u00e5rs lista. Tre kategorier \u00e4r nya eller kraftigt omdefinierade: Security Misconfiguration kl\u00e4ttrade fr\u00e5n plats fem till plats tv\u00e5, Software Supply Chain Failures (A03:2025) \u00e4r en ny kategori som ers\u00e4tter &#8220;Using Components with Known Vulnerabilities&#8221;, och Injection sj\u00f6nk till femte plats trots att den fortfarande finns i 100 procent av testade applikationer.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Plats<\/th><th>Kategori<\/th><th>F\u00f6rekomst i testade appar<\/th><th>Antal kartlagda CWE<\/th><\/tr><\/thead><tbody><tr><td>A01<\/td><td>Broken Access Control<\/td><td>~100%<\/td><td>H\u00f6gst antal av alla kategorier<\/td><\/tr><tr><td>A02<\/td><td>Security Misconfiguration<\/td><td>100%<\/td><td>719 000+<\/td><\/tr><tr><td>A03<\/td><td>Software Supply Chain Failures<\/td><td>5,19% (genomsnittlig incidens)<\/td><td>215 000+ f\u00f6rekomster<\/td><\/tr><tr><td>A04<\/td><td>Cryptographic Failures<\/td><td>H\u00f6g<\/td><td>1 600 000+ f\u00f6rekomster<\/td><\/tr><tr><td>A05<\/td><td>Injection<\/td><td>100%<\/td><td>Flest CVE:er av alla kategorier<\/td><\/tr><tr><td>A06<\/td><td>Vulnerable and Outdated Components<\/td><td>H\u00f6g<\/td><td>Stor<\/td><\/tr><tr><td>A07<\/td><td>Identification and Authentication Failures<\/td><td>H\u00f6g<\/td><td>Stor<\/td><\/tr><tr><td>A08<\/td><td>Software and Data Integrity Failures<\/td><td>Medel<\/td><td>Medel<\/td><\/tr><tr><td>A09<\/td><td>Security Logging and Monitoring Failures<\/td><td>H\u00f6g<\/td><td>Stor<\/td><\/tr><tr><td>A10<\/td><td>Server-Side Request Forgery (SSRF)<\/td><td>\u00d6kande<\/td><td>Medel<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"forutsattningar\">F\u00f6ruts\u00e4ttningar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Innan du startar beh\u00f6ver du f\u00f6ljande installerat och konfigurerat:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Node.js 22.x LTS<\/strong> (minst 20.x, \u00e4ldre versioner som 17.x och 19.x har kritiska CVE:er som CVE-2025-23087 och CVE-2025-23088)<\/li>\n<li><strong>npm 10.x<\/strong> eller yarn 4.x<\/li>\n<li><strong>Express.js 4.21.x<\/strong> eller 5.x<\/li>\n<li>En texteditor (VS Code rekommenderas)<\/li>\n<li>Terminal med bash eller zsh<\/li>\n<li>Grundl\u00e4ggande kunskaper i JavaScript och HTTP<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Kontrollera din Node.js-version med:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node --version\nnpm --version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Om du k\u00f6r Node.js 20.x eller \u00e4ldre, uppgradera innan du forts\u00e4tter. I januari 2026 patchade Node.js-teamet \u00e5tta s\u00e5rbarheter i alla aktiva utg\u00e5vor, varav tre klassades som h\u00f6g allvarlighetsgrad. CVE-2025-55131 exponerade oinitaliserat heap-minne via <code>Buffer.alloc()<\/code> efter vm-timeout, vilket potentiellt l\u00e4cker l\u00f6senord och API-tokens i minnet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"projektstruktur-och-installation\">Projektstruktur och installation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa ett nytt projekt och installera de s\u00e4kerhetspaket du beh\u00f6ver f\u00f6r att t\u00e4cka hela OWASP Top 10:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir owasp-nodejs-demo && cd owasp-nodejs-demo\nnpm init -y\nnpm install express helmet express-rate-limit express-validator \\\n  bcrypt jsonwebtoken dotenv cors cookie-parser winston \\\n  express-mongo-sanitize hpp csurf\nnpm install --save-dev nodemon snyk<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Paketen t\u00e4cker f\u00f6ljande OWASP-kategorier:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Paket<\/th><th>Version (juni 2026)<\/th><th>OWASP-kategori<\/th><\/tr><\/thead><tbody><tr><td>helmet<\/td><td>8.x<\/td><td>A02: Security Misconfiguration<\/td><\/tr><tr><td>express-rate-limit<\/td><td>7.x<\/td><td>A07: Authentication Failures<\/td><\/tr><tr><td>express-validator<\/td><td>7.x<\/td><td>A05: Injection<\/td><\/tr><tr><td>bcrypt<\/td><td>5.x<\/td><td>A04: Cryptographic Failures<\/td><\/tr><tr><td>jsonwebtoken<\/td><td>9.x<\/td><td>A07: Authentication Failures<\/td><\/tr><tr><td>winston<\/td><td>3.x<\/td><td>A09: Logging and Monitoring Failures<\/td><\/tr><tr><td>express-mongo-sanitize<\/td><td>2.x<\/td><td>A05: Injection (NoSQL)<\/td><\/tr><tr><td>hpp<\/td><td>0.2.x<\/td><td>A05: HTTP Parameter Pollution<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-1-grundkonfiguration-med-helmet-a02\">Steg 1: Grundkonfiguration med helmet (A02)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Security Misconfiguration kl\u00e4ttrade till andraplatsen i OWASP Top 10:2025. Det inkluderar os\u00e4kra HTTP-headers, \u00f6ppna felmeddelanden och standardkonfigurationer som l\u00e4mnar k\u00e4nslig information exponerad. I Node.js\/Express skickas som standard headers som <code>X-Powered-By: Express<\/code>, vilket ber\u00e4ttar f\u00f6r angripare vilken teknikstack du anv\u00e4nder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa filen <code>app.js<\/code> och b\u00f6rja med helmet:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>require('dotenv').config();\nconst express = require('express');\nconst helmet = require('helmet');\nconst app = express();\n\n\/\/ A02: Security Misconfiguration - s\u00e4kra HTTP-headers\napp.use(helmet({\n  contentSecurityPolicy: {\n    directives: {\n      defaultSrc: [\"'self'\"],\n      styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n      scriptSrc: [\"'self'\"],\n      imgSrc: [\"'self'\", 'data:', 'https:'],\n    },\n  },\n  hsts: {\n    maxAge: 31536000,      \/\/ 1 \u00e5r i sekunder\n    includeSubDomains: true,\n    preload: true,\n  },\n  noSniff: true,\n  xssFilter: true,\n  referrerPolicy: { policy: 'strict-origin-when-cross-origin' },\n}));\n\n\/\/ Ta bort X-Powered-By helt\napp.disable('x-powered-by');\n\napp.use(express.json({ limit: '10kb' }));\napp.use(express.urlencoded({ extended: true, limit: '10kb' }));\n\nmodule.exports = app;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Helmet s\u00e4tter \u00e5tta s\u00e4kerhetskritiska headers automatiskt: <code>Content-Security-Policy<\/code>, <code>X-DNS-Prefetch-Control<\/code>, <code>X-Frame-Options<\/code>, <code>X-Content-Type-Options<\/code>, <code>Strict-Transport-Security<\/code>, <code>X-XSS-Protection<\/code>, <code>Referrer-Policy<\/code> och <code>Permissions-Policy<\/code>. Content-Security-Policy (CSP) \u00e4r det viktigaste skyddet mot XSS-attacker och begr\u00e4nsar vilka resurser webbl\u00e4saren f\u00e5r ladda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Begr\u00e4nsningen av request-storlek till 10 KB skyddar mot &#8220;JSON bomb&#8221;-attacker d\u00e4r en angripare skickar djupt nestade JSON-strukturer som tar enorma m\u00e4ngder minne att parsa. Express har inget standardgr\u00e4nsv\u00e4rde, vilket \u00e4r ett vanligt konfigurationsmisstag.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-2-broken-access-control-a01\">Steg 2: Broken Access Control (A01)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Broken Access Control \u00e4r den vanligaste kategorin i OWASP Top 10:2025 och finns i n\u00e4stan alla testade applikationer. Det inneb\u00e4r att anv\u00e4ndare kan utf\u00f6ra \u00e5tg\u00e4rder eller komma \u00e5t data utanf\u00f6r sina beh\u00f6righeter. Typiska fel \u00e4r att ID-parametrar i URL:er kan manipuleras (Insecure Direct Object Reference, IDOR), att rollkontroller saknas p\u00e5 API-niv\u00e5, eller att k\u00e4nsliga endpoints inte skyddas alls.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa middleware f\u00f6r rolbaserad \u00e5tkomstkontroll i <code>middleware\/auth.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const jwt = require('jsonwebtoken');\n\n\/\/ Verifiera JWT-token\nconst authenticate = (req, res, next) => {\n  const authHeader = req.headers.authorization;\n  if (!authHeader || !authHeader.startsWith('Bearer ')) {\n    return res.status(401).json({ error: 'Autentisering kr\u00e4vs' });\n  }\n\n  const token = authHeader.split(' ')[1];\n  try {\n    const decoded = jwt.verify(token, process.env.JWT_SECRET);\n    req.user = decoded;\n    next();\n  } catch (err) {\n    return res.status(401).json({ error: 'Ogiltig eller utg\u00e5ngen token' });\n  }\n};\n\n\/\/ Kontrollera rollbeh\u00f6righeter\nconst authorize = (...roles) => {\n  return (req, res, next) => {\n    if (!req.user || !roles.includes(req.user.role)) {\n      return res.status(403).json({ error: 'Beh\u00f6righet saknas' });\n    }\n    next();\n  };\n};\n\n\/\/ F\u00f6rhindra IDOR: verifiera att anv\u00e4ndaren \u00e4ger resursen\nconst checkResourceOwnership = (getOwnerId) => {\n  return async (req, res, next) => {\n    try {\n      const ownerId = await getOwnerId(req);\n      if (String(ownerId) !== String(req.user.id)) {\n        return res.status(403).json({ error: '\u00c5tkomst nekad' });\n      }\n      next();\n    } catch (err) {\n      next(err);\n    }\n  };\n};\n\nmodule.exports = { authenticate, authorize, checkResourceOwnership };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nyckelprincipen \u00e4r att implementera &#8220;deny by default&#8221;: alla endpoints kr\u00e4ver autentisering som standard, och specifika roller beviljas explicit. Utan denna princip riskerar du att en ny endpoint publiceras utan skydd. IDOR-skyddet i <code>checkResourceOwnership<\/code> s\u00e4kerst\u00e4ller att en inloggad anv\u00e4ndare inte kan l\u00e4sa eller modifiera en annan anv\u00e4ndares data bara genom att byta ID i URL:en.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-3-inputvalidering-mot-injection-a05\">Steg 3: Inputvalidering mot Injection (A05)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Injection \u00e4r OWASP A05:2025 och finns i 100 procent av testade applikationer. Det inkluderar SQL-injektion, NoSQL-injektion, OS-kommandoinjektion och LDAP-injektion. I Node.js-applikationer med MongoDB \u00e4r NoSQL-injektion lika allvarlig som SQL-injektion och missas ofta av utvecklare som tror att &#8220;ingen SQL = inget injektionsproblem&#8221;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa valideringsregler i <code>validators\/userValidator.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const { body, param, validationResult } = require('express-validator');\nconst mongoSanitize = require('express-mongo-sanitize');\nconst hpp = require('hpp');\n\n\/\/ Middleware: sanitera MongoDB-operatorer och HTTP-parameterf\u00f6rorening\nconst sanitizeInput = [\n  mongoSanitize(),   \/\/ tar bort $ och . fr\u00e5n req.body, req.query, req.params\n  hpp(),             \/\/ tar bort duplicerade query-parametrar\n];\n\n\/\/ Valideringsregler f\u00f6r registrering\nconst registerValidation = [\n  body('email')\n    .isEmail().withMessage('Ogiltig e-postadress')\n    .normalizeEmail()\n    .isLength({ max: 254 }).withMessage('E-postadress f\u00f6r l\u00e5ng'),\n  body('password')\n    .isLength({ min: 12, max: 128 }).withMessage('L\u00f6senord: 12\u2013128 tecken')\n    .matches(\/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])\/)\n    .withMessage('L\u00f6senord m\u00e5ste inneh\u00e5lla stora\/sm\u00e5 bokst\u00e4ver, siffra och specialtecken'),\n  body('username')\n    .trim()\n    .isAlphanumeric('sv-SE').withMessage('Anv\u00e4ndarnamn: bokst\u00e4ver och siffror')\n    .isLength({ min: 3, max: 30 }).withMessage('Anv\u00e4ndarnamn: 3\u201330 tecken')\n    .escape(),\n];\n\n\/\/ Hantera valideringsfel\nconst handleValidation = (req, res, next) => {\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    return res.status(400).json({\n      error: 'Valideringsfel',\n      details: errors.array().map(e => ({ field: e.path, message: e.msg })),\n    });\n  }\n  next();\n};\n\n\/\/ Blockera farliga tecken i s\u00f6kparametrar\nconst searchValidation = [\n  param('query')\n    .trim()\n    .escape()\n    .isLength({ max: 100 }).withMessage('S\u00f6kning f\u00f6r l\u00e5ng'),\n];\n\nmodule.exports = {\n  sanitizeInput,\n  registerValidation,\n  handleValidation,\n  searchValidation,\n};<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Express-mongo-sanitize tar bort MongoDB-operat\u00f6rer (<code>$<\/code> och <code>.<\/code>) fr\u00e5n indata, vilket f\u00f6rhindrar attacker som <code>{\"$gt\": \"\"}<\/code> i l\u00f6senordsf\u00e4ltet som annars returnerar alla anv\u00e4ndare. HPP (HTTP Parameter Pollution) skyddar mot att en angripare skickar duplicerade parametrar som <code>?role=user&role=admin<\/code> och d\u00e4rmed kringg\u00e5r rollkontroller.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-4-saker-losenordshantering-och-kryptografisk-styrka-a04\">Steg 4: S\u00e4ker l\u00f6senordshantering och kryptografisk styrka (A04)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cryptographic Failures (A04:2025) har kartlagts i \u00f6ver 1,6 miljoner f\u00f6rekomster. Det inkluderar svaga hashfunktioner f\u00f6r l\u00f6senord (MD5, SHA-1 utan saltning), os\u00e4ker lagring av k\u00e4nslig data, och d\u00e5lig nyckelhantering. I Node.js \u00e4r det vanligaste misstaget att anv\u00e4nda <code>crypto.createHash('md5')<\/code> f\u00f6r l\u00f6senord eller att lagra k\u00e4nslig data okrypterad i milj\u00f6variabler utan ordentlig nyckelrotation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const bcrypt = require('bcrypt');\nconst crypto = require('crypto');\n\nconst SALT_ROUNDS = 12; \/\/ Tar ~250ms, tillr\u00e4ckligt f\u00f6r att f\u00f6rsv\u00e5ra brute force\n\n\/\/ Hasha l\u00f6senord med bcrypt\nconst hashPassword = async (password) => {\n  return await bcrypt.hash(password, SALT_ROUNDS);\n};\n\n\/\/ Verifiera l\u00f6senord (constant-time j\u00e4mf\u00f6relse ing\u00e5r i bcrypt)\nconst verifyPassword = async (password, hash) => {\n  return await bcrypt.compare(password, hash);\n};\n\n\/\/ Generera kryptografiskt s\u00e4kert token (f\u00f6r l\u00f6senords\u00e5terst\u00e4llning, etc.)\nconst generateSecureToken = (bytes = 32) => {\n  return crypto.randomBytes(bytes).toString('hex');\n};\n\n\/\/ Kryptera k\u00e4nslig data med AES-256-GCM\nconst encryptSensitiveData = (plaintext, key) => {\n  const iv = crypto.randomBytes(16);\n  const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(key, 'hex'), iv);\n  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n  const authTag = cipher.getAuthTag();\n  return {\n    iv: iv.toString('hex'),\n    encrypted: encrypted.toString('hex'),\n    authTag: authTag.toString('hex'),\n  };\n};\n\nmodule.exports = { hashPassword, verifyPassword, generateSecureToken, encryptSensitiveData };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Bcrypt med 12 salt-rundor tar ungef\u00e4r 250 millisekunder per hash p\u00e5 modern h\u00e5rdvara. Det g\u00f6r brute force-attacker praktiskt om\u00f6jliga: en angripare som testar 1 miljon l\u00f6senord per sekund (m\u00f6jligt med GPU:er mot osaltad MD5) klarar bara 4 per sekund mot bcrypt med 12 rundor. AES-256-GCM \u00e4r det rekommenderade l\u00e4get f\u00f6r symmetrisk kryptering: det ger konfidentialitet (kryptering) och integritet (authenticated tag) i ett svep, och varje krypteringsoperation ska anv\u00e4nda ett unikt, slumpm\u00e4ssigt IV.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-5-rate-limiting-och-brute-force-skydd-a07\">Steg 5: Rate limiting och brute force-skydd (A07)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Identification and Authentication Failures (A07:2025) inkluderar svaga l\u00f6senordspolicyer, saknade skydd mot brute force, os\u00e4ker &#8220;gl\u00f6mt l\u00f6senord&#8221;-funktionalitet och exponerade sessions-ID:n. Rate limiting \u00e4r den enklaste och mest effektiva mot\u00e5tg\u00e4rden mot automatiserade inloggningsf\u00f6rs\u00f6k och credential stuffing-attacker.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const rateLimit = require('express-rate-limit');\n\n\/\/ Generell rate limit f\u00f6r alla endpoints\nconst generalLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, \/\/ 15 minuter\n  max: 100,                   \/\/ max 100 anrop per f\u00f6nster\n  standardHeaders: 'draft-7',\n  legacyHeaders: false,\n  message: { error: 'F\u00f6r m\u00e5nga anrop, f\u00f6rs\u00f6k igen om 15 minuter' },\n});\n\n\/\/ Strikt limit f\u00f6r autentiseringsendpoints\nconst authLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,          \/\/ max 5 inloggningsf\u00f6rs\u00f6k per 15 minuter\n  skipSuccessfulRequests: true,\n  standardHeaders: 'draft-7',\n  legacyHeaders: false,\n  message: { error: 'Kontot tillf\u00e4lligt l\u00e5st, f\u00f6rs\u00f6k igen om 15 minuter' },\n});\n\n\/\/ Limit f\u00f6r l\u00f6senords\u00e5terst\u00e4llning\nconst passwordResetLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, \/\/ 1 timme\n  max: 3,\n  message: { error: 'Max 3 l\u00f6senords\u00e5terst\u00e4llningar per timme' },\n});\n\nmodule.exports = { generalLimiter, authLimiter, passwordResetLimiter };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Credential stuffing-attacker anv\u00e4nder l\u00e4ckta l\u00f6senordsdatabaser (2025 rapporterades 1,8 miljarder stulna inloggningsuppgifter) och pr\u00f6var dem mot din applikation. Med en rate limit p\u00e5 5 f\u00f6rs\u00f6k per 15 minuter tar det en angripare \u00f6ver 3 \u00e5r att testa en lista med 10 000 l\u00f6senord. Utan rate limiting kan en angripare med botnet prova 100 000 kombinationer per minut.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-6-jwt-sakerhet-och-sessions-hantering\">Steg 6: JWT-s\u00e4kerhet och sessions-hantering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Os\u00e4ker JWT-implementering \u00e4r ett av de vanligaste autentiseringsfelen i Node.js-applikationer. Typiska misstag inkluderar algoritmen <code>none<\/code> som godtas, f\u00f6r l\u00e5nga token-livsl\u00e4ngar, och hemliga nycklar h\u00e5rdkodade i k\u00e4llkoden. Skapa <code>services\/tokenService.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const jwt = require('jsonwebtoken');\nconst crypto = require('crypto');\n\nif (!process.env.JWT_SECRET || process.env.JWT_SECRET.length < 32) {\n  throw new Error('JWT_SECRET m\u00e5ste vara minst 32 tecken l\u00e5ng');\n}\n\nconst ACCESS_TOKEN_EXPIRY = '15m';  \/\/ kort livsl\u00e4ngd\nconst REFRESH_TOKEN_EXPIRY = '7d';\n\nconst generateTokens = (userId, role) => {\n  const accessToken = jwt.sign(\n    { id: userId, role, type: 'access' },\n    process.env.JWT_SECRET,\n    {\n      expiresIn: ACCESS_TOKEN_EXPIRY,\n      algorithm: 'HS256',\n      issuer: 'myapp',\n      audience: 'myapp-users',\n    }\n  );\n\n  const refreshToken = jwt.sign(\n    { id: userId, type: 'refresh', jti: crypto.randomUUID() },\n    process.env.JWT_REFRESH_SECRET,\n    { expiresIn: REFRESH_TOKEN_EXPIRY, algorithm: 'HS256' }\n  );\n\n  return { accessToken, refreshToken };\n};\n\nconst verifyToken = (token, secret) => {\n  return jwt.verify(token, secret, {\n    algorithms: ['HS256'],   \/\/ till\u00e5t aldrig 'none'\n    issuer: 'myapp',\n    audience: 'myapp-users',\n  });\n};\n\nmodule.exports = { generateTokens, verifyToken };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Access tokens s\u00e4tts till 15 minuters livsl\u00e4ngd, vilket begr\u00e4nsar exponeringstiden om ett token komprometteras. Refresh tokens med 7 dagars livsl\u00e4ngd lagras s\u00e4kert i HTTP-only cookies (inte i localStorage som \u00e4r \u00e5tkomligt via JavaScript och XSS-attacker). Algoritmen specificeras explicit som <code>['HS256']<\/code> i <code>algorithms<\/code>-arrayen, vilket f\u00f6rhindrar &#8220;alg: none&#8221;-attacker d\u00e4r en angripare skapar ett token utan signatur.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-7-beroendehantering-och-supply-chain-sakerhet-a03-och-a06\">Steg 7: Beroendehantering och supply chain-s\u00e4kerhet (A03 och A06)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Software Supply Chain Failures \u00e4r den nyaste kategorin i OWASP Top 10:2025 och den som 50 procent av s\u00e4kerhetsforskare rankar som det allvarligaste framtidshotet. Kategorin t\u00e4cker komprometterade byggmilj\u00f6er, skadliga npm-paket och os\u00e4kra CI\/CD-pipelines. CVE-2025-6514 i paketet mcp-remote m\u00f6jliggjorde Remote Code Execution via OS-kommandoinjektion i OAuth-handskaken.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa en automatiserad s\u00e4kerhetscheck i <code>package.json<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"scripts\": {\n    \"start\": \"node server.js\",\n    \"dev\": \"nodemon server.js\",\n    \"security:audit\": \"npm audit --audit-level=high\",\n    \"security:scan\": \"npx snyk test\",\n    \"security:fix\": \"npm audit fix\",\n    \"prestart\": \"npm audit --audit-level=critical\"\n  },\n  \"engines\": {\n    \"node\": \">=22.0.0\"\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L\u00e4gg till en <code>.npmrc<\/code>-fil f\u00f6r att blockera os\u00e4kra installations-scenarios:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .npmrc\naudit=true\nfund=false\nignore-scripts=false\nsave-exact=true          # pinnar exakta versioner, f\u00f6rhindrar ov\u00e4ntade uppgraderingar\npackage-lock=true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Med <code>save-exact=true<\/code> sparas exakt version i <code>package.json<\/code> (t.ex. <code>\"helmet\": \"8.0.0\"<\/code>) i st\u00e4llet f\u00f6r <code>\"^8.0.0\"<\/code>. Det f\u00f6rhindrar att en komprometterad patchversion (som vid Polyfill.io-attacken 2024 som drabbade 100 000 webbplatser) installeras automatiskt. L\u00e4gg alltid <code>package-lock.json<\/code> i versionskontroll och verifiera integriteten med <code>npm ci<\/code> i CI\/CD i st\u00e4llet f\u00f6r <code>npm install<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-8-saker-loggning-a09\">Steg 8: S\u00e4ker loggning (A09)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Security Logging and Monitoring Failures (A09:2025) \u00e4r ett underskattat problem. Utan tillr\u00e4cklig loggning kan ett intr\u00e5ng p\u00e5g\u00e5 i m\u00e5nader utan att uppt\u00e4ckas, som vid de flesta stora datal\u00e4ckor. Felet g\u00e5r ocks\u00e5 \u00e5t det andra h\u00e5llet: loggar som inneh\u00e5ller l\u00f6senord, tokens eller personuppgifter i klartext skapar en ny s\u00e5rbarhet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa en s\u00e4ker logger i <code>utils\/logger.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const winston = require('winston');\n\n\/\/ Maskera k\u00e4nslig data i loggar\nconst maskSensitiveData = (obj) => {\n  if (!obj || typeof obj !== 'object') return obj;\n  const sensitive = ['password', 'token', 'secret', 'authorization', 'cookie', 'creditCard'];\n  const masked = { ...obj };\n  for (const key of Object.keys(masked)) {\n    if (sensitive.some(s => key.toLowerCase().includes(s))) {\n      masked[key] = '[MASKERAD]';\n    } else if (typeof masked[key] === 'object') {\n      masked[key] = maskSensitiveData(masked[key]);\n    }\n  }\n  return masked;\n};\n\nconst logger = winston.createLogger({\n  level: process.env.LOG_LEVEL || 'info',\n  format: winston.format.combine(\n    winston.format.timestamp(),\n    winston.format.errors({ stack: true }),\n    winston.format.json(),\n  ),\n  transports: [\n    new winston.transports.File({ filename: 'logs\/security.log', level: 'warn' }),\n    new winston.transports.File({ filename: 'logs\/combined.log' }),\n  ],\n});\n\nif (process.env.NODE_ENV !== 'production') {\n  logger.add(new winston.transports.Console({ format: winston.format.simple() }));\n}\n\n\/\/ Logga s\u00e4kerhetsh\u00e4ndelse\nconst logSecurityEvent = (event, req, extra = {}) => {\n  logger.warn({\n    event,\n    ip: req.ip,\n    userId: req.user?.id || 'anonym',\n    userAgent: req.headers['user-agent'],\n    path: req.path,\n    method: req.method,\n    ...maskSensitiveData(extra),\n  });\n};\n\nmodule.exports = { logger, logSecurityEvent };<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-9-ssrf-skydd-a10\">Steg 9: SSRF-skydd (A10)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Server-Side Request Forgery (SSRF) \u00e4r en kategori p\u00e5 stark uppg\u00e5ng och landade p\u00e5 tiondeplats i OWASP Top 10:2025. En SSRF-s\u00e5rbarhet uppst\u00e5r n\u00e4r applikationen g\u00f6r HTTP-anrop till URL:er kontrollerade av anv\u00e4ndaren, vilket kan ge en angripare tillg\u00e5ng till interna tj\u00e4nster som metadata-tj\u00e4nsten p\u00e5 AWS (169.254.169.254) eller interna databaser.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const dns = require('dns').promises;\nconst net = require('net');\n\n\/\/ Lista blockerade privata IP-intervall\nconst BLOCKED_RANGES = [\n  \/^127\\.\/,                    \/\/ localhost\n  \/^10\\.\/,                     \/\/ privat n\u00e4t\n  \/^172\\.(1[6-9]|2\\d|3[01])\\.\/,  \/\/ privat n\u00e4t\n  \/^192\\.168\\.\/,               \/\/ privat n\u00e4t\n  \/^169\\.254\\.\/,               \/\/ link-local (AWS metadata!)\n  \/^::1$\/,                     \/\/ IPv6 localhost\n  \/^fc00:\/,                    \/\/ IPv6 privat\n  \/^fe80:\/,                    \/\/ IPv6 link-local\n];\n\nconst isPrivateIP = (ip) => BLOCKED_RANGES.some(re => re.test(ip));\n\n\/\/ Validera URL innan externt anrop\nconst validateExternalUrl = async (urlString) => {\n  let url;\n  try {\n    url = new URL(urlString);\n  } catch {\n    throw new Error('Ogiltig URL');\n  }\n\n  \/\/ Till\u00e5t bara HTTPS\n  if (url.protocol !== 'https:') {\n    throw new Error('Endast HTTPS till\u00e5ts');\n  }\n\n  \/\/ L\u00f6s upp hostname och kontrollera IP\n  const addresses = await dns.lookup(url.hostname, { all: true });\n  for (const { address } of addresses) {\n    if (isPrivateIP(address)) {\n      throw new Error('\u00c5tkomst till interna adresser \u00e4r f\u00f6rbjuden');\n    }\n  }\n\n  return url;\n};\n\nmodule.exports = { validateExternalUrl };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">AWS EC2-metadata-tj\u00e4nsten p\u00e5 <code>169.254.169.254<\/code> \u00e4r det klassiska SSRF-m\u00e5let och kan ge en angripare tillg\u00e5ng till IAM-credentials med full AWS-kontobeh\u00f6righet. Valideringen l\u00f6ser upp DNS-namnet och kontrollerar att den faktiska IP-adressen inte \u00e4r i ett privat intervall, vilket f\u00f6rhindrar DNS-rebinding-attacker d\u00e4r ett publikt dom\u00e4nnamn pekar p\u00e5 en privat IP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-10-integritetsskydd-a08\">Steg 10: Integritetsskydd (A08)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Software and Data Integrity Failures (A08:2025) inkluderar os\u00e4kra deserialisering, CI\/CD-kompromisser och autoUpgrade utan integritetskontroll. I Node.js \u00e4r det vanligaste felet att anv\u00e4nda <code>eval()<\/code>, <code>new Function()<\/code> eller <code>JSON.parse()<\/code> p\u00e5 op\u00e5litlig indata utan validering.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const crypto = require('crypto');\n\n\/\/ Generera HMAC-signatur f\u00f6r data (t.ex. webhooks)\nconst signData = (data, secret) => {\n  const payload = typeof data === 'object' ? JSON.stringify(data) : String(data);\n  return crypto.createHmac('sha256', secret).update(payload).digest('hex');\n};\n\n\/\/ Verifiera webhook-signatur (constant-time j\u00e4mf\u00f6relse)\nconst verifyWebhookSignature = (payload, signature, secret) => {\n  const expected = signData(payload, secret);\n  const expectedBuf = Buffer.from(expected, 'hex');\n  const receivedBuf = Buffer.from(signature, 'hex');\n\n  if (expectedBuf.length !== receivedBuf.length) return false;\n  return crypto.timingSafeEqual(expectedBuf, receivedBuf);\n};\n\n\/\/ S\u00e4ker JSON-parsning utan prototype pollution\nconst safeJsonParse = (str) => {\n  const parsed = JSON.parse(str);\n  if (parsed !== null && typeof parsed === 'object') {\n    \/\/ F\u00f6rhindra prototype pollution\n    if ('__proto__' in parsed || 'constructor' in parsed) {\n      throw new Error('Misst\u00e4nkt JSON-struktur');\n    }\n  }\n  return parsed;\n};\n\nmodule.exports = { signData, verifyWebhookSignature, safeJsonParse };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>crypto.timingSafeEqual()<\/code> \u00e4r avg\u00f6rande f\u00f6r signaturj\u00e4mf\u00f6relser. En vanlig str\u00e4ngkomparation i JavaScript (<code>a === b<\/code>) avbryts s\u00e5 snart den hittar ett tecken som skiljer sig, vilket g\u00f6r det m\u00f6jligt att m\u00e4ta svarstiden och p\u00e5 s\u00e5 vis gissa fram signaturen bit f\u00f6r bit (timing attack). Den tidss\u00e4kra versionen tar alltid lika l\u00e5ng tid oavsett var skillnaden finns.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-11-cors-och-cookie-sakerhet\">Steg 11: CORS och Cookie-s\u00e4kerhet<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Felkonfigurerad CORS \u00e4r ett av de vanligaste s\u00e4kerhetsfelen som faller under A02: Security Misconfiguration. Standardinst\u00e4llningen <code>origin: '*'<\/code> till\u00e5ter varje webbplats att g\u00f6ra autentiserade anrop till din API, vilket kan kombineras med XSS f\u00f6r att stj\u00e4la data. Cookies utan r\u00e4tt flaggor \u00e4r l\u00e4sbara via JavaScript och kan f\u00f6ras vidare via HTTP.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const cors = require('cors');\nconst cookieParser = require('cookie-parser');\n\n\/\/ CORS: till\u00e5t bara definierade ursprung\nconst corsOptions = {\n  origin: (origin, callback) => {\n    const allowed = (process.env.ALLOWED_ORIGINS || '').split(',');\n    if (!origin || allowed.includes(origin)) {\n      callback(null, true);\n    } else {\n      callback(new Error(`CORS blockerat f\u00f6r ursprung: ${origin}`));\n    }\n  },\n  credentials: true,        \/\/ till\u00e5t cookies med cross-origin-anrop\n  methods: ['GET', 'POST', 'PUT', 'DELETE'],\n  allowedHeaders: ['Content-Type', 'Authorization'],\n  maxAge: 86400,            \/\/ cache preflight 24 timmar\n};\n\napp.use(cors(corsOptions));\napp.use(cookieParser(process.env.COOKIE_SECRET));\n\n\/\/ S\u00e4ker cookie f\u00f6r refresh token\nconst setRefreshTokenCookie = (res, token) => {\n  res.cookie('refreshToken', token, {\n    httpOnly: true,         \/\/ ej \u00e5tkomlig via JavaScript\n    secure: true,           \/\/ bara \u00f6ver HTTPS\n    sameSite: 'strict',     \/\/ blockerar CSRF\n    maxAge: 7 * 24 * 60 * 60 * 1000,  \/\/ 7 dagar\n    signed: true,           \/\/ HMAC-signerad med COOKIE_SECRET\n    path: '\/api\/auth',      \/\/ begr\u00e4nsad s\u00f6kv\u00e4g\n  });\n};<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-12-komplett-applikation-och-felhantering\">Steg 12: Komplett applikation och felhantering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Samla allt i en komplett <code>server.js<\/code> med s\u00e4ker felhantering. Felmeddelanden mot klienten ska aldrig exponera stack traces, databasfel eller interna tekniska detaljer som en angripare kan anv\u00e4nda f\u00f6r rekognosering.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>require('dotenv').config();\nconst app = require('.\/app');\nconst { generalLimiter } = require('.\/middleware\/rateLimiter');\nconst { logger } = require('.\/utils\/logger');\nconst authRoutes = require('.\/routes\/auth');\nconst userRoutes = require('.\/routes\/users');\nconst { sanitizeInput } = require('.\/validators\/userValidator');\n\nconst PORT = process.env.PORT || 3000;\n\n\/\/ Global sanitering\napp.use(sanitizeInput);\n\n\/\/ Rate limiting p\u00e5 alla routes\napp.use('\/api\/', generalLimiter);\n\n\/\/ Routes\napp.use('\/api\/auth', authRoutes);\napp.use('\/api\/users', userRoutes);\n\n\/\/ 404-hantering (avsl\u00f6ja inte teknisk info)\napp.use((req, res) => {\n  res.status(404).json({ error: 'Resursen hittades inte' });\n});\n\n\/\/ Global felhantering\napp.use((err, req, res, next) => {\n  logger.error({\n    message: err.message,\n    stack: err.stack,\n    path: req.path,\n    method: req.method,\n    ip: req.ip,\n  });\n\n  \/\/ Skicka aldrig intern felinformation till klienten\n  if (process.env.NODE_ENV === 'production') {\n    res.status(err.status || 500).json({ error: 'Internt serverfel' });\n  } else {\n    res.status(err.status || 500).json({\n      error: err.message,\n      stack: err.stack,\n    });\n  }\n});\n\napp.listen(PORT, () => {\n  logger.info(`Server startad p\u00e5 port ${PORT} i ${process.env.NODE_ENV}-l\u00e4ge`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa filen <code>.env<\/code> med alla hemliga v\u00e4rden. L\u00e4gg till <code>.env<\/code> i din <code>.gitignore<\/code> direkt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .env (l\u00e4gg till .env i .gitignore!)\nNODE_ENV=development\nPORT=3000\nJWT_SECRET=minst-32-tecken-langt-slumpmassigt-hemligt-varde\nJWT_REFRESH_SECRET=ett-annat-minst-32-tecken-langt-varde\nCOOKIE_SECRET=cookie-signing-hemligt-varde-minst-32-tecken\nALLOWED_ORIGINS=http:\/\/localhost:3001,https:\/\/din-app.se\nLOG_LEVEL=info<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"verifiering-kora-applikationen\">Verifiering: k\u00f6ra applikationen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Starta utvecklingsservern och verifiera att s\u00e4kerhetsheaders \u00e4r p\u00e5 plats:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm run dev\n\n# I ett annat terminalf\u00f6nster, kontrollera headers:\ncurl -I http:\/\/localhost:3000\/api\/auth\/login\n\n# F\u00f6rv\u00e4ntad output:\nHTTP\/1.1 404 Not Found\nContent-Security-Policy: default-src 'self';...\nStrict-Transport-Security: max-age=31536000; includeSubDomains; preload\nX-Content-Type-Options: nosniff\nX-Frame-Options: SAMEORIGIN\nReferrer-Policy: strict-origin-when-cross-origin<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verifiera att rate limiting fungerar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Prova 6 inloggningsf\u00f6rs\u00f6k (ska blockeras vid det 6:e)\nfor i in {1..6}; do\n  curl -s -o \/dev\/null -w \"F\u00f6rs\u00f6k $i: %{http_code}\\n\" \\\n    -X POST http:\/\/localhost:3000\/api\/auth\/login \\\n    -H \"Content-Type: application\/json\" \\\n    -d '{\"email\":\"test@test.se\",\"password\":\"fel\"}'\ndone\n\n# F\u00f6rv\u00e4ntad output:\nF\u00f6rs\u00f6k 1: 401\nF\u00f6rs\u00f6k 2: 401\nF\u00f6rs\u00f6k 3: 401\nF\u00f6rs\u00f6k 4: 401\nF\u00f6rs\u00f6k 5: 401\nF\u00f6rs\u00f6k 6: 429 (Too Many Requests)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-vanliga-fallgropar-och-hur-du-undviker-dem\">5 vanliga fallgropar och hur du undviker dem<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dessa misstag dyker upp g\u00e5ng p\u00e5 g\u00e5ng i s\u00e4kerhetsgranskningar av Node.js-applikationer:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 1: H\u00e5rdkodade hemligheter.<\/strong> Att l\u00e4gga JWT-nycklar eller databas-l\u00f6senord direkt i k\u00e4llkoden \u00e4r det vanligaste kritiska misstaget. Anv\u00e4nd alltid milj\u00f6variabler och ladda dem med dotenv. K\u00f6r <code>git log --all --full-history -- \"**\/.env\"<\/code> f\u00f6r att kontrollera att inga .env-filer l\u00e4ckt in i git-historiken.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 2: Felaktig Content-Security-Policy.<\/strong> Att s\u00e4tta <code>helmet()<\/code> utan konfiguration ger en standardpolicy som kan vara f\u00f6r restriktiv (bryta befintliga funktioner) eller f\u00f6r till\u00e5tlig. Testa CSP med <code>report-only<\/code>-l\u00e4get innan du enforcar det: <code>Content-Security-Policy-Report-Only<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 3: Gl\u00f6md s\u00f6kv\u00e4gstraversering.<\/strong> Att servera filer med <code>express.static()<\/code> eller <code>fs.readFile()<\/code> baserat p\u00e5 anv\u00e4ndarinput utan validering \u00f6ppnar f\u00f6r path traversal-attacker (<code>..\/..\/..\/etc\/passwd<\/code>). Anv\u00e4nd alltid <code>path.resolve()<\/code> och kontrollera att den l\u00f6sta s\u00f6kv\u00e4gen b\u00f6rjar med din tillt\u00e4nkta baskatalog.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 4: RegEx Denial of Service (ReDoS).<\/strong> Komplexa regulj\u00e4ra uttryck mot user-kontrollerad indata kan ta exponentiell tid att exekvera (catastrophic backtracking) och krascha servern. Undvik kapslade kvantifierare (<code>(a+)+<\/code>) och anv\u00e4nd paketet <code>safe-regex<\/code> f\u00f6r att validera dina regulj\u00e4ra uttryck.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 5: Express body-parser utan storleksgr\u00e4ns.<\/strong> Utan <code>limit: '10kb'<\/code> i <code>express.json()<\/code> kan en angripare skicka ett JSON-objekt p\u00e5 100 MB och krascha servern via minnes\u00f6verskott. S\u00e4tt alltid en explicit gr\u00e4ns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 6: Felaktig HTTP-metod-hantering.<\/strong> Express svarar p\u00e5 alla HTTP-metoder om ingen begr\u00e4nsning s\u00e4tts. En angripare kan skicka TRACE-anrop f\u00f6r att se server-intern information eller OPTIONS-anrop f\u00f6r att mappa vilka endpoints som finns. Blockera o\u00f6nskade metoder explicit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 7: Exponerade felmeddelanden i produktion.<\/strong> Stack traces i JSON-felresponser avsl\u00f6jar teknisk infrastruktur (Node-version, biblioteksnamn, fils\u00f6kv\u00e4gar) som en angripare anv\u00e4nder f\u00f6r att hitta k\u00e4nda CVE:er. Skilj alltid p\u00e5 development- och production-felhantering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 8: Okrypterade sessionsdata i cookies.<\/strong> Att lagra k\u00e4nslig data direkt i cookies utan kryptering eller signering g\u00f6r att en anv\u00e4ndare kan modifiera sina egna sessionsdata (inklusive roller). Anv\u00e4nd alltid <code>signed: true<\/code> med <code>COOKIE_SECRET<\/code> och lagra bara session-ID i cookies, inte faktisk sessionsdata.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"felsokning-8-vanliga-problem\">Fels\u00f6kning: 8 vanliga problem<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Problem<\/th><th>Symptom<\/th><th>L\u00f6sning<\/th><\/tr><\/thead><tbody><tr><td>helmet() bryter CSS\/JS<\/td><td>Webbplats laddas men stilar saknas<\/td><td>L\u00e4gg till k\u00e4lla i CSP: <code>styleSrc: [\"'self'\", \"din-cdn.se\"]<\/code><\/td><\/tr><tr><td>CORS-fel p\u00e5 frontend<\/td><td><code>Access to fetch blocked by CORS policy<\/code><\/td><td>L\u00e4gg till frontend-URL i <code>ALLOWED_ORIGINS<\/code> i .env<\/td><\/tr><tr><td>Rate limit blockerar API-anrop<\/td><td>429 Too Many Requests i test<\/td><td>\u00d6ka <code>max<\/code> i testmilj\u00f6n, eller l\u00e4gg till IP i whitelist<\/td><\/tr><tr><td>JWT-verifiering misslyckas<\/td><td><code>JsonWebTokenError: invalid signature<\/code><\/td><td>Kontrollera att <code>JWT_SECRET<\/code> \u00e4r identisk i sign och verify<\/td><\/tr><tr><td>bcrypt \u00e4r f\u00f6r l\u00e5ngsamt<\/td><td>Inloggning tar 3+ sekunder<\/td><td>Minska SALT_ROUNDS till 10 i dev. I prod \u00e4r 12 r\u00e4tt.<\/td><\/tr><tr><td>mongo-sanitize tar bort legitimt data<\/td><td>F\u00e4ltnamn med punkt f\u00f6rsvinner<\/td><td>Till\u00e5t punkter selektivt: <code>allowDots: true<\/code> i sanitize-config<\/td><\/tr><tr><td>Cookie ej satt i browser<\/td><td>refreshToken syns inte i DevTools<\/td><td>Kontrollera att <code>secure: true<\/code> kr\u00e4ver HTTPS; anv\u00e4nd HTTPS i test<\/td><\/tr><tr><td>NODE_ENV ej satt i produktion<\/td><td>Stack traces syns i API-svar<\/td><td>S\u00e4tt <code>NODE_ENV=production<\/code> i produktionsmilj\u00f6ns milj\u00f6variabler<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"avancerade-tips-for-produktionsmiljo\">Avancerade tips f\u00f6r produktionsmilj\u00f6<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Subresource Integrity (SRI) f\u00f6r externa resurser.<\/strong> Om du laddar CSS eller JS fr\u00e5n ett CDN, l\u00e4gg till SRI-hash-attributet i dina script- och link-taggar. Det garanterar att webbl\u00e4saren bara k\u00f6r koden om den matchar den f\u00f6rv\u00e4ntade hashen, vilket skyddar mot komprometterade CDN-leverant\u00f6rer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Automatisk s\u00e5rbarhetsskanning i CI\/CD.<\/strong> L\u00e4gg till <code>npm audit --audit-level=high<\/code> som ett steg i din GitHub Actions- eller GitLab CI-pipeline. Om kommandot returnerar ett non-zero exit-code (dvs. h\u00f6g eller kritisk s\u00e5rbarhet hittas) avbryts deployen automatiskt. Det f\u00f6rhindrar att k\u00e4nd s\u00e5rbar kod n\u00e5r produktion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Rotera krypteringsnycklar regelbundet.<\/strong> JWT-hemligheter och krypteringsnycklar b\u00f6r roteras med en definierad frekvens (exempelvis kvartalsvis). Implementera nyckelversionshantering med ett <code>kid<\/code>-f\u00e4lt i JWT-headern s\u00e5 att tokens signerade med den gamla nyckeln kan valideras under \u00f6verg\u00e5ngsperioden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Aktivera Node.js permission model.<\/strong> I Node.js 22+ kan du starta servern med <code>--experimental-permission --allow-fs-read=. --allow-net<\/code> f\u00f6r att sandboxa applikationen p\u00e5 OS-niv\u00e5. Var medveten om att tre av de \u00e5tta CVE:erna som patchades i januari 2026 (CVE-2025-55132, CVE-2026-21636, CVE-2025-55130) var just bypasses av permission-systemet via syml\u00e4nkar och Unix Domain Sockets.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Aktivera audit-loggning f\u00f6r k\u00e4nsliga operationer.<\/strong> Alla \u00e4ndringar av beh\u00f6righeter, l\u00f6senordsbyten, och \u00e5tkomst till k\u00e4nslig data b\u00f6r loggas med tidsst\u00e4mpel, anv\u00e4ndar-ID, IP-adress och \u00e5tg\u00e4rd. Det \u00e4r inte bara god praxis utan ett krav under NIS2-direktivet som i Sverige tr\u00e4dde i kraft 15 januari 2026 via Cybers\u00e4kerhetslagen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Implementera Security Headers-test.<\/strong> K\u00f6r <a href=\"https:\/\/securityheaders.com\" rel=\"noopener noreferrer\" target=\"_blank\">securityheaders.com<\/a> mot din staging-milj\u00f6 och sikta p\u00e5 betyget A+. Testet verifierar att alla headers \u00e4r korrekt konfigurerade och ger konkreta f\u00f6rb\u00e4ttringsf\u00f6rslag.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"komplett-projektoversikt\">Komplett projekt\u00f6versikt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ditt f\u00e4rdiga projekt ska ha f\u00f6ljande struktur:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>owasp-nodejs-demo\/\n\u251c\u2500\u2500 .env                     (ej i git)\n\u251c\u2500\u2500 .env.example             (mall utan v\u00e4rden, i git)\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 .npmrc\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 server.js                (startpunkt)\n\u251c\u2500\u2500 app.js                   (Express-konfiguration)\n\u251c\u2500\u2500 middleware\/\n\u2502   \u251c\u2500\u2500 auth.js              (authenticate, authorize, checkResourceOwnership)\n\u2502   \u2514\u2500\u2500 rateLimiter.js       (generalLimiter, authLimiter)\n\u251c\u2500\u2500 routes\/\n\u2502   \u251c\u2500\u2500 auth.js              (login, register, refresh, logout)\n\u2502   \u2514\u2500\u2500 users.js             (CRUD med \u00e5tkomstkontroll)\n\u251c\u2500\u2500 services\/\n\u2502   \u251c\u2500\u2500 tokenService.js      (generateTokens, verifyToken)\n\u2502   \u2514\u2500\u2500 passwordService.js   (hashPassword, verifyPassword)\n\u251c\u2500\u2500 validators\/\n\u2502   \u2514\u2500\u2500 userValidator.js     (registerValidation, sanitizeInput)\n\u251c\u2500\u2500 utils\/\n\u2502   \u251c\u2500\u2500 logger.js            (winston logger med maskning)\n\u2502   \u2514\u2500\u2500 ssrfGuard.js         (validateExternalUrl)\n\u2514\u2500\u2500 logs\/                    (genereras vid k\u00f6rning, ej i git)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00f6r en sista s\u00e4kerhetsrevision innan deployment:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Kontrollera s\u00e5rbara beroenden\nnpm audit --audit-level=high\n\n# S\u00f6k efter h\u00e5rdkodade hemligheter\ngrep -r \"password\\|secret\\|api_key\\|token\" --include=\"*.js\" . | grep -v node_modules | grep -v \".env\" | grep -v logger\n\n# Kontrollera att .env inte \u00e4r i git\ngit ls-files .env\n\n# Kontrollera att NODE_ENV \u00e4r satt\necho $NODE_ENV<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vanliga-fragor-om-owasp-top-10-i-node-js\">Vanliga fr\u00e5gor om OWASP Top 10 i Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vad \u00e4r skillnaden mellan OWASP Top 10:2025 och 2021?<\/strong> De tre st\u00f6rsta f\u00f6r\u00e4ndringarna \u00e4r: Security Misconfiguration kl\u00e4ttrade fr\u00e5n femte till andraplats, Software Supply Chain Failures (A03) \u00e4r en helt ny kategori som reflekterar hotet fr\u00e5n skadliga npm-paket och komprometterade CI\/CD-pipelines, och Injection sj\u00f6nk fr\u00e5n f\u00f6rstaplats till femteplats trots att det fortfarande finns i varje testad applikation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>M\u00e5ste jag implementera alla tio skydden?<\/strong> Ja, om din applikation hanterar anv\u00e4ndardata, autentisering eller externa anrop. OWASP-listan \u00e4r inte en prioriteringslista utan en checklista. En applikation som bara saknar skydd mot A10 (SSRF) men \u00e4r s\u00e4ker p\u00e5 \u00f6vriga nio kan \u00e4nd\u00e5 komprometteras fullst\u00e4ndigt om en SSRF-s\u00e5rbarhet finns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u00c4r helmet() tillr\u00e4ckligt f\u00f6r Security Misconfiguration?<\/strong> Helmet t\u00e4cker HTTP-headers, vilket \u00e4r en viktig del av A02. Men Security Misconfiguration inkluderar ocks\u00e5: exponerade felmeddelanden (konfigureras i felhanteraren), \u00f6ppna directory listings (blockeras med <code>express.static()<\/code>-konfiguration), on\u00f6diga aktiverade features och os\u00e4kra standardv\u00e4rden i databaser och molntj\u00e4nster.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vad \u00e4r NoSQL-injektion och hur skiljer det sig fr\u00e5n SQL-injektion?<\/strong> SQL-injektion manipulerar SQL-fr\u00e5gor med specialtecken. NoSQL-injektion mot MongoDB manipulerar i st\u00e4llet JSON-operat\u00f6rer. Payload <code>{\"password\": {\"$gt\": \"\"}}<\/code> i inloggningsformul\u00e4ret matchar alla l\u00f6senord som \u00e4r &#8220;greater than&#8221; en tom str\u00e4ng, vilket ger angriparen tillg\u00e5ng till kontot utan r\u00e4tt l\u00f6senord. Express-mongo-sanitize l\u00f6ser detta automatiskt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Hur ofta b\u00f6r jag k\u00f6ra s\u00e4kerhetsrevisioner?<\/strong> K\u00f6r <code>npm audit<\/code> som del av din CI\/CD-pipeline vid varje commit. K\u00f6r en mer grundlig revision med Snyk eller OWASP ZAP kvartalsvis eller inf\u00f6r varje st\u00f6rre release. Under NIS2\/Cybers\u00e4kerhetslagen (som g\u00e4ller ca 8 000 svenska f\u00f6retag) \u00e4r regelbundna riskbed\u00f6mningar ett lagkrav.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vilka Node.js-versioner \u00e4r s\u00e4kra att anv\u00e4nda 2026?<\/strong> Node.js 22.x LTS och Node.js 20.x LTS (underh\u00e5llet till april 2026) \u00e4r de rekommenderade versionerna. Node.js 17.x och 19.x n\u00e5dde end-of-life och har kritiska oslagade CVE:er (CVE-2025-23087, CVE-2025-23088) som ger angripare m\u00f6jlighet till remote code execution.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kan jag anv\u00e4nda dessa skydd med Fastify ist\u00e4llet f\u00f6r Express?<\/strong> Ja, principerna g\u00e4ller f\u00f6r alla Node.js-ramverk. Fastify har inbyggda motsvarigheter till m\u00e5nga Express-middleware: <code>@fastify\/helmet<\/code>, <code>@fastify\/rate-limit<\/code> och <code>@fastify\/jwt<\/code>. Valideringslogiken i express-validator ers\u00e4tts av Fastify&#8217;s inbyggda JSON Schema-validering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vad \u00e4r skillnaden mellan autentisering och auktorisering?<\/strong> Autentisering verifierar vem du \u00e4r (JWT-token, l\u00f6senord). Auktorisering avg\u00f6r vad du f\u00e5r g\u00f6ra (rolkontroller, resurs\u00e4gandekontroll). Broken Access Control (A01:2025) uppst\u00e5r n\u00e4stan alltid n\u00e4r applikationer autentiserar korrekt men missar auktoriseringen, till exempel n\u00e4r en autentiserad anv\u00e4ndare kan l\u00e4sa en annan anv\u00e4ndares data.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"relaterat-innehall\">Relaterat inneh\u00e5ll<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00f6rdjupa dig vidare i angr\u00e4nsande \u00e4mnen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/sv\/ecdsa-digitala-signaturer-nodejs\/\">Digitala signaturer i Node.js med ECDSA: 12 steg [2026]<\/a><\/li>\n<li><a href=\"\/sv\/openssl-nycklar-certifikat\/\">OpenSSL 3.5: nycklar och certifikat i 12 steg [2026]<\/a><\/li>\n<li><a href=\"\/sv\/cybersakerhetslagen-nis2-2026\/\">Cybers\u00e4kerhetslagen: 8 000 f\u00f6retag, 2 % b\u00f6ter [2026]<\/a><\/li>\n<li><a href=\"\/sv\/pgp-kryptering-gpg\/\">PGP-kryptering med GPG: e-post i 12 steg [2026]<\/a><\/li>\n<li><a href=\"\/sv\/ransomware-sverige-norden-2026\/\">Ransomware 2026: Sverige v\u00e4rst i Norden, 60 incidenter [2026]<\/a><\/li>\n<li><a href=\"\/security\/\">S\u00e4kerhet: fullst\u00e4ndig guide till cybers\u00e4kerhet [2026]<\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Externa resurser:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/owasp.org\/Top10\/2025\/en\/\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP Top 10:2025 (officiell)<\/a><\/li>\n<li><a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Nodejs_Security_Cheat_Sheet.html\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP Node.js Security Cheat Sheet<\/a><\/li>\n<li><a href=\"https:\/\/nodejs.org\/en\/blog\/vulnerability\/december-2025-security-releases\" rel=\"noopener noreferrer\" target=\"_blank\">Node.js Security Releases januari 2026<\/a><\/li>\n<li><a href=\"https:\/\/expressjs.com\/en\/advanced\/best-practice-security.html\" rel=\"noopener noreferrer\" target=\"_blank\">Express.js: Production Best Practices Security<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>OWASP Top 10 \u00e4r den mest citerade s\u00e4kerhetsstandarden f\u00f6r webbapplikationer. Den senaste versionen, OWASP Top 10:2025, publicerades i november 2025 och identifierar tio kritiska s\u00e5rbarhetskategorier som drabbar virtuellt alla testade\u2026<\/p>\n","protected":false},"author":7,"featured_media":103,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,3],"tags":[],"class_list":["post-102","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-10","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/102","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/comments?post=102"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/102\/revisions"}],"predecessor-version":[{"id":104,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/102\/revisions\/104"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/media\/103"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/media?parent=102"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/categories?post=102"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/tags?post=102"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}