Den 11 augusti 2026 passerade CISA:s katalog över aktivt utnyttjade sårbarheter, Known Exploited Vulnerabilities (KEV), gränsen på 1 665 poster. 181 av dem lades till bara under 2026. Samtidigt håller branschen på att byta språk: CVSS 4.0 har ersatt Scope-metriken med två separata skademodeller och lagt till en helt ny bas-metrik. Den som fortfarande läser sårbarhetsrapporter manuellt hinner inte längre med.
Den här guiden visar hur du bygger en egen bevakningspipeline i Python som hämtar CVE-data från NVD API v2.0, tolkar CVSS-poäng (både 3.1 och det nyare 4.0-formatet), matchar mot CISA:s KEV-katalog och skickar automatiska varningar via e-post eller Slack när något i din egen miljö plötsligt blir aktivt utnyttjat. Du behöver inga dyra verktyg. Allt bygger på öppna API:er och ett par hundra rader Python.
Upplägget passar särskilt team som redan känner sig bekväma med Python men saknar budget för en fullskalig plattform för sårbarhetshantering. Du får full insyn i varje regel som styr en varning, du äger datan själv, och du kan skräddarsy prioriteringslogiken efter din egen infrastruktur istället för att anpassa dig efter en leverantörs standardmall. Vi går igenom tio konkreta steg, ett komplett exempelprojekt du kan klistra in direkt, samt de fallgropar och felsökningsscenarier som annars kostar timmar att hitta på egen hand.
Vad är CVSS 4.0 och varför förändrar det sårbarhetshantering 2026
CVSS (Common Vulnerability Scoring System) är standarden som organisationen FIRST (Forum of Incident Response and Security Teams) underhåller för att gradera hur allvarlig en sårbarhet är, från 0 till 10. Poängen styr allt från patchprioritering till avtalskrav i upphandlingar. Problemet är att CVSS 3.1, som varit norm sedan 2019, aldrig riktigt fångade skillnaden mellan en sårbarhet som bara drabbar en enskild server och en som sprider sig vidare till andra system.
CVSS 4.0 löser det genom att dela upp konsekvensbedömningen i två separata delar och lägga till en ny metrik som beskriver vilka förutsättningar som måste vara på plats för att attacken ska fungera. Men adoptionen går långsamt. Enligt en analys av CVE:er publicerade under 2025 hade endast 25,9 procent av de 43 002 registrerade sårbarheterna fått en CVSS 4.0-poäng alls, oavsett källa. En separat rapport landar på liknande siffror: bara 24 procent av 2025 års CVE:er har en 4.0-poäng, och det är i praktiken databasen VulDB som står för nästan hälften av dem (49 procent), medan NVD, Microsoft, Red Hat och Oracle sällan publicerar 4.0-vektorer alls.
Det betyder att din bevakningspipeline måste kunna hantera båda versionerna samtidigt, ofta för samma sårbarhet. Det är precis vad vi bygger i den här guiden: ett system som läser cvssMetricV31 när det är det enda som finns, men föredrar cvssMetricV40 när NVD har fått en 4.0-vektor från den ansvariga CNA:n (CVE Numbering Authority).
CVSS har funnits sedan 2005, med version 2 år 2007, version 3.0 år 2015 och den finjusterade 3.1 år 2019. CVSS 4.0 släpptes av FIRST i november 2023, men det är först under 2025 och 2026 som verktygskedjan runt omkring, inklusive NVD:s eget API, hunnit ikapp och börjat exponera fälten fullt ut. Den utdragna övergångstiden är själva anledningen till att en bevakningspipeline måste vara version-agnostisk: bygger du bara för 4.0 missar du nästan tre fjärdedelar av alla nya sårbarheter, bygger du bara för 3.1 missar du den mer exakta konsekvensbedömningen på de sårbarheter där en CNA faktiskt levererat en 4.0-vektor.
CVSS 4.0 mot CVSS 3.1: metrikerna som förändrar allt
Innan du skriver en rad kod är det värt att förstå exakt vad som skiljer de två versionerna åt, eftersom fälten i NVD:s API speglar strukturen rakt av.
Base-gruppen och den nya Attack Requirements-metriken
CVSS 3.1 byggde sin bas-poäng på Attack Vector, Attack Complexity, Privileges Required, User Interaction och Scope, plus en enda uppsättning konsekvenser för Confidentiality, Integrity och Availability. CVSS 4.0 tar bort Scope helt. I stället får varje sårbarhet två separata konsekvensuppsättningar: Vulnerable System Impact (VC, VI, VA), som beskriver skadan på den sårbara komponenten själv, och Subsequent System Impact (SC, SI, SA), som beskriver skadan på system längre bort i kedjan. Dessutom introduceras Attack Requirements (AT), en helt ny bas-metrik som fångar förutsättningar i måltillståndet, till exempel att offret måste ha en specifik konfiguration aktiverad. User Interaction har också utökats från två till tre värden för att skilja på passiv och aktiv interaktion från användaren.
Threat, Environmental och Supplemental: kontext som spelar roll
Det som hette Temporal i CVSS 3.1 heter numera Threat, och två av de tre gamla metrikerna (Remediation Level och Report Confidence) är helt borttagna. Kvar är bara Exploit Maturity, som beskriver om en sårbarhet har ett bekräftat aktivt utnyttjande, ett proof-of-concept eller inget känt alls. Environmental-gruppen fungerar som tidigare och låter dig justera bas-metrikerna och sätta krav på konfidentialitet, integritet och tillgänglighet utifrån din egen verksamhet.
Helt nytt i 4.0 är Supplemental-gruppen. Den påverkar aldrig själva poängen, men ger extra kontext: Safety (fysisk skaderisk), Automatable (går attacken att skripta i skala), Recovery, Value Density och Provider Urgency. FIRST har också infört en tydlig namngivning så att man ser exakt vilka grupper som ingår i en given poäng: CVSS-B (bara bas), CVSS-BT (bas plus threat), CVSS-BE (bas plus environmental) och CVSS-BTE (alla tre poängsättande grupper).
| Aspekt | CVSS 3.1 | CVSS 4.0 |
|---|---|---|
| Metrikgrupper | Base, Temporal, Environmental | Base, Threat, Environmental, Supplemental |
| Scope-metrik | Finns (Unchanged/Changed) | Borttagen |
| Konsekvensmodell | En uppsättning C/I/A | Vulnerable System (VC/VI/VA) + Subsequent System (SC/SI/SA) |
| Attack Requirements | Saknas | Ny bas-metrik (AT) |
| User Interaction | 2 värden | 3 värden |
| Temporal/Threat-metriker | Exploit Code Maturity, Remediation Level, Report Confidence | Endast Exploit Maturity (RL och RC borttagna) |
| Supplemental-grupp | Finns inte | Ny, icke poängsättande (Safety, Automatable, Recovery, Value Density, Response Effort, Provider Urgency) |
| Vektorprefix | CVSS:3.1 | CVSS:4.0 |
En fullständig CVSS 4.0-vektor för en kritisk, nätverksbar sårbarhet utan krav på inloggning kan se ut så här: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. Det säger att attacken sker över nätverket (AV:N), har låg komplexitet (AC:L), inga särskilda förutsättningar (AT:N), kräver varken privilegier eller användarinteraktion, och ger full konfidentialitets-, integritets- och tillgänglighetsskada på den sårbara komponenten men ingen skada på andra system.
Förutsättningar: detta behöver du innan du börjar
- Python 3.11 eller senare installerat (kontrollera med
python3 --version) - pip för pakethantering, medföljer normalt Python
- Biblioteken
requests,python-dotenvochschedulei valfri aktuell version - En gratis NVD API-nyckel (registrera dig på NVD:s utvecklarsida)
- Ett Linux-, macOS- eller WSL-baserat skal för att kunna schemalägga med cron
- Ett SMTP-konto för e-postutskick, eller en Slack-arbetsyta med rättighet att skapa en inkommande webhook
- Grundläggande förtrogenhet med terminalen och JSON
- Cirka 60-75 minuter avsatt tid
Egen pipeline eller köpt plattform: när passar vilket
Kommersiella plattformar för sårbarhetshantering gör betydligt mer än det vi bygger här: fullständig tillgångsupptäckt, agentbaserad scanning, dashboards för ledningsgrupper och integrationer mot dussintals ärendehanteringssystem. För en organisation med redan etablerad säkerhetsavdelning är ett sådant verktyg ofta rätt val. Den egna pipelinen i den här guiden löser ett smalare men vanligare problem: du vet redan vilka fem, tio eller tjugo produkter som utgör din kritiska yta, och du vill ha ett enkelt, transparent sätt att bli larmad inom minuter när någon av dem dyker upp i KEV-katalogen eller får en hög CVSS-poäng.
Fördelen med att bygga själv är att du ser exakt vilken logik som ligger bakom varje larm, vilket gör det enkelt att felsöka och justera. Du är inte heller bunden till en leverantörs uppdateringscykel om NVD eller CISA ändrar sitt API. Nackdelen är lika tydlig: du äger underhållet. Om du redan har ett etablerat SIEM eller en vuxen säkerhetsfunktion kan det vara smartare att mata in samma NVD- och KEV-data i det verktyget istället för att bygga en fristående kedja. För mindre team, konsultbolag som bevakar flera kunders miljöer, eller utvecklingsteam som vill ha koll på sitt eget beroendeträd, är en fristående pipeline ofta den snabbaste vägen till faktiskt värde.
Steg 1: Skapa projektstruktur och virtuell miljö
Börja med en isolerad Python-miljö så att paketen inte krockar med annat på maskinen. Skapa en mapp, en virtuell miljö och en fil för hemligheter som API-nycklar och SMTP-uppgifter.
mkdir cve-bevakning && cd cve-bevakning
python3 -m venv venv
source venv/bin/activate
pip install requests python-dotenv schedule
touch .env pipeline.py assets.json
mkdir -p logs data
Filen .env håller känsliga uppgifter borta från källkoden. assets.json blir din egen lista över produkter och leverantörer som är relevanta för din organisation, vilket du bygger i steg 7. logs/ och data/ används för körningsloggar respektive cachad NVD-data.
Steg 2: Skaffa en NVD API-nyckel och förstå hastighetsgränserna
NVD:s API för sårbarheter går att använda utan nyckel, men då gäller en gräns på fem anrop per rullande 30-sekundersfönster per IP-adress. Med en gratis API-nyckel, som du beställer via NVD:s utvecklarsida och får skickad till din e-post, höjs gränsen till 50 anrop per 30 sekunder. Nyckeln skickas antingen som query-parametern apiKey eller i headern X-API-KEY. Överskrider du gränsen riskerar du ett tillfälligt block, vanligen med HTTP-status 403.
Lägg nyckeln i din .env-fil så att den aldrig hamnar i versionshanteringen:
NVD_API_KEY=din-nyckel-har
SMTP_HOST=smtp.exempel.se
[email protected]
SMTP_PASS=ditt-app-losenord
[email protected]
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzz
Steg 3: Hämta CVE-data från NVD API v2.0
NVD:s bas-URL för CVE-uppslag är https://services.nvd.nist.gov/rest/json/cves/2.0. Du kan filtrera på publiceringsdatum, nyckelord eller ett specifikt CVE-ID. Funktionen nedan hämtar alla CVE:er publicerade det senaste dygnet och respekterar hastighetsgränsen genom att pausa mellan anropen.
import os
import time
import requests
from datetime import datetime, timedelta, timezone
NVD_BASE = "https://services.nvd.nist.gov/rest/json/cves/2.0"
def fetch_recent_cves(hours=24, api_key=None):
end = datetime.now(timezone.utc)
start = end - timedelta(hours=hours)
params = {
"pubStartDate": start.strftime("%Y-%m-%dT%H:%M:%S.000"),
"pubEndDate": end.strftime("%Y-%m-%dT%H:%M:%S.000"),
"resultsPerPage": 200,
}
headers = {"apiKey": api_key} if api_key else {}
resp = requests.get(NVD_BASE, params=params, headers=headers, timeout=30)
resp.raise_for_status()
delay = 0.7 if api_key else 6.5
time.sleep(delay)
return resp.json().get("vulnerabilities", [])
Med en API-nyckel kan du hålla nere fördröjningen till under en sekund mellan anrop. Utan nyckel behöver du en paus på minst sex sekunder för att stanna under gränsen på fem anrop per 30 sekunder. Om ditt tidsfönster ger fler resultat än gränsen på 2 000 poster per sida måste du också loopa med parametern startIndex tills fältet totalResults i svaret är uttömt. I praktiken märker de flesta av det först om de bevakar väldigt breda nyckelord eller kör pipelinen mer sällan än varannan dag, då hinner fler CVE:er hopa sig mellan varje körning.
Steg 4: Tolka CVSS-vektorsträngar i Python
NVD:s svar innehåller ett metrics-objekt per CVE med två möjliga arrayer: cvssMetricV31 och cvssMetricV40. Din pipeline bör föredra 4.0 när den finns, men falla tillbaka på 3.1 eftersom det fortfarande är den version som täcker flest CVE:er (ungefär 179 000 poster i NVD:s dataset mot cirka 14 000 för 4.0, enligt en sammanställning av CVSS-fragmentering i NVD-data).
def extract_cvss(cve_item):
metrics = cve_item.get("cve", {}).get("metrics", {})
if "cvssMetricV40" in metrics:
data = metrics["cvssMetricV40"][0]["cvssData"]
return {
"version": "4.0",
"vector": data["vectorString"],
"score": data["baseScore"],
"severity": data["baseSeverity"],
}
if "cvssMetricV31" in metrics:
data = metrics["cvssMetricV31"][0]["cvssData"]
return {
"version": "3.1",
"vector": data["vectorString"],
"score": data["baseScore"],
"severity": data["baseSeverity"],
}
return {"version": None, "vector": None, "score": 0.0, "severity": "OKÄND"}
Notera att du alltid bör spara både vektorsträng och version i din egen datalagring. Det gör det möjligt att i efterhand förklara exakt varför en sårbarhet fick en viss poäng, vilket ofta efterfrågas i internrevisioner. Spara helst hela det råa JSON-svaret från NVD också, inte bara de fält du extraherat. Fältnamn och strukturer förändras ibland mellan API-versioner, och att ha originaldatan kvar sparar dig från att behöva hämta om historik du redan bearbetat.
Steg 5: Beräkna och klassificera allvarlighetsgrad automatiskt
Nästa steg är att slå ihop CVSS-poängen med din egen riskmodell. En ren poäng säger inte hela sanningen, en sårbarhet med CVSS 7,5 som redan finns i KEV-katalogen är i praktiken farligare än en isolerad 9,8 utan känd exploatering. Funktionen nedan viktar poängen mot KEV-status och mot om metadata nämner aktiv exploatering.
def prioritize(cve_id, cvss, in_kev, exploit_maturity=None):
score = cvss["score"] or 0.0
priority = "LÅG"
if in_kev:
priority = "AKUT"
elif score >= 9.0:
priority = "KRITISK"
elif score >= 7.0:
priority = "HÖG"
elif score >= 4.0:
priority = "MEDEL"
if exploit_maturity == "A":
priority = "AKUT"
return {"cve_id": cve_id, "priority": priority, "score": score, "in_kev": in_kev}
Modellen är avsiktligt enkel. Poängen från kod ovan används sedan för att bestämma hur snabbt en varning ska gå ut och till vilken kanal, vilket vi bygger i steg 8. Vill du göra modellen mer nyanserad kan du väga in fältet exploit_maturity från Threat-gruppen i CVSS 4.0, som beskriver om det redan finns ett bekräftat aktivt utnyttjande (värdet A), ett proof-of-concept (P) eller inget känt alls (U). Kombinerat med KEV-status ger det en betydligt mer träffsäker bild av verklig risk än en isolerad bas-poäng.
Steg 6: Hämta och filtrera CISA KEV-katalogen
CISA publicerar hela KEV-katalogen som en öppen JSON-fil utan krav på autentisering. Katalogen växte till 1 665 poster senast den 11 augusti 2026, varav 181 lades till bara under 2026 fram till det datumet. Till jämförelse innehöll katalogen 1 484 poster vid utgången av 2025, efter att 246 nya lagts till under hela året. Tillväxten har snarare accelererat än avtagit: CISA lade till 75 sårbarheter under andra kvartalet 2026, en ökning med 5,6 procent jämfört med 71 under första kvartalet.
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
def fetch_kev_catalog():
resp = requests.get(KEV_URL, timeout=30)
resp.raise_for_status()
vulns = resp.json()["vulnerabilities"]
return {v["cveID"]: v for v in vulns}
def is_in_kev(cve_id, kev_index):
return cve_id in kev_index
Bland de senaste tillskotten finns exempel som visar spridningen av produkter som drabbas. En sårbarhet i Zimbra Collaboration Suite (CVE-2026-73570) rör förfalskning av SOAP-autentisering och fick en CVSS-poäng på 9,1, med sju arbetsdagars åtgärdsfrist enligt CISA:s direktiv, ett fall vi gått igenom i detalj tidigare med fokus på svenska servrar. En sårbarhet för fjärrkörning av kod i MLflow Tracking Server (CVE-2026-64849) fick CVSS 9,8 med samma sjudagarsfrist. VMware vCenter Server fick en lokal privilegie-eskalering (CVE-2026-61209, CVSS 8,4) med 14 dagars frist, och Microsoft IKE Extensions fick en IPsec-relaterad buffertöverskridning (CVE-2026-58311, CVSS 8,8), också med 14 dagar på sig.
| CVE-ID | Produkt | CVSS | Åtgärdsfrist (KEV) |
|---|---|---|---|
| CVE-2026-73570 | Zimbra Collaboration Suite | 9,1 | 7 arbetsdagar |
| CVE-2026-64849 | MLflow Tracking Server | 9,8 | 7 arbetsdagar |
| CVE-2026-61209 | VMware vCenter Server | 8,4 | 14 arbetsdagar |
| CVE-2026-58311 | Microsoft IKE Extensions | 8,8 | 14 arbetsdagar |
| CVE-2026-16232 | Check Point SmartConsole | Ej publicerad vid tillägg | Nyligen tillagd (juli 2026) |
Notera att våra tidigare artiklar om VMware vCenter CVE-2026-59310 och Windows IKE CVE-2026-33824 rör andra, separata sårbarheter i samma produktfamiljer. Det är precis den typen av förväxling din pipeline måste undvika genom att alltid matcha på exakt CVE-ID, aldrig bara produktnamn.
Värt att komma ihåg är att KEV-katalogens åtgärdsfrister ursprungligen är bindande direktiv för amerikanska federala myndigheter, inte en global lag. Svenska och nordiska organisationer omfattas inte formellt, men katalogen fungerar ändå som en de facto branschstandard eftersom den bygger på bekräftad, verklig exploatering snarare än teoretisk risk. Många säkerhetsteam använder därför KEV-medlemskap som en egen intern prioriteringsregel, oavsett om man är juridiskt bunden av CISA:s tidsramar eller inte. Det är också anledningen till att vår pipeline flaggar KEV-träffar som AKUT oavsett CVSS-poäng, en sårbarhet som redan utnyttjas i det vilda är alltid mer akut än en teoretiskt allvarlig men opatchad brist utan känd aktivitet.
Ett drygt kvarts sekel av samlad data finns nu i katalogen sedan starten 2021, men själva tillväxttakten är det som är mest talande för 2026: från 71 nya poster under första kvartalet till 75 under andra, en accelererande takt som gör manuell bevakning allt mer orealistisk för team utan dedikerad kapacitet.
Steg 7: Matcha CVE:er mot din egen tillgångslista
Utan en lista över vad du faktiskt driftar blir varningarna oanvändbara brus. Skapa en enkel assets.json med produktnamn och nyckelord som förekommer i CVE-beskrivningarna.
{
"assets": [
{"name": "Zimbra Collaboration Suite", "keywords": ["zimbra"]},
{"name": "VMware vCenter", "keywords": ["vcenter", "vmware vcenter"]},
{"name": "Microsoft Exchange", "keywords": ["exchange server"]},
{"name": "Fortinet FortiGate", "keywords": ["fortios", "fortigate"]}
]
}
import json
def load_assets(path="assets.json"):
with open(path, encoding="utf-8") as f:
return json.load(f)["assets"]
def match_asset(description, assets):
desc_lower = description.lower()
hits = []
for asset in assets:
if any(kw in desc_lower for kw in asset["keywords"]):
hits.append(asset["name"])
return hits
Håll listan smal i början. Det är bättre att missa en obskyr komponent än att dränka teamet i falska positiva träffar från generiska ord som “server” eller “API”. Vill du ha högre precision än ren textmatchning kan du istället matcha mot CPE-strängar (Common Platform Enumeration), det standardiserade formatet NVD använder för att peka ut exakt produkt och versionsintervall som en CVE påverkar. Fördelen är att du slipper falska träffar när ett ord råkar förekomma i en beskrivning utan att produkten faktiskt är berörd. Nackdelen är att du måste hålla din egen CPE-lista uppdaterad varje gång ni byter version på en komponent, vilket är mer underhåll än en enkel nyckelordslista.
Steg 8: Bygg ett varningssystem med e-post och Slack
När en CVE både matchar en tillgång och antingen ligger i KEV eller har en CVSS-poäng över din tröskel, ska ett meddelande gå ut direkt. Nedan skickas AKUT- och KRITISK-prioriterade fynd till Slack, medan allt annat samlas i ett dagligt e-postsammandrag. Tanken är att undvika larmtrötthet: får teamet ett Slack-pip för varje enskild MEDEL-prioriterad sårbarhet slutar de läsa kanalen inom en vecka, medan ett samlat dagligt e-postmejl går att skumma på fem minuter utan att avbryta pågående arbete.
import smtplib
from email.mime.text import MIMEText
def send_slack_alert(webhook_url, cve_id, priority, score, assets):
text = (f":rotating_light: *{priority}* — {cve_id} (CVSS {score}) "
f"träffar: {', '.join(assets)}")
requests.post(webhook_url, json={"text": text}, timeout=10)
def send_email_digest(smtp_host, user, password, to_addr, findings):
lines = [f"{f['cve_id']}: {f['priority']} (CVSS {f['score']})" for f in findings]
body = "Dagens CVE-sammandrag:\n\n" + "\n".join(lines)
msg = MIMEText(body)
msg["Subject"] = f"CVE-bevakning: {len(findings)} nya träffar"
msg["From"] = user
msg["To"] = to_addr
with smtplib.SMTP_SSL(smtp_host, 465) as server:
server.login(user, password)
server.send_message(msg)
Vill du undvika att fastna med lösenord i klartext bör du använda ett app-lösenord eller en OAuth2-baserad SMTP-relä istället för kontots huvudlösenord, oavsett vilken e-postleverantör organisationen använder.
Steg 9: Schemalägg pipelinen med cron och logghantering
Kör pipelinen med jämna mellanrum, till exempel varje timme för KEV-katalogen (som uppdateras oregelbundet men ibland flera gånger per dag) och var sjätte timme för nya CVE:er från NVD. Lägg loggutskrift till fil så att du kan felsöka i efterhand.
# Redigera med: crontab -e
0 * * * * cd /home/dittnamn/cve-bevakning && ./venv/bin/python pipeline.py >> logs/pipeline.log 2>&1
Föredrar du systemd framför cron fungerar en .timer-enhet lika bra och ger dig strukturerad loggning via journalctl på köpet:
# /etc/systemd/system/cve-bevakning.service
[Unit]
Description=CVE- och KEV-bevakningspipeline
[Service]
Type=oneshot
WorkingDirectory=/home/dittnamn/cve-bevakning
ExecStart=/home/dittnamn/cve-bevakning/venv/bin/python pipeline.py
# /etc/systemd/system/cve-bevakning.timer
[Unit]
Description=Kör CVE-bevakning varje timme
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
Aktivera med systemctl enable --now cve-bevakning.timer, och kontrollera senaste körning med systemctl status cve-bevakning.timer. Fördelen jämfört med cron är att en misslyckad körning syns direkt i journalctl -u cve-bevakning.service istället för att tyst försvinna i en loggfil ingen läser.
Steg 10: Testa och validera hela flödet
Kör pipelinen manuellt först, med utökad loggning, innan du litar på cron-jobbet. Testa specifikt mot ett känt CVE-ID, till exempel ett av dem i tabellen ovan, för att bekräfta att både CVSS-tolkningen och KEV-matchningen fungerar som väntat.
python pipeline.py --dry-run --cve CVE-2026-73570
python pipeline.py --dry-run --hours 48
python pipeline.py # skarp körning
Lägg alltid till en --dry-run-flagga i din egen kod som skriver ut vad som skulle ha skickats utan att faktiskt trigga e-post eller Slack. Det sparar dig från att spamma teamet under utveckling.
Fullständigt exempelprojekt: hela pipelinen i en fil
Nedan är alla delar sammanfogade till ett körbart skript. Spara det som pipeline.py i projektmappen du skapade i steg 1.
import os
import json
import time
import argparse
import requests
from datetime import datetime, timedelta, timezone
from dotenv import load_dotenv
load_dotenv()
NVD_BASE = "https://services.nvd.nist.gov/rest/json/cves/2.0"
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
API_KEY = os.getenv("NVD_API_KEY")
def fetch_recent_cves(hours=24):
end = datetime.now(timezone.utc)
start = end - timedelta(hours=hours)
params = {
"pubStartDate": start.strftime("%Y-%m-%dT%H:%M:%S.000"),
"pubEndDate": end.strftime("%Y-%m-%dT%H:%M:%S.000"),
"resultsPerPage": 200,
}
headers = {"apiKey": API_KEY} if API_KEY else {}
resp = requests.get(NVD_BASE, params=params, headers=headers, timeout=30)
resp.raise_for_status()
time.sleep(0.7 if API_KEY else 6.5)
return resp.json().get("vulnerabilities", [])
def fetch_kev_catalog():
resp = requests.get(KEV_URL, timeout=30)
resp.raise_for_status()
return {v["cveID"]: v for v in resp.json()["vulnerabilities"]}
def extract_cvss(cve_item):
metrics = cve_item.get("cve", {}).get("metrics", {})
if "cvssMetricV40" in metrics:
d = metrics["cvssMetricV40"][0]["cvssData"]
return {"version": "4.0", "score": d["baseScore"], "vector": d["vectorString"]}
if "cvssMetricV31" in metrics:
d = metrics["cvssMetricV31"][0]["cvssData"]
return {"version": "3.1", "score": d["baseScore"], "vector": d["vectorString"]}
return {"version": None, "score": 0.0, "vector": None}
def load_assets(path="assets.json"):
with open(path, encoding="utf-8") as f:
return json.load(f)["assets"]
def match_asset(description, assets):
desc = description.lower()
return [a["name"] for a in assets if any(k in desc for k in a["keywords"])]
def prioritize(score, in_kev):
if in_kev:
return "AKUT"
if score >= 9.0:
return "KRITISK"
if score >= 7.0:
return "HÖG"
if score >= 4.0:
return "MEDEL"
return "LÅG"
def run(hours, dry_run):
assets = load_assets()
kev_index = fetch_kev_catalog()
findings = []
for item in fetch_recent_cves(hours=hours):
cve = item["cve"]
cve_id = cve["id"]
description = cve["descriptions"][0]["value"]
hits = match_asset(description, assets)
if not hits:
continue
cvss = extract_cvss(item)
in_kev = cve_id in kev_index
priority = prioritize(cvss["score"], in_kev)
findings.append({
"cve_id": cve_id, "priority": priority,
"score": cvss["score"], "assets": hits, "in_kev": in_kev,
})
if dry_run:
print(json.dumps(findings, indent=2, ensure_ascii=False))
else:
print(f"{len(findings)} relevanta fynd, skicka vidare till alarm-modul")
return findings
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--hours", type=int, default=24)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
run(args.hours, args.dry_run)
Detta är grundstommen. Koppla in send_slack_alert och send_email_digest från steg 8 i slutet av run() så har du en komplett, självkörande bevakningskedja.
Exempel på utdata från pipelinen
En körning med --dry-run mot en tillgångslista som innehåller Zimbra och VMware kan se ut så här:
[
{
"cve_id": "CVE-2026-73570",
"priority": "AKUT",
"score": 9.1,
"assets": ["Zimbra Collaboration Suite"],
"in_kev": true
},
{
"cve_id": "CVE-2026-61209",
"priority": "HÖG",
"score": 8.4,
"assets": ["VMware vCenter"],
"in_kev": true
}
]
Slack-meddelandet som genereras av samma körning blir kortfattat med flit, så att det går att skumma på mobilen: “AKUT — CVE-2026-73570 (CVSS 9.1) träffar: Zimbra Collaboration Suite”.
Vanliga fallgropar när du bygger CVE-bevakning
De flesta problem med hemmabyggda bevakningssystem handlar inte om trasig kod, utan om dålig kalibrering. Här är de fallgropar som oftast gör att ett team slutar lita på sina egna larm inom några veckor.
- Matchning på för generella nyckelord. Ord som “cloud” eller “server” ger hundratals falska träffar per dag och gör att teamet slutar läsa varningarna.
- Att bara läsa CVSS 3.1 och missa 4.0-vektorer. Eftersom NVD publicerar båda formaten parallellt riskerar du att missa den mer detaljerade konsekvensbedömningen om koden bara letar efter
cvssMetricV31. - Att strunta i hastighetsgränsen. Kör du utan paus och utan API-nyckel blockeras klienten efter några sekunder, vilket ofta upptäcks först dagar senare när larmen tystnat helt.
- Att inte cacha KEV-katalogen lokalt. Hämtar du hela filen vid varje litet skript-anrop belastar du CISA:s infrastruktur i onödan och riskerar timeouts under hög last.
- Att förväxla CVE-ID mellan liknande produkter. Två VMware vCenter-sårbarheter kan existera samtidigt med helt olika allvarlighetsgrad, som CVE-2026-59310 och CVE-2026-61209. Matcha alltid på exakt ID, aldrig bara produktnamn plus årtal.
- Att skicka alla varningar till samma kanal. Utan prioritering drunknar de akuta fallen bland lågriskfynd inom loppet av en vecka.
- Att glömma tidszonshantering. NVD:s tidsstämplar är i UTC. Jämför du dem mot lokal svensk tid utan konvertering riskerar du att räkna fel på fönstret och antingen missa eller dubbelhämta CVE:er kring midnatt.
Felsökning: vanliga problem och lösningar
Nedan är de problem som oftast dyker upp när pipelinen går från test till skarp drift, tillsammans med den snabbaste vägen till en fungerande lösning.
| Problem | Trolig orsak | Lösning |
|---|---|---|
| HTTP 403 från NVD API | Hastighetsgränsen överskriden | Öka fördröjningen mellan anrop eller skaffa en API-nyckel för 50 anrop/30 s |
Tomt vulnerabilities-fält | Fel datumformat i pubStartDate | Använd exakt formatet ÅÅÅÅ-MM-DDTHH:MM:SS.000 i UTC |
KeyError på cvssMetricV31 | CVE:n saknar helt CVSS-poäng ännu | Kontrollera nyckeln finns innan uppslag, sätt standardvärde 0.0 |
| KEV-hämtningen ger timeout | Ingen lokal cachning, upprepade anrop | Cacha JSON-filen lokalt och uppdatera högst en gång i timmen |
| Slack-webhooken svarar 404 | Webhook-URL:en har återkallats eller är felstavad | Skapa en ny inkommande webhook i Slack-appens inställningar |
| SMTP-inloggning misslyckas | Kontot kräver app-lösenord, inte huvudlösenordet | Generera ett app-specifikt lösenord hos e-postleverantören |
| Cron-jobbet körs aldrig | Sökvägen till venv eller skriptet är relativ | Använd absoluta sökvägar i crontab-raden |
| Dubbletter av samma varning | Ingen deduplicering mellan körningar | Spara skickade CVE-ID i en lokal SQLite-fil eller textfil och filtrera bort dem |
| Skriptet kraschar på encoding-fel | Beskrivningstext med specialtecken | Öppna och skriv alla filer med encoding="utf-8" |
Avancerade tips och svensk kontext
När grundpipelinen fungerar finns flera sätt att göra den vassare. Lägg till en lokal SQLite-databas för att cacha både CVE- och KEV-data, vilket minskar antalet API-anrop drastiskt och gör pipelinen motståndskraftig mot tillfälliga nätverksfel. Använd CPE-strängar (Common Platform Enumeration) istället för fritextmatchning när du vill ha exakt matchning mot versionsnummer, inte bara produktnamn. Vill du gå längre kan fynd med prioritet AKUT eller KRITISK skickas vidare till ett SIEM-system, till exempel enligt samma principer som i vår guide om API-säkerhetstestning, så att incidenthanteringen sker i samma verktyg som resten av säkerhetsövervakningen.
Värt att känna till för svenska organisationer: efter ett regeringsbeslut den 20 november 2025 flyttades ansvaret för CERT-SE:s cyberoperativa uppgifter, tidigare placerade hos Myndigheten för civilt försvar, den 1 juli 2026 till det nya Nationella cybersäkerhetscentret (NCSC) vid Försvarets radioanstalt. Det påverkar var svenska organisationer bör rapportera incidenter och söka vägledning om sårbarhetshantering, men det finns ännu inga offentliga, sammanställda siffror över hur snabbt svenska organisationer i praktiken patchar mot KEV-listan. Global data ger ändå en fingervisning om trycket: enligt VulnCheck:s rapport om aktiv exploatering sjönk mediantiden från att en CVE publiceras till att den dyker upp i KEV-katalogen från 120 dagar under 2025 till 80 dagar under första halvåret 2026. Den marginalen krymper, vilket gör att en manuell, veckovis genomgång av säkerhetsbulletiner helt enkelt inte hinner med längre.
Ett annat sätt att skärpa träffsäkerheten är att låta Environmental-metrikerna i CVSS 4.0 spegla din egen verksamhets kritikalitet. En sårbarhet i ett internt testsystem ska inte trigga samma AKUT-larm som samma sårbarhet i ett system som hanterar kunddata, även om bas-poängen är identisk. Bygg in det som en enkel multiplikator i prioritize()-funktionen baserat på vilken tillgång som träffas, hämtad från fältet du redan definierat i assets.json.
Slutligen: håll koll på att NVD sedan april 2026 arbetar efter en riskbaserad modell för sin egen anrikning av data. Omkring 29 000 äldre CVE:er publicerade före den 1 mars 2026 klassades då om till “Not Scheduled”, och NVD producerar inte längre en egen CVSS-poäng när den ansvariga CNA:n redan levererat en. Det innebär att en del CVE:er i din pipeline kommer sakna poäng helt, vilket funktionen extract_cvss() ovan redan hanterar genom att falla tillbaka på 0.0 och markera versionen som okänd.
Så mäter du att pipelinen faktiskt gör nytta
Ett vanligt misstag är att bygga larmet, koppla in det, och sedan aldrig utvärdera om det faktiskt hjälper. Sätt upp tre enkla mått från start. Först: tid från att en CVE dyker upp i KEV-katalogen till att ett larm når rätt person, målet bör ligga under 60 minuter givet att du kör pipelinen varje timme. Andra: andelen larm som teamet faktiskt agerar på, jämfört med de som ignoreras eller markeras som irrelevanta. Ligger den siffran under hälften är det ett tecken på att tillgångslistan eller nyckelorden i assets.json behöver skärpas. Tredje: täckningsgrad, det vill säga hur stor andel av din faktiska produktportfölj som finns representerad i tillgångslistan. En pipeline som bara bevakar fyra av tjugo kritiska system ger falsk trygghet snarare än verkligt skydd.
Gå igenom dessa tre mått en gång i månaden tillsammans med teamet som tar emot larmen. Justera trösklarna i prioritize()-funktionen om AKUT-kategorin genererar för många eller för få larm, och lägg till nya produkter i tillgångslistan varje gång infrastrukturen förändras. En bevakningspipeline som inte underhålls blir snabbt lika otillförlitlig som ingen bevakning alls, bara med falsk känsla av kontroll på köpet.
Dokumentera också varje gång ett larm faktiskt ledde till en patch inom fristen. Den historiken blir ovärderlig när ni ska visa för ledning eller revisorer att sårbarhetshanteringen är mer än ett dokument i en pärm, den är ett fungerande, mätbart flöde som går att följa från upptäckt till åtgärd.
Vanliga frågor
Är CVSS 4.0 obligatoriskt att använda 2026?
Nej. NVD publicerar fortfarande CVSS 3.1 som primär poäng för de flesta CVE:er och kommer inte retroaktivt räkna om äldre sårbarheter till 4.0. Din pipeline bör stödja båda formaten parallellt snarare än att kräva 4.0. Så länge de flesta stora leverantörer, inklusive Microsoft och Red Hat, fortsätter publicera i huvudsak 3.1-vektorer är det den version du bör räkna som standard i din egen rapportering, med 4.0 som ett tillägg när det finns tillgängligt.
Behöver jag betala för en NVD API-nyckel?
Nej, API-nyckeln är kostnadsfri och beställs direkt via NVD:s utvecklarsida. Den enda skillnaden mot att köra utan nyckel är den högre hastighetsgränsen, 50 anrop per 30 sekunder istället för fem.
Hur ofta uppdateras CISA:s KEV-katalog?
Oregelbundet, ibland flera gånger samma dag när nya bekräftade exploateringar dyker upp. Att hämta katalogen en gång i timmen är oftast en rimlig avvägning mellan aktualitet och belastning på CISA:s infrastruktur.
Kan jag använda samma pipeline för flera produkter samtidigt?
Ja. Lägg helt enkelt till fler poster i assets.json med relevanta nyckelord. Skriptet loopar redan igenom hela listan för varje CVE som hämtas.
Vad gör jag om en sårbarhet saknar CVSS-poäng helt?
Det händer allt oftare sedan NVD gick över till riskbaserad anrikning i april 2026. Behandla sådana fynd som medelhög prioritet som standard om de matchar en tillgång, snarare än att ignorera dem helt.
Räcker det med e-postvarningar, eller behövs Slack också?
Det beror på teamets arbetsflöde. E-post fungerar bra för dagliga sammandrag av lägre prioriterade fynd, medan Slack eller motsvarande snabbmeddelandekanal passar bättre för AKUT- och KRITISK-larm som kräver omedelbar uppmärksamhet.
Hur skiljer sig detta från att bara prenumerera på CISA:s e-postutskick?
CISA:s allmänna utskick täcker alla sårbarheter, oavsett om de rör din miljö. Den egna pipelinen filtrerar bort bruset och larmar bara på det som faktiskt matchar dina tillgångar, vilket i praktiken innebär betydligt färre men mer relevanta meddelanden. Ett generellt utskick kräver också att en människa manuellt läser och bedömer relevans för varje post, medan pipelinen gör den bedömningen automatiskt utifrån din tillgångslista och skickar vidare bara det som faktiskt kräver uppmärksamhet.
Går det att bygga vidare på detta för att också patcha automatiskt?
Pipelinen är byggd för upptäckt och prioritering, inte automatisk patchning. Kombinera den istället med en etablerad process för patchhantering, till exempel den vi beskriver i vår guide om CISA KEV-patchhantering, där bevakningen triggar ett ärende i ert ärendehanteringssystem snarare än att skriva över produktionsservrar direkt. Fler tekniska genomgångar av aktuella sårbarheter och säkerhetsverktyg hittar du i vår samlade bevakning av cybersäkerhet.




