Den 2 augusti 2026 bekräftade elscooterbolaget Ryde att obehöriga kommit över kunddata från samtliga registrerade konton i Sverige, Norge, Finland och Tyskland. Enligt svensk press rörde det sig om omkring 1,35 miljoner svenska konton av totalt cirka 4,5 miljoner i Norden och Tyskland tillsammans. Några veckor tidigare, mellan 29 juli och 1 augusti, drabbades logistikbolaget Ceva Logistics av ett intrång som spred sig vidare till kunder som Valve och exponerade namn, adresser, telefonnummer och orderdetaljer. Två separata attacker, samma mönster: ingen av organisationerna visste om läckan förrän datan redan låg ute.

Det är precis den typen av fördröjning ett automatiserat läckagevakt-system är byggt för att stänga. I den här guiden bygger vi ett komplett övervakningsflöde med Have I Been Pwned-API:et (HIBP), skapat av säkerhetsforskaren Troy Hunt, som kontrollerar om e-postadresser eller lösenord från din organisation dyker upp i kända dataintrång. Du får kod i Node.js, en fungerande k-anonymitetsimplementation för Pwned Passwords, ett schemalagt övervakningsjobb och ett larmflöde kopplat till Slack. Allt körs med gratis eller lågkostnadsabonnemang, och du behöver inga externa molntjänster för att komma igång.

Vad är Have I Been Pwned och varför behöver svenska företag det

Have I Been Pwned är en databas över kända dataintrång som byggts upp sedan 2013. Tjänsten drivs av Troy Hunt och innehåller miljarder läckta konton från tusentals källor, allt från gamla forumintrång till färska ransomware-läckor. Poängen med att bygga in HIBP i din egen infrastruktur är enkel: i stället för att manuellt slå upp adresser på webbsidan kan du programmera ett flöde som kontinuerligt kontrollerar hela kundbasen eller personallistan mot nya läckor, och larmar automatiskt när en träff dyker upp.

Det finns två separata API:er värda att skilja på. Breach-API:et söker efter en e-postadress bland kända intrång och returnerar vilka tjänster som läckt den, när intrånget skedde och vilken typ av data som exponerades. Pwned Passwords-API:et löser ett annat problem: det kontrollerar om ett specifikt lösenord någon gång förekommit i en läckt databas, utan att lösenordet någonsin skickas i klartext till servern. Båda bygger på samma grundprincip, men täcker olika delar av en säkerhetsarkitektur.

För en nordisk organisation är det här inget “nice to have” längre. Ryde-läckan och Ceva-attacken visar att svenska kunder och anställda med stor sannolikhet redan finns i flera läckta dataset, ofta utan att veta om det själva. Återanvänds samma lösenord på din tjänst som på det läckta kontot öppnas dörren för credential stuffing, en attackmetod där kriminella testar stulna inloggningsuppgifter i bulk mot andra tjänster tills något träffar rätt.

Så fungerar k-anonymitet i Pwned Passwords-API:et

Den mest eleganta delen av HIBP:s design är hur Pwned Passwords-API:et skyddar lösenordet du kontrollerar. Enligt den officiella API-dokumentationen (v3) hashas lösenordet lokalt med SHA-1 (eller NTLM), och bara de första fem tecknen av hashen skickas till API:et. Servern svarar med samtliga suffix som matchar det prefixet, tillsammans med hur många gånger varje variant förekommit i läckta dataset. Du jämför sedan lokalt om ditt fullständiga hash-suffix finns i listan.

Det här är avgörande av två skäl. Dels lämnar det fullständiga lösenordet, eller ens hela hashen, aldrig din server. Dels gör designen det omöjligt för HIBP att se exakt vilket lösenord du kontrollerade, eftersom varje femteckensprefix i snitt matchar hundratals eller tusentals olika hashsuffix. API:et svarar dessutom alltid med HTTP 200 för ett giltigt prefix oavsett om en träff finns eller inte, vilket stänger dörren för sidokanalsattacker där svarstider eller statuskoder skulle kunna avslöja om ett visst lösenord förekommer i databasen.

Det gör modellen väl lämpad för GDPR-känslig verksamhet. Du kan bygga lösenordskontroll i registreringsflödet utan att någonsin överföra användarens faktiska lösenord till en tredje part, vilket är precis den typ av dataminimering Integritetsskyddsmyndigheten (IMY) förväntar sig av svenska organisationer som hanterar personuppgifter.

Förutsättningar

Innan du börjar, se till att följande finns på plats. Guiden är testad med versionerna nedan, men nyare patch-versioner ska fungera utan problem:

  • Node.js version 20 LTS eller senare (kontrollera med node -v)
  • npm version 10 eller senare
  • Ett HIBP API-abonnemang för breach-sökning (betalabonnemang krävs för e-postsökning; Pwned Passwords är gratis och kräver ingen nyckel)
  • Node 20+ med inbyggt fetch, eller paketet node-fetch version 3.x för äldre versioner
  • En textredigerare eller IDE, till exempel VS Code
  • Grundläggande kunskap i JavaScript och asynkron programmering (async/await)
  • Ett Slack-webhook-URL för larmutskick
  • Cron eller ett schemaläggningsverktyg för att köra kontrollerna automatiskt

Notera att e-postsökning via breach-API:et kräver ett betalt abonnemang sedan Troy Hunt förändrade prismodellen. Pwned Passwords-API:et för lösenordskontroll är däremot fortsatt helt gratis och kräver ingen API-nyckel, vilket gör det till en bra startpunkt om du vill testa konceptet innan du investerar i ett abonnemang för domänövervakning.

Steg 1: Skapa projektstruktur och installera beroenden

Börja med att skapa ett nytt projekt och installera det vi behöver. Vi använder dotenv för att hantera API-nyckeln säkert utanför källkoden.

mkdir lackagevakt && cd lackagevakt
npm init -y
npm install dotenv
mkdir src
touch .env .gitignore src/hibp.js src/passwords.js src/historik.js src/index.js

Lägg din HIBP API-nyckel i .env-filen. Lägg aldrig nyckeln direkt i källkoden eller i en fil som checkas in i versionshantering.

HIBP_API_KEY=din_api_nyckel_har
ALERT_WEBHOOK_URL=https://hooks.slack.com/services/din/webhook/url

Lägg till node_modules, .env och historik.json i .gitignore. Ett projekt om läckor som självt läcker sin API-nyckel via Git är en dålig start.

Steg 2: Bygg klienten för breach-sökning

Nu bygger vi HIBP-klienten i src/hibp.js. Funktionen tar en e-postadress och frågar breach-API:et om adressen förekommer i någon känd läcka. Vi hanterar rate limiting (HTTP 429) genom att läsa retry-after-headern och vänta innan vi försöker igen, precis som dokumentationen rekommenderar.

// src/hibp.js
import 'dotenv/config';

const BASE_URL = 'https://haveibeenpwned.com/api/v3';
const API_KEY = process.env.HIBP_API_KEY;

async function checkBreaches(email, retries = 3) {
  const url = `${BASE_URL}/breachedaccount/${encodeURIComponent(email)}?truncateResponse=false`;

  const response = await fetch(url, {
    headers: {
      'hibp-api-key': API_KEY,
      'user-agent': 'lackagevakt-nordic/1.0',
    },
  });

  if (response.status === 200) {
    return await response.json();
  }

  if (response.status === 404) {
    return []; // Ingen kand lacka hittad
  }

  if (response.status === 429 && retries > 0) {
    const retryAfter = Number(response.headers.get('retry-after') || 5);
    console.warn(`Rate limit nadd. Vantar ${retryAfter} sekunder...`);
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
    return checkBreaches(email, retries - 1);
  }

  throw new Error(`HIBP svarade med status ${response.status}`);
}

export { checkBreaches };

Funktionen returnerar en tom array om adressen inte finns i någon läcka (HTTP 404), och en lista med läckobjekt om den gör det. Varje objekt innehåller fält som Name, BreachDate, DataClasses (vilken typ av data som läckte, exempelvis lösenord eller telefonnummer) och Description.

Steg 3: Implementera k-anonym lösenordskontroll

Här bygger vi den viktigaste säkerhetskomponenten: lösenordskontrollen. Vi hashar lösenordet lokalt med SHA-1, skickar bara de första fem tecknen till API:et och matchar resten lokalt.

// src/passwords.js
import crypto from 'node:crypto';

async function isPasswordPwned(password) {
  const sha1 = crypto
    .createHash('sha1')
    .update(password, 'utf8')
    .digest('hex')
    .toUpperCase();

  const prefix = sha1.slice(0, 5);
  const suffix = sha1.slice(5);

  const response = await fetch(
    `https://api.pwnedpasswords.com/range/${prefix}`,
    { headers: { 'user-agent': 'lackagevakt-nordic/1.0' } }
  );

  if (response.status !== 200) {
    throw new Error(`Pwned Passwords svarade med status ${response.status}`);
  }

  const text = await response.text();
  const matches = text.split('\r\n').map((line) => line.split(':'));
  const hit = matches.find(([suf]) => suf === suffix);

  return hit ? { pwned: true, count: Number(hit[1]) } : { pwned: false, count: 0 };
}

export { isPasswordPwned };

Testa funktionen med ett känt svagt lösenord, exempelvis “password123”, så ser du direkt att den returnerar ett träfftal i miljonklassen. Koppla in mönstret i registrerings- och lösenordsbytesflödet, inte som en engångskontroll du glömmer bort.

Steg 4: Blockera svaga lösenord vid registrering

Nu kopplar vi in kontrollen i ett faktiskt registreringsflöde. Exemplet nedan visar en enkel Express-middleware som avvisar registreringar med lösenord som redan förekommer i en läcka, innan lösenordet ens hashas för lagring med bcrypt eller Argon2.

import { isPasswordPwned } from './passwords.js';

async function losenordsvalidering(req, res, next) {
  const { password } = req.body;

  if (!password || password.length < 12) {
    return res.status(400).json({ fel: 'Losenordet maste vara minst 12 tecken.' });
  }

  const resultat = await isPasswordPwned(password);

  if (resultat.pwned) {
    return res.status(400).json({
      fel: `Losenordet har hittats i ${resultat.count} tidigare lackor. Valj ett annat.`,
    });
  }

  next();
}

export { losenordsvalidering };

Rekommendationen att blockera kända läckta lösenord vid registrering, i stället för att bara kräva komplexitetsregler som specialtecken, är också i linje med de riktlinjer NIST SP 800-63B publicerat för digital identitet och autentisering.

Steg 5: Schemalägg övervakning av kunddomäner

Med grundfunktionerna på plats bygger vi ett jobb som går igenom listan med kund- eller personaladresser och kontrollerar dem mot nya läckor. Detta är kärnan i läckagevakten: ett flöde som körs automatiskt, inte något som körs manuellt när någon kommer ihåg det.

// src/overvakning.js
import { checkBreaches } from './hibp.js';

async function korOvervakning(kundlista) {
  const larm = [];

  for (const email of kundlista) {
    try {
      const traffar = await checkBreaches(email);
      if (traffar.length > 0) {
        larm.push({ email, breaches: traffar.map((b) => b.Name) });
      }
      await new Promise((r) => setTimeout(r, 1600)); // respektera rate limit
    } catch (err) {
      console.error(`Fel vid kontroll av ${email}:`, err.message);
    }
  }

  return larm;
}

export { korOvervakning };

Väntetiden på 1600 millisekunder mellan anropen matchar den lägsta abonnemangsnivåns hastighetsgräns. Har du ett större abonnemang med högre RPM-gräns (requests per minute) kan du sänka väntetiden, se tabellen längre ner i guiden.

Steg 6: Undvik dubbla larm med en historikfil

Utan historik larmas ditt team om samma läcka varje natt så länge kontot ligger kvar i databasen. Lösningen är en enkel JSON-fil (byt till en riktig databastabell i produktion) som håller reda på vilka läckor som redan rapporterats per adress.

// src/historik.js
import fs from 'node:fs/promises';

const HISTORIK_FIL = './historik.json';

async function laddaHistorik() {
  try {
    const data = await fs.readFile(HISTORIK_FIL, 'utf8');
    return JSON.parse(data);
  } catch {
    return {};
  }
}

async function sparaHistorik(historik) {
  await fs.writeFile(HISTORIK_FIL, JSON.stringify(historik, null, 2));
}

function filtreraNyaTraffar(email, breaches, historik) {
  const kanda = historik[email] || [];
  const nya = breaches.filter((namn) => !kanda.includes(namn));
  historik[email] = [...kanda, ...nya];
  return nya;
}

export { laddaHistorik, sparaHistorik, filtreraNyaTraffar };

Steg 7: Bygg larmflödet till Slack

Ett övervakningsflöde som bara skriver ut resultat i en terminal hjälper ingen. Lägg till en funktion som skickar ett larm till Slack (eller Teams, samma princip gäller) så fort en ny träff hittas.

// src/larm.js
async function skickaLarm(larm) {
  if (larm.length === 0) return;

  const text = larm
    .map((l) => `Ny traff: ${l.email} hittades i: ${l.breaches.join(', ')}`)
    .join('\n');

  const svar = await fetch(process.env.ALERT_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ text: `Nya traffar i lackagevakten:\n${text}` }),
  });

  if (!svar.ok) {
    console.error(`Slack-webhook misslyckades: ${svar.status}`);
  }
}

export { skickaLarm };

Steg 8: Sätt ihop det kompletta projektet

Nu kopplar vi ihop alla delar i src/index.js. Det här är hela läckagevakten: hämta kundlista, kontrollera mot HIBP, filtrera bort redan kända träffar, larma, och spara historik.

// src/index.js
import 'dotenv/config';
import { korOvervakning } from './overvakning.js';
import { laddaHistorik, sparaHistorik, filtreraNyaTraffar } from './historik.js';
import { skickaLarm } from './larm.js';

const KUNDLISTA = [
  '[email protected]',
  '[email protected]',
  // Ladda i praktiken listan fran din databas
];

async function main() {
  const historik = await laddaHistorik();
  const traffar = await korOvervakning(KUNDLISTA);

  const nyaLarm = traffar
    .map(({ email, breaches }) => ({
      email,
      breaches: filtreraNyaTraffar(email, breaches, historik),
    }))
    .filter((l) => l.breaches.length > 0);

  await skickaLarm(nyaLarm);
  await sparaHistorik(historik);

  console.log(`Klart. ${nyaLarm.length} konton med nya traffar av ${KUNDLISTA.length} kontrollerade.`);
}

main().catch((err) => {
  console.error('Ovakningen kraschade:', err);
  process.exit(1);
});

Kör med node src/index.js. Det här är den fullständigt fungerande versionen du kan lägga rakt in i produktion, förutsatt att du byter ut den hårdkodade kundlistan mot en riktig databasfråga.

Steg 9: Exempel på utdata

När systemet hittar en träff ser konsolutskriften ut ungefär så här:

Klart. 1 konton med nya traffar av 2 kontrollerade.
[
  {
    "email": "[email protected]",
    "breaches": ["Ryde2026", "CeraLogisticsSupplyChain2026"]
  }
]

Och Slack-meddelandet som går ut till kanalen blir:

Nya traffar i lackagevakten:
Ny traff: [email protected] hittades i: Ryde2026, CeraLogisticsSupplyChain2026

Nästa gång jobbet körs, och samma adress fortfarande ligger i samma läckor, skickas inget nytt larm eftersom historikfilen redan känner till träffen. Bara nytillkomna läckor triggar ett meddelande.

Steg 10: Automatisera körningen med cron

Domän- och kontosökningen saknar en formellt angiven hastighetsgräns i dokumentationen, men är avsedd för periodisk snarare än kontinuerlig kontroll. En körning per dygn räcker för de flesta organisationer, eftersom nya poster i databasen normalt läggs till i omgångar snarare än i realtid.

# Redigera crontab
crontab -e

# Kor overvakningen varje natt klockan 03:00
0 3 * * * cd /opt/lackagevakt && /usr/bin/node src/index.js >> /var/log/lackagevakt.log 2>&1

Kör du i en molnmiljö kan cron ersättas med ett schemalagt jobb i till exempel GitHub Actions eller molnleverantörens egen schemaläggare. Principen är densamma: en daglig eller veckovis körning som loggar resultat och larmar vid nya träffar.

Steg 11: Koppla larmet till er incidentprocess

Ett larm som bara hamnar i en kanal ingen bevakar är i praktiken ett larm som inte finns. Se till att ett nytt läckagelarm rutinmässigt triggar samma process som andra säkerhetshändelser: bekräfta träffen, forcera lösenordsbyte för det drabbade kontot, och dokumentera händelsen. Har organisationen redan en etablerad NIS2-incidentplan, hör läckagevakten hemma som en tidig varningskälla i det flödet snarare än som ett fristående verktyg.

Om läckagevakten upptäcker att er egen kunddatabas, inte bara enskilda konton, förekommer i en ny läcka gäller GDPR:s underrättelseregler. Enligt artikel 33 i GDPR måste en personuppgiftsansvarig anmäla en personuppgiftsincident till tillsynsmyndigheten inom 72 timmar från upptäckt, om incidenten sannolikt medför en risk för de registrerades rättigheter. En automatiserad läckagevakt gör att klockan börjar ticka på rätt tidpunkt, i stället för veckor senare när ett medieuppslag tvingar fram en utredning.

Steg 12: Testa och verifiera hela flödet

Innan läckagevakten läggs i produktion bör den testas mot kända fall, inte bara mot tomma testadresser. Have I Been Pwned tillhandahåller officiellt ett testkonto avsett för just det här: [email protected] ger alltid en giltig träff från breach-API:et, vilket gör det möjligt att verifiera att autentisering, felhantering och larmkedjan fungerar utan att behöva vänta på en riktig läcka.

// test/verifiera.js
import { checkBreaches } from '../src/hibp.js';
import { isPasswordPwned } from '../src/passwords.js';

async function korTester() {
  const breachResultat = await checkBreaches('[email protected]');
  console.assert(breachResultat.length > 0, 'Testkontot borde ge minst en traff');
  console.log('Breach-test OK:', breachResultat.length, 'traffar hittades');

  const losenordResultat = await isPasswordPwned('password123');
  console.assert(losenordResultat.pwned === true, 'password123 ska vara markerat som lackt');
  console.log('Losenordstest OK:', losenordResultat.count, 'forekomster');

  const sakertResultat = await isPasswordPwned(`sallsynt-fras-${Date.now()}`);
  console.assert(sakertResultat.pwned === false, 'Ett unikt losenord ska inte vara lackt');
  console.log('Negativt test OK: unikt losenord gav ingen traff');
}

korTester();

Kör testfilen med node test/verifiera.js och kontrollera att alla tre påståenden går igenom utan att skriva ut ett assertion-fel i konsolen. Lägg gärna in testet som ett steg i er CI-pipeline så att en framtida ändring i felhanteringen upptäcks innan den når produktion, snarare än när ett riktigt larm uteblir.

Vilken HIBP-prenumeration behöver du?

Prissättningen för breach-API:et skalar efter hur många förfrågningar per minut (RPM) du behöver och hur många domäner du vill övervaka. Pwned Passwords är alltid gratis och kräver ingen nyckel. Tabellen nedan visar en förenklad översikt över nivåerna, i USD per månad, baserat på HIBP:s publicerade abonnemangsstruktur.

NivåRPM (förfrågningar/minut)Ungefärligt prisLämplig för
Pwned PasswordsIngen gräns angiven, gratis0 USDLösenordskontroll vid registrering
Pwned 110 RPMCa 4,50 USD/månadEnstaka konton, test och utveckling
Pwned 51 000 RPMCa 326 USD/månadMellanstora kundlistor, daglig batch
Core 110 RPMCa 4,39 USD/månadEn domän, upp till 25 adresser
Core 51 000 RPMRörligt, se officiell prislistaUpp till 20 domäner
Ultra 4000/8000/120004 000–12 000 RPMUpp till ca 3 912 USD/månadStora organisationer, miljontals kontroller

För att sätta siffrorna i perspektiv: att kontrollera en miljon e-postadresser tar omkring 69 dagar på Pwned 1-nivån (10 RPM), knappt 17 timmar på Pwned 5 (1 000 RPM), och ungefär 1,4 timmar på Ultra 12000, förutsatt att klienten kan skicka i den takten. De flesta nordiska små och medelstora företag klarar sig gott med en lägre nivå eftersom kundlistan sällan behöver kontrolleras mer än en gång per dygn.

Breach-API mot Pwned Passwords: vilket ska du använda när

De två API:erna löser olika delar av problemet, och de flesta produktionsflöden behöver båda. Tabellen nedan sammanfattar skillnaderna.

EgenskapBreach-API (kontosökning)Pwned Passwords-API
Kräver API-nyckelJaNej
KostnadBetalabonnemangGratis
Data som skickasE-postadressFem tecken av en hash
Svarar medLista över kända läckorTräfftal för lösenordet
Typiskt användningsfallÖvervaka kundbas mot nya intrångBlockera svaga lösenord vid registrering
Lämplig anropsfrekvensDagligen eller veckovisVid varje registrering/lösenordsbyte

Nordiska dataintrång 2026 som visar varför övervakning behövs

De senaste incidenterna i Norden är ett bra argument för att bygga in läckagevakten som standard, inte som ett projekt man skjuter upp. Tabellen samlar tre av de intrång som redan bevakats på shattered.io under 2026.

IncidentDatumDrabbadeExponerad data
Ryde-hacket2 augusti 2026Ca 4,5 miljoner konton i Norden och Tyskland, varav ca 1,35 miljoner i SverigePerson- och betalningsuppgifter
Ceva Logistics-attacken29 juli–1 augusti 2026Kunder hos 7 storföretag, inklusive ValveNamn, adress, telefon, orderdetaljer
Miljödata-attacken2026Ca 870 000 personer i 200 kommunerPerson- och anställningsuppgifter

Gemensamt för alla tre är att de drabbade organisationerna, och i flera fall deras kunder och underleverantörer, inte hade ett sätt att snabbt se om egna konton fanns med i en ny läcka. En läckagevakt byggd enligt den här guiden hade flaggat träffar samma dygn som posterna dök upp i HIBP:s databas, i stället för att vänta på ett pressmeddelande.

Vanliga fallgropar att undvika

Även ett till synes enkelt API-integrationsprojekt har fällor som kostar tid eller, värre, exponerar data i onödan. Här är de vanligaste misstagen.

  • Skicka hela lösenordet till API:et. Det bryter mot hela poängen med k-anonymitetsmodellen och innebär att du överför känslig data till en tredje part i onödan. Skicka aldrig mer än de fem första hashtecknen.
  • Ingen hantering av rate limiting. Utan backoff-logik för HTTP 429 blockeras hela övervakningsjobbet mitt i en körning, och du missar delar av kundlistan utan att märka det.
  • Hårdkodad API-nyckel i källkoden. En nyckel som checkas in i Git hamnar förr eller senare i en offentlig historik, även om filen senare tas bort.
  • Ingen deduplicering av larm. Utan en historikfil eller databastabell larmas teamet om samma gamla träff varje natt, vilket snabbt gör att larmen ignoreras helt.
  • Blanda ihop SHA-1 för uppslagning med SHA-1 för lagring. SHA-1-hashen i Pwned Passwords är bara till för att slå upp mot databasen. Lösenord ska fortfarande lagras med en långsam hashfunktion som bcrypt eller Argon2, aldrig med SHA-1.
  • Anta att breach-API:et är gratis. Många utvecklare testar Pwned Passwords, som är gratis, och blir sedan förvånade när kontosökningen kräver ett betalabonnemang.
  • Köra kontroller för hela kundlistan vid varje inloggning. Det belastar API:et i onödan. Kör kontosökning som ett schemalagt batch-jobb, inte synkront i inloggningsflödet.

Felsökning: 10 vanliga problem och lösningar

Stöter du på fel när du bygger eller kör läckagevakten är det oftast något av följande:

  1. HTTP 401 Unauthorized: API-nyckeln saknas eller är felaktig i hibp-api-key-headern. Kontrollera att .env-filen laddas innan anropet görs.
  2. HTTP 429 Too Many Requests: Du har överskridit din RPM-gräns. Läs retry-after-headern och öka väntetiden mellan anropen, eller uppgradera abonnemang.
  3. HTTP 404 vid kontosökning: Det här är inte ett fel, det betyder att adressen inte finns i någon känd läcka. Hantera det som ett giltigt "inga träffar"-svar, inte som ett undantag.
  4. Tomt svar från Pwned Passwords: Kontrollera att hashen skickas i versaler (uppercase hex), eftersom jämförelsen med suffixlistan är skiftlägeskänslig i den egna koden.
  5. "fetch is not defined": Node-versionen är äldre än 18 och saknar inbyggt fetch. Installera node-fetch eller uppgradera till Node 20 LTS.
  6. Cron-jobbet kör inte: Crontab-miljön saknar ofta samma PATH som ditt terminalskal. Ange fullständig sökväg till node-binären i cron-raden.
  7. Slack-webhooken svarar 404: Webhook-URL:en har återkallats eller kanalen tagits bort. Generera en ny webhook i Slack-appens inställningar.
  8. Dubbla larm trots historikfilen: Kontrollera att sparaHistorik() faktiskt anropas efter varje körning och att processen har skrivrättigheter till filen.
  9. Jobbet tar för lång tid och timeoutar: Dela upp stora kundlistor i mindre batcher som körs i separata cron-scheman i stället för en lång synkron loop.
  10. CORS-fel vid anrop direkt från webbläsaren: Breach-API:et är inte avsett att anropas från klientkod i produktion. Kör alla kontosökningar server-side och exponera bara ditt eget, kontrollerade endpoint till frontend.

Utöka skyddet till Active Directory och Microsoft Entra ID

Många nordiska organisationer har redan varit med om egna incidenter kopplade till identitetsplattformen, inte minst efter de kritiska sårbarheter som skakat Microsoft Entra ID under 2026. Lösenordskontrollen från Pwned Passwords kan återanvändas för att skydda både lokal Active Directory och molnbaserat Entra ID, utöver webbapplikationens registreringsflöde.

Microsoft Entra Password Protection stödjer egna anpassade listor med förbjudna lösenord som komplement till det inbyggda globala skyddet. Genom att periodiskt hämta de vanligast förekommande lösenorden ur Pwned Passwords-svaren och skriva ut dem till en textfil kan ni bygga en anpassad banned-lista som laddas upp i Entra-administrationsportalen eller distribueras till on-prem-agenten för Active Directory.

// verktyg/exportera-bannlista.js
import { isPasswordPwned } from '../src/passwords.js';
import fs from 'node:fs/promises';

async function exporteraBannlista(kandidater) {
  const bannade = [];

  for (const ord of kandidater) {
    const resultat = await isPasswordPwned(ord);
    if (resultat.pwned && resultat.count > 100000) {
      bannade.push(ord);
    }
  }

  await fs.writeFile('bannlista.txt', bannade.join('\n'));
  console.log(`${bannade.length} losenord skrivna till bannlista.txt`);
}

Tröskelvärdet på 100 000 förekomster i exemplet ovan är godtyckligt valt för att fånga de absolut vanligaste lösenorden, inklusive svenska varianter som ofta återanvänds mellan tjänster. Justera gränsen efter hur stor bannlistan får bli, eftersom både Entra ID och lokal Active Directory har tak för hur många anpassade poster som stöds.

Avancerade tips för att bygga ut läckagevakten

När grundflödet fungerar finns flera sätt att göra systemet mer robust. Byt ut JSON-filen för historik mot en riktig databastabell (SQLite räcker gott för de flesta) så att flera instanser av jobbet kan köras parallellt utan krockar. Har organisationen ett Pro-abonnemang kan domänsökning med verifierad TXT-post ge en samlad bild av alla adresser på en domän i ett enda anrop, i stället för att loopa igenom listan adress för adress.

Pro-nivåerna ger också tillgång till stealer log-endpointen, som visar om enskilda enheter komprometterats av infostealer-skadlig kod, ofta tidigare än en formell breach hinner katalogiseras. För organisationer med höga säkerhetskrav kan Pwned Passwords-kontrollen också köras helt offline genom att ladda ner hela hashlistan lokalt, vilket eliminerar beroendet av extern nätverkstrafik för lösenordskontrollen. Slutligen, lägg till strukturerad loggning (till exempel JSON-loggar till en SIEM-lösning) så att läckagevakten blir en del av den bredare säkerhetsövervakningen i stället för ett fristående skript ingen minns finns.

Checklista innan läckagevakten går i produktion

Innan systemet kopplas till en riktig kundlista och börjar larma på riktigt är det värt att gå igenom en kort checklista. Den fångar de vanligaste sakerna som annars upptäcks först efter en driftsstörning eller ett missat larm.

  1. API-nyckeln ligger i .env, inte i källkoden, och .env finns med i .gitignore.
  2. Rate limit-hanteringen är testad genom att medvetet skicka fler anrop än abonnemanget tillåter och verifiera att koden väntar korrekt på retry-after.
  3. Testkontot [email protected] ger en träff och triggar hela larmkedjan fram till Slack.
  4. Historikfilen (eller databastabellen) skrivs till varaktig lagring, inte till en tillfällig katalog som rensas vid omstart.
  5. Cron-jobbet är verifierat med fullständig sökväg till node-binären, och loggfilen kontrolleras efter första skarpa körningen.
  6. Lösenordskontrollen i registreringsflödet är kopplad före, inte efter, att lösenordet hashas för lagring med bcrypt eller Argon2.
  7. Ansvarig för säkerhetsincidenter vet att läckagevaktens larm ska hanteras enligt samma rutin som andra säkerhetshändelser, inklusive bedömning av anmälningsplikt enligt GDPR artikel 33.
  8. Abonnemangsnivån matchar storleken på kundlistan, så att en daglig körning hinner klart inom rimlig tid utan att trigga upprepade 429-fel.

Vanliga frågor

Är Have I Been Pwned gratis att använda?

Pwned Passwords-API:et för lösenordskontroll är helt gratis och kräver ingen API-nyckel. Breach-API:et för att söka efter e-postadresser i kända läckor kräver däremot ett betalt abonnemang, med priser som skalar efter hur många förfrågningar per minut du behöver.

Skickar jag mitt lösenord till Have I Been Pwned när jag använder API:et?

Nej. Med k-anonymitetsmodellen hashas lösenordet lokalt och bara de första fem tecknen av hashen skickas till API:et. Det fullständiga lösenordet eller den fullständiga hashen lämnar aldrig din server.

Hur ofta bör vi köra breach-övervakningen?

En körning per dygn räcker för de flesta organisationer. Nya poster i HIBP:s databas läggs normalt till i omgångar när en ny läcka bekräftas och bearbetas, inte kontinuerligt i realtid, så tätare kontroller ger sällan extra värde.

Måste vi anmäla till IMY om läckagevakten hittar en träff?

Bara om träffen rör er egen personuppgiftsbehandling och innebär en sannolik risk för de registrerades rättigheter. Enligt artikel 33 i GDPR gäller då en anmälningsfrist på 72 timmar från upptäckt. Att en enskild anställds privata e-postadress dyker upp i en helt orelaterad tredjepartsläcka utlöser normalt inte anmälningsplikten.

Kan vi använda samma lösning för att kontrollera anställdas lösenord i Active Directory?

Ja. Pwned Passwords-API:et stödjer NTLM-hashar utöver SHA-1, vilket gör det möjligt att integrera kontrollen direkt i lösenordspolicyn för Windows-domäner, till exempel via ett anpassat lösenordsfilter.

Vad händer om vi överskrider vår RPM-gräns?

API:et svarar med HTTP 429 och en retry-after-header som anger hur många sekunder du bör vänta innan nästa anrop. Koden i den här guiden hanterar det automatiskt genom att pausa och försöka igen.

Fungerar den här lösningen för att övervaka domäner i flera nordiska länder samtidigt?

Ja, HIBP:s API är inte begränsat till specifika länder eller toppdomäner. Samma kod fungerar oavsett om kundlistan innehåller .se-, .no-, .fi- eller .dk-adresser, så länge adresserna verifieras mot rätt domän i Pro-abonnemanget om du använder domänsökning.

Räcker Pwned Passwords-kontrollen som enda lösenordsskydd?

Nej. Den ska komplettera, inte ersätta, grundläggande krav som minimilängd och stark hashning vid lagring (bcrypt eller Argon2). Lösenordshashning i Node.js går igenom hur den delen byggs korrekt, och tvåfaktorsautentisering ger ytterligare ett skyddslager om ett lösenord ändå läcker.