Is LiteLLM veilig? Het dreigingsmodel voor je LLM-gateway
LiteLLM bewaart al je API-sleutels op één plek. Wat betekenen de PyPI-aanval van maart 2026 en de lekken van dit jaar, en welke maatregelen heb je zelf in handen?
Is LiteLLM veilig? Het korte antwoord
Ja, LiteLLM is veilig genoeg om zelf te draaien, als je het behandelt als de kluis die het is. De gateway bewaart de API-sleutels van al je providers op één plek. Een lek in LiteLLM is daarom een lek van al die sleutels tegelijk. In 2026 had het project een besmette PyPI-release, een logincident en een reeks ernstige kwetsbaarheden. Wie een vaste Docker-image draaide, poort 4000 niet open had staan en op tijd bijwerkte, liep bij de meeste daarvan weinig risico.
Dit stuk loopt het dreigingsmodel stap voor stap door. Eerst de vraag wat er op het spel staat. Daarna de incidenten van 2026, met de versies uit de officiële advisories. Tot slot de maatregelen die je zelf in de hand hebt. Dit is de stand van zaken op 4 oktober 2026. Er komen nieuwe advisories bij, dus controleer altijd de actuele lijst voordat je een versie kiest.
Waarom een LLM-gateway een aantrekkelijk doelwit is
LiteLLM is een proxy die tientallen LLM-providers achter één OpenAI-compatibele API zet. Je apps praten met LiteLLM. LiteLLM praat met OpenAI, Anthropic, Mistral of Azure. Hoe je dat opzet, staat in onze handleiding voor een eigen LiteLLM-gateway op een VPS.
Dat ontwerp heeft een prijs. Op de gateway staan de echte providersleutels, de master key, de database met virtuele sleutels en vaak ook logs met prompts en antwoorden. Iemand die de gateway overneemt, hoeft niet per provider in te breken. Die heeft alles in één keer. De term daarvoor is blast radius: hoeveel schade één gecompromitteerd onderdeel kan aanrichten. Bij een LLM-gateway is die maximaal, omdat het hele punt van de gateway is dat alle sleutels daar samenkomen.
Daar komt bij dat LLM-sleutels direct geld waard zijn. Met een gestolen sleutel draait een aanvaller modellen op jouw rekening. Dat merk je pas aan de factuur, of aan een budgetlimiet die je hopelijk hebt ingesteld.
Wat gebeurde er op 24 maart 2026 met LiteLLM op PyPI?
Op 24 maart 2026 verschenen op PyPI twee versies die het LiteLLM-team niet zelf had gebouwd: litellm==1.82.7 en litellm==1.82.8. Volgens het officiële advisory stonden ze vanaf 10:39 UTC ongeveer veertig minuten online. Daarna zette PyPI ze in quarantaine. LiteLLM noemt iedereen mogelijk getroffen die op die dag tussen 10:39 en 16:00 UTC LiteLLM via pip installeerde of bijwerkte.
De besmette pakketten bevatten een credential stealer. Die zocht naar omgevingsvariabelen, SSH-sleutels, cloudsleutels voor AWS, GCP en Azure, Kubernetes-tokens en databasewachtwoorden. De buit werd versleuteld en via een POST-verzoek naar models.litellm.cloud gestuurd. Dat domein hoort niet bij het project. Analyses van derden beschrijven dat 1.82.8 daarnaast een bestand litellm_init.pth meebracht. Python voert .pth-bestanden uit bij elke start van de interpreter, dus de code draaide al zonder dat iemand import litellm typte.
Hoe kwam de aanvaller erin? LiteLLM schrijft dat de inbraak vermoedelijk begon bij Trivy, een beveiligingsscanner die in de eigen CI/CD-pijplijn draaide (continuous integration en continuous delivery: de geautomatiseerde bouw- en releasestraat). Met de buitgemaakte publicatierechten uploadde de aanvaller de pakketten rechtstreeks naar PyPI, buiten de officiële workflow om. Externe analyses koppelen de aanval aan de groep TeamPCP en aan een bredere campagne rond Trivy. Die toeschrijving staat niet in het advisory van LiteLLM zelf.
Waarom het installatiepad bepaalde wie geraakt werd
Het advisory is hier duidelijk. De officiële proxy-image ghcr.io/berriai/litellm was niet getroffen, omdat die image zijn afhankelijkheden vastlegt in requirements.txt en de besmette PyPI-pakketten niet gebruikte.
Wie wel geraakt kon worden:
- wie op 24 maart
pip install litellmofpip install -U litellmdraaide zonder versienummer; - wie die dag een eigen Docker-image bouwde met een ongepinde
pip install litellmerin; - wie een ander pakket installeerde dat LiteLLM als ongepinde afhankelijkheid binnenhaalt.
Het verschil zit dus niet in Docker tegenover pip. Het zit in vastgepind tegenover ongepind. Een ongepinde installatie vraagt PyPI om de nieuwste versie en vertrouwt blind wat terugkomt. Een vaste versie, of nog beter een vaste hash, vraagt om precies dat ene artefact dat je eerder hebt gecontroleerd. Hetzelfde patroon zie je bij supply-chain-aanvallen via npm op een server: de schade valt bij wie automatisch de nieuwste versie ophaalt.
Ben ik geraakt? Zo controleer je het
Kijk eerst welke versie in je Python-omgevingen staat. Doe dit in elke virtualenv en in elke container waarin LiteLLM via pip kwam.
pip show litellm | grep -i '^version'
sudo find / -name 'litellm_init.pth' -not -path '/proc/*' 2>/dev/nullEen versie anders dan 1.82.7 of 1.82.8 en geen uitvoer van find is het gezonde resultaat. Zie je wel een van die versies, of vindt find het bestand? Ga er dan van uit dat elk geheim op die machine is uitgelezen. Het advisory adviseert in dat geval alles te roteren: API-sleutels, cloudsleutels, databasewachtwoorden, SSH-sleutels en Kubernetes-tokens. Alleen de versie verwijderen is niet genoeg, omdat de diefstal al bij de installatie plaatsvond.
De kwetsbaarheden van 2026, één voor één
De versies hieronder komen uit de advisories van LiteLLM en de GitHub Security Advisories van het project. Controleer ze opnieuw op het moment dat je dit leest.
CVE-2026-42208: SQL-injectie bij de sleutelcontrole
Het advisory van 29 april 2026 beschrijft een SQL-injectie in de code die API-sleutels controleert. Een verzoek zonder geldige sleutel, met een speciaal gemaakte Authorization: Bearer-header, kon onder bepaalde omstandigheden een kwetsbare databasequery bereiken. Getroffen waren v1.81.16 tot en met v1.83.6. De fix zit in v1.83.7 en later. LiteLLM raadt v1.83.10-stable aan. Dit is ernstig omdat er geen sleutel voor nodig was, en de database is precies de plek waar de sleutels staan.
CVE-2026-42271: commando's uitvoeren via de MCP-testendpoints
Twee endpoints om een MCP-server (Model Context Protocol) te testen voordat je hem opslaat, POST /mcp-rest/test/connection en POST /mcp-rest/test/tools/list, accepteerden een volledige serverconfiguratie. Daarin zaten ook de velden command, args en env. De proxy startte dat commando als subprocess op de host. De enige controle was een geldige proxysleutel, zonder rolcheck. Elke houder van een gewone gebruikerssleutel kon dus willekeurige commando's draaien met de rechten van het proxyproces. Getroffen: >=1.74.2 en <1.83.7. Opgelost in 1.83.7, waar beide endpoints nu de rol PROXY_ADMIN eisen. Kun je niet direct bijwerken, dan adviseert LiteLLM beide paden te blokkeren in je reverse proxy. De Amerikaanse overheidsdienst CISA zette deze CVE in juni 2026 op haar lijst van actief misbruikte kwetsbaarheden.
GHSA-4xpc-pv4p-pm3w: authenticatie omzeilen via de Host-header
De authenticatielaag leidde de route af uit request.url.path. Het onderliggende framework bouwt die waarde mede op uit de Host-header. Een gemanipuleerde header kon de authenticatie daardoor een andere route laten beoordelen dan de route die werkelijk werd uitgevoerd. Het gevolg: onbevoegde toegang tot beheerroutes. Getroffen zijn versies vóór 1.84.0. De fix zit in 1.84.0. Aanvullende verharding staat in v1.84.3, v1.85.2, v1.86.2 en v1.83.10-stable.patch.3.
Het advisory noemt drie voorwaarden die allemaal waar moeten zijn. Je draait de proxyserver (niet alleen de Python-SDK). Je zit op een versie vóór 1.84.0. En onbetrouwbare clients kunnen de listener bereiken. Een CDN, WAF of reverse proxy die de Host-header controleert of vast instelt, blokkeert de aanval. Daarom staat het afschermen van poort 4000 verderop zo hoog. Let op: de bronnen noemen verschillende CVE-nummers voor dit lek. Zoek het daarom op via de GHSA-code.
GHSA-7hp6-4w63-5g45: één sleutel voor twee taken
Op 30 september 2026 verscheen een kritiek advisory over LITELLM_SALT_KEY. De proxy gebruikte één sleutel voor twee doelen: geheimen versleutelen in de database en sessietokens maken. Een ingelogde interne gebruiker kon daardoor een token vervalsen en proxy-admin worden. Getroffen zijn onder meer 1.91.0 tot en met 1.100.3, met fixes in 1.100.4, 1.101.3, 1.102.2, 1.103.1 en 1.104.0rc2. Als tijdelijke maatregel noemt het advisory EXPERIMENTAL_UI_LOGIN=false. Dat schakelt wel CLI-SSO en de Claude Code gateway-login uit.
18 maart 2026: logs en traces zijn ook een geheimenkluis
Het incidentrapport van 18 maart 2026 laat een ander soort risico zien. Custom guardrails die het volledige request-object teruggaven, zorgden ervoor dat secret_fields.raw_headers in de logs belandde. Daarin stonden Authorization-headers met API-sleutels in leesbare tekst. Ze verschenen in de spend logs, die admins kunnen zien, en in OpenTelemetry-traces, die iedereen met toegang tot de observability-backend kan lezen. Het lek trad alleen op bij een custom guardrail die de hele dictionary teruggaf, in combinatie met de standaard guardrail-logging. De fix zit in 1.82.3 en later.
De les reikt verder dan deze ene bug. Elke plek waar requests worden opgeslagen, kan geheimen bevatten: spend logs, traces, debuglogs en back-ups van de database. Wie toegang heeft tot je Grafana of Langfuse, heeft dan in feite toegang tot je sleutels. Meer over dat patroon lees je in hoe je geheimen buiten AI-agents en hun logs houdt.
Wat LiteLLM zelf heeft verbeterd
Een eerlijk beeld hoort hier ook bij. Na het PyPI-incident bracht LiteLLM een schone v1.83.0 uit via een nieuwe CI/CD v2-pijplijn. Op 30 maart 2026 beschreef het team die pijplijn: geïsoleerde omgevingen, strengere beveiligingspoorten en een striktere scheiding van releases. In april volgden een audit met Veria Labs en de start van een bug bounty-programma. Op 8 september 2026 publiceerde LiteLLM een bijgewerkt SOC 2 Type 2-rapport. Dat is een audit van de interne beveiligingsprocessen door een externe partij.
Het grote aantal advisories zegt deels iets over de codebasis en deels over het feit dat er nu actief naar lekken wordt gezocht. De maandelijkse updates van het team noemden in juli 38 en in augustus 79 beveiligingsfixes. Een SOC 2-rapport gaat over processen bij het bedrijf. Het zegt niets over de configuratie van jouw server. Dat deel blijft jouw werk.
Maatregelen die jij zelf in handen hebt
De voorbeelden hieronder zijn bedoeld om zelf uit te voeren en aan te passen. Ze gaan uit van Ubuntu 24.04, Docker en nginx.
De master key hoort nooit in een app
De master key kan alles: sleutels maken, modellen toevoegen, budgetten aanpassen. Gebruik hem alleen voor beheer. Elke app krijgt een eigen virtuele sleutel. Maak de master key lang en willekeurig, en zet hem in een bestand dat alleen root kan lezen. LiteLLM eist dat de master key begint met sk-.
sudo install -d -m 700 /etc/litellm
sudo sh -c 'umask 077; printf "LITELLM_MASTER_KEY=sk-%s\nLITELLM_SALT_KEY=sk-%s\n" "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > /etc/litellm/litellm.env'
sudo ls -l /etc/litellm/litellm.envls -l hoort -rw------- en eigenaar root te tonen. Staat er iets anders, dan kunnen andere gebruikers op de server je sleutels lezen.
Poort 4000 en /ui horen niet op het internet
Docker publiceert poorten via eigen iptables-regels, en die gaan voor ufw. Een -p 4000:4000 staat daardoor open voor de hele wereld, ook als ufw status iets anders suggereert. Bind de poort aan localhost.
sudo docker run -d --name litellm --restart unless-stopped \
-p 127.0.0.1:4000:4000 \
--env-file /etc/litellm/litellm.env \
-v /etc/litellm/config.yaml:/app/config.yaml:ro \
ghcr.io/berriai/litellm:<vaste-versietag> \
--config /app/config.yaml
sudo ss -tlnp | grep 4000ss moet 127.0.0.1:4000 tonen, niet 0.0.0.0:4000. Test daarna vanaf je eigen laptop met curl -m 5 http://<publiek-ip>:4000/ en verwacht een time-out of Connection refused.
Apps die van buitenaf moeten praten, laat je via nginx binnen. Twee regels doen hier het meeste werk. De Host-header wordt vast ingesteld, zodat LiteLLM nooit de header van de client ziet. En de beheerpaden worden publiek geblokkeerd.
server {
listen 443 ssl;
server_name llm.example.nl;
# ssl_certificate en ssl_certificate_key hier
location ~ ^/(ui|mcp-rest/test) {
return 403;
}
location / {
proxy_pass http://127.0.0.1:4000;
proxy_set_header Host llm.example.nl;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}Controleer met sudo nginx -t && sudo systemctl reload nginx, en daarna met curl -s -o /dev/null -w '%{http_code}\n' https://llm.example.nl/ui. Dat moet 403 geven. Voor beheer open je een SSH-tunnel met ssh -L 4000:127.0.0.1:4000 jij@server en ga je naar http://localhost:4000/ui. Wil je de UI toch via de browser bereikbaar maken, zet er dan oauth2-proxy met een echte login voor. Wie helemaal geen poort wil openen, kan ook een Cloudflare Tunnel zonder open poorten gebruiken. Blokkeer de beheerpaden dan op dezelfde manier.
Virtuele sleutels met een beperkte scope
Een virtuele sleutel die alleen bepaalde modellen mag gebruiken, binnen een budget en met een vervaldatum, maakt een gelekte sleutel een stuk minder waard.
curl -s http://127.0.0.1:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H 'Content-Type: application/json' \
-d '{"models": ["gpt-4o-mini"], "max_budget": 20, "duration": "30d", "metadata": {"app": "helpdesk-bot"}}'Het antwoord bevat een key die met sk- begint. Een verzoek met die sleutel naar een model buiten de lijst hoort te worden geweigerd, met een foutmelding dat de sleutel geen toegang heeft tot dat model. Test dat één keer, zodat je weet dat de scope echt werkt.
Ga voorzichtig om met LITELLM_SALT_KEY
Met LITELLM_SALT_KEY versleutelt LiteLLM de providersleutels in de database. De documentatie is hier streng: verander hem niet nadat je modellen hebt toegevoegd, want dan zijn de opgeslagen sleutels onleesbaar. Bewaar hem dus apart van de database-back-up. Een back-up met de database en de salt key in hetzelfde archief geeft de vinder alle sleutels in leesbare vorm. Na het advisory van 30 september weet je ook dat deze sleutel meer beschermt dan alleen de opslag. Werk bij naar een gepatchte versie.
Pin de image-tag en lees de release notes
LiteLLM zegt het zelf: pin een versie en gebruik geen :latest. Nog strenger is pinnen op de digest, de SHA-256-hash van de image.
sudo docker pull ghcr.io/berriai/litellm:<vaste-versietag>
sudo docker inspect --format '{{index .RepoDigests 0}}' ghcr.io/berriai/litellm:<vaste-versietag>De uitvoer eindigt op @sha256: gevolgd door een lange hash. Gebruik die volledige naam in je docker run of compose-bestand. Dan draait een nieuwe pull nooit stilletjes een andere image. Installeer je toch via pip, gebruik dan pip install --require-hashes -r requirements.txt met hashes in het bestand. Hoe hashes je beschermen, lees je in downloads controleren met checksums. Lees bij elke update de release notes en de advisorylijst op GitHub, juist omdat je niet meer automatisch bijwerkt.
Roteer sleutels na elk vermoeden van een lek
Draaide je een getroffen versie terwijl de proxy bereikbaar was? Roteer dan in deze volgorde: eerst de providersleutels bij OpenAI, Anthropic en de anderen, dan de master key, dan de virtuele sleutels. De providersleutels gaan eerst, omdat die direct geld kosten. Een virtuele sleutel blokkeer je zonder hem te verwijderen met een POST naar /key/block, met {"key": "sk-..."} in de body. Automatisch regenereren via de API is een Enterprise-functie, dus reken op handwerk.
Wat zelf hosten een Europese lezer oplevert, en wat niet
Zelf hosten levert twee echte dingen op. Er zit geen externe router, zoals een gehoste gateway, tussen jouw app en de provider. Er ziet dus één partij minder je prompts. En je logs, spend-gegevens en traces staan op een server die jij kiest, bijvoorbeeld in een datacenter in Amsterdam of Frankfurt. Dat maakt de verwerking onder de AVG (Algemene verordening gegevensbescherming) beter te overzien.
Het verandert niets aan de bestemming. Configureer je OpenAI of Anthropic, dan gaan je prompts nog steeds naar die Amerikaanse providers. LiteLLM geeft verkeer door. Het filtert niets. Je verwerkersovereenkomst met die providers blijft dus nodig. Wil je dat prompts in de EU blijven, dan moet je zelf modellen kiezen die daar draaien: een Europese provider, een EU-regio bij een cloudprovider, of een lokaal model. Zelf hosten geeft je de controle over het pad. De bestemming kies je in je config.yaml.
Hoe je deze geschiedenis leest
De incidenten hierboven zijn gedateerd, omdat de lijst langer wordt. Wat stabiel blijft, is het patroon. Bijna elke ernstige kwetsbaarheid van 2026 vroeg om een bereikbare proxy, en de PyPI-aanval vroeg om een ongepinde installatie. Wie de beheerpaden afschermt, vaste versies draait en de advisories volgt, verkleint ook het risico bij het volgende lek. Dezelfde manier van denken vind je in onze analyse van de beveiliging van Tailscale: kijk eerst wat een onderdeel kan als het misgaat, en pas daarna naar wat de leverancier belooft.
FAQ
Was de officiële LiteLLM Docker-image getroffen door de PyPI-aanval van maart 2026?
Nee. Volgens het advisory van LiteLLM was ghcr.io/berriai/litellm niet getroffen, omdat die image zijn afhankelijkheden vastlegt in requirements.txt en de besmette pakketten 1.82.7 en 1.82.8 niet gebruikte. Een eigen image die op 24 maart 2026 een ongepinde pip install litellm uitvoerde, kon wel besmet raken.
Welke LiteLLM-versie heb ik nodig om de bekende lekken van 2026 te dichten?
CVE-2026-42208 en CVE-2026-42271 zijn opgelost in 1.83.7. Het Host-header-lek GHSA-4xpc-pv4p-pm3w is opgelost in 1.84.0. Het salt key-advisory van 30 september 2026 vraagt om een van de gepatchte versies, zoals 1.100.4 of 1.103.1. Kijk altijd in de advisorylijst op GitHub voordat je kiest, want na oktober 2026 kunnen er nieuwe advisories bij zijn gekomen.
Is het veilig om de LiteLLM-beheer-UI op het internet te zetten?
Niet zonder extra laag. Meerdere lekken in 2026 gingen over beheerroutes die bereikbaar waren voor onbetrouwbare clients. Bind poort 4000 aan 127.0.0.1, blokkeer /ui in je reverse proxy, en gebruik een SSH-tunnel of een login-laag zoals oauth2-proxy voor beheer.
Moet ik mijn API-sleutels roteren na een LiteLLM-advisory?
Roteer als je een getroffen versie draaide terwijl de proxy bereikbaar was voor anderen, of als je 1.82.7 of 1.82.8 hebt geïnstalleerd. Begin met de providersleutels, omdat die direct geld kosten. Daarna volgen de master key en de virtuele sleutels. Controleer ook je spend logs en traces, want die kunnen sleutels bevatten.