Hoe voorkomt u dat AI-agents uw API-keys lekken?
Voorkom dat uw AI-agent API-keys lekt via prompt injection. Gebruik in plaats van echte keys altijd kortstondige tokens met beperkte rechten achter een veilige gateway.
Wat het buiten AI-agents houden van geheimen betekent
Een AI-agent is een normaal Linux-proces dat commando's uitvoert. Elke omgevingsvariabele die dat proces bevat, is leesbaar voor de code die het uitvoert. Een API-key in de omgeving van de agent is dus een sleutel die de agent naar elke bereikbare host kan sturen. Geheimen buiten de agent houden betekent dat u de agent een verwijzing geeft in plaats van de sleutel: een kortstondig token met beperkte rechten, of een tijdelijke aanduiding die door een ander systeem wordt vervangen door de werkelijke waarde bij de netwerkgrens.
Dit gaat niet over een model dat kwaadaardig wordt. Het mechanisme is prozaïscher. Een agent leest een webpagina, een README of een issue-commentaar met instructies en volgt deze op, omdat er voor een taalmodel geen verschil is tussen tekst die u schreef en tekst die het heeft opgehaald. Dat is prompt injection. Zodra dit gebeurt, wordt de schade door precies één ding beperkt: wat het proces kan lezen. Als u nog geen grens heeft ingesteld, behandelt veilig een coding agent op een server draaien de isolatieniveaus waarop deze handleiding voortbouwt.
Het dreigingsmodel in begrijpelijke taal
Voer dit uit als de gebruiker waaronder uw agent draait.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'Elke regel die dit script afdrukt, is slechts één HTTP-verzoek verwijderd van de server van een vreemde. Kijk nu wat er op de schijf staat in de buurt van de agent.
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'Een agent met toegang tot een shell heeft geen slimme exploit nodig om die gegevens te exfiltreren. Vier gangbare paden volstaan, en alle vier zien er in de logbestanden uit als normaal werk:
- Een uitgaande
curloffetchnaar een willekeurige host, waarbij de waarde in een query-string staat. - Een
git commitengit pushnaar een repository waar de agent schrijfrechten heeft. - Een installatiescript voor een pakket, dat willekeurige code uitvoert als de gebruiker van de agent.
- Een DNS-opzoekopdracht van een hostnaam die de waarde bevat; dit werkt zelfs als uitgaand HTTP-verkeer is geblokkeerd.
U kunt dit risico niet elimineren door enkel code te controleren. De oplossing is ervoor te zorgen dat er niets waardevols binnen bereik ligt.
Een geheim in de werkmap is een geheim in het contextvenster
Een agent leest bestanden. Een .env-bestand in de repository waarin de agent werkt, wordt gelezen. Zodra het is gelezen, bevindt het zich in het contextvenster. Dit betekent dat het in het transcript staat, in elk logboek dat u bijhoudt en in alles wat de agent vervolgens schrijft.
Voorheen, met de sleutel in de werkmap waarin de agent werkt:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingDaarna, met het bestand buiten bereik verplaatst:
sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.envDe gebruiker van de agent kan het bestand niet langer openen, omdat de werkmap het niet meer bevat. Ontzeggingsregels in de eigen configuratie van de agent vormen een tweede verdedigingslaag, niet de eerste. Claude Code leest toegangsregels uit .claude/settings.json in het project:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}Dit voorkomt de eerlijke fout dat de agent tijdens het verkennen een bestand opent. Het stopt echter niet dat een geïnjecteerde instructie base64 .env uitvoert, omdat dit een shell-commando is en geen bestand dat wordt gelezen. Of u wordt gevraagd voordat dat commando wordt uitgevoerd, hangt af van de toestemmingsmodus van de sessie, en auto mode wordt de standaardinstelling voor Claude Code in augustus 2026. Een server die u niet in de gaten houdt, zal dus meer van dergelijke commando's ongevraagd uitvoeren. Dezelfde beperking geldt voor alles wat de gewoonten van de agent vormgeeft in plaats van de rechten: een vaardigheid die de agent dwingt tot de kleinst mogelijke wijziging die werkt voorkomt dat een run afdwaalt naar bestanden die de agent niet hoefde te openen, maar het blijft advies waar het model van kan worden afgebracht. Beschouw de configuratie als een vangrail en de bestandssysteemrechten als de muur. Dezelfde splitsing is van toepassing binnen containers: env-bestanden en geheimen in Docker Compose behandelt de versie van dit probleem één laag dieper.
Geef elke agent een eigen gebruiker zonder privileges
Als de agent onder uw eigen account draait, erft deze uw SSH-sleutels, cloud-inloggegevens en shell-geschiedenis. Een aparte gebruiker kost slechts één commando en elimineert al deze risico's.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519De laatste regel moet falen met cat: /home/you/.ssh/id_ed25519: Permission denied. Als er in plaats daarvan een sleutel wordt getoond, is uw home-directory leesbaar voor de groep of voor iedereen; chmod 700 ~ lost dit op. Voeg de agent-gebruiker niet toe aan sudo en geef deze geen NOPASSWD-regel die ruimer is dan het ene commando dat strikt noodzakelijk is. Gebruikers met minimale privileges op een VPS behandelt de details over groepen en sudoers. Houd deze scheiding in gedachten zodra u meer dan één sessie op de server draait, want één Claude Code-sessie kan tekst direct naar een andere sturen, en alles waar de eerste sessie toegang toe heeft, kan via dat kanaal in één enkel bericht worden verstuurd.
Het is zinvol om op een cloud-VPS nog een extra grens in te stellen. De metadata-service van de instance reageert op een vast link-local adres en verstrekt vaak rol-gebaseerde inloggegevens aan iedereen die erom vraagt.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTControleer dit vanuit het perspectief van de agent. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ zou niets moeten weergeven en met een non-zero status moeten afsluiten, omdat het pakket wordt geweigerd voordat het de server verlaat.
Injecteer de inloggegevens bij de grens
Het patroon dat dit daadwerkelijk oplost, is het injecteren van inloggegevens. De agent beschikt nooit over een echte sleutel. Deze stuurt het verzoek via een lokale gateway, en de gateway vervangt onderweg een tijdelijke aanduiding door het echte geheim. Het geheim bevindt zich in de opslag van de gateway, in een ander proces, dat eigendom is van een andere gebruiker.
OneCLI is een open-source implementatie hiervan, onder de Apache-2.0 licentie, en draait als een container naast de agent. Sinds juli 2026 documenteert het project deze configuratie:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitHet dashboard luistert op poort 10254 en de gateway op 10255. U slaat de echte inloggegevens eenmalig op en geeft elke agent vervolgens een tijdelijke waarde in plaats van de sleutel, aangevuld met een eigen scoped access token, die de agent meestuurt in een Proxy-Authorization header. De gateway koppelt het uitgaande verzoek aan de hand van host en pad, ontsleutelt de bijbehorende inloggegevens en voert de vervanging uit. De omgeving van de agent bevat niets dat de moeite waard is om te stelen.
De waarde hiervan zit niet in de versleuteling. Het punt is dat de vraag "wat heeft deze agent gebruikt, en wanneer" een log-query wordt. U leest één audit-trail in plaats van te moeten gissen welke van de zes omgevingen een kopie van de sleutel bevatte.
Geef het geheim door aan het proces, niet aan de omgeving
Als u de agent onder systemd draait, heeft u helemaal geen omgevingsvariabelen nodig. LoadCredential= plaatst het geheim in een privédirectory die alleen die service kan lezen, blootgesteld als %d in het unit-bestand en als $CREDENTIALS_DIRECTORY binnen het proces. De waarde verschijnt nooit in /proc/<pid>/environ, dus ps eww kan deze niet tonen, en de directory verdwijnt wanneer de service stopt.
Versleutel de credential eerst voor de machine. Deze commando's komen uit de systemd-documentatie en werken op systemd 250 of nieuwer, wat Ubuntu 24.04 en Debian 13 omvat:
echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
systemd-creds cat api_keyHet laatste commando print sk-example-value. Dat bewijst dat het versleutelde bestand op deze host ontsleutelt. Verwijs er vervolgens naar vanuit de unit:
[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-workerUw agent-code opent het bestand op $AGENT_KEY_FILE wanneer deze de waarde nodig heeft. Een bestand lezen is een momentopname. Een omgevingsvariabele blijft bestaan gedurende de levensduur van het proces, in elk kindproces dat het start.
Geef de voorkeur aan kortstondige tokens boven langdurige sleutels
Een sleutel die nooit verloopt, blijft geldig zodra deze maanden later opduikt in een logbestand of transcriptie. Wanneer de service een sessietoken aanbiedt, gebruik dan het sessietoken en stel de kortst mogelijke levensduur in die voor uw werkzaamheden volstaat.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900Vijftien minuten is het minimum dat AWS STS (security token service) accepteert, en dit is doorgaans voldoende voor één agent-taak. Geef voor GitHub de agent-gebruiker een eigen gh-login met een fijnmazig token dat beperkt is tot de specifieke repository waarvoor het wordt gebruikt, zodat gh auth token binnen die sessie niets anders kan benaderen. Beperk de toegang eerst op basis van resources en daarna op basis van tijd.
Verifieer, en blijf verifiëren
Drie controles zijn nuttig na elke wijziging in de configuratie van een agent. Voer deze uit als de gebruiker van de agent, niet als uzelf.
sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/userDe eerste hoort niets weer te geven. De tweede hoort ls: cannot open directory '/home/you/': Permission denied weer te geven. De derde vertelt u welke identiteit het netwerkpad van de agent presenteert; dit is de vraag waarvoor het gateway-patroon bestaat: een 401 betekent dat de agent geen eigen GitHub-referentie draagt, en een 200 betekent dat er wel een aanwezig is, dus u dient te weten welk token dit is. Als u agents onbeheerd laat draaien, behandelt het beheersen van AI-agentkosten op een VPS de budgetlimieten die bij deze toegangsbeperkingen horen.
FAQ
Kan ik erop vertrouwen dat het model mijn keys niet lekt?
Nee, want in dit dreigingsmodel is het model niet de aanvaller. De agent leest tekst van webpagina's, repositories en issue trackers, en die tekst kan instructies bevatten. Het model heeft geen betrouwbare manier om uw instructies te onderscheiden van de opgehaalde tekst. Elke controle die afhangt van de juiste keuze van het model faalt zodra een geïnjecteerde instructie overtuigend is. De controle moet daarom in het besturingssysteem of het netwerk worden ondergebracht.
Zijn omgevingsvariabelen echt zo slecht voor agent-geheimen?
Ze zijn slecht op één specifiek punt: ze worden overgeërfd. Elk kindproces dat de agent start, krijgt een kopie, inclusief build-scripts, test-runners en hooks voor pakketinstallaties. De variabelen zijn bovendien leesbaar via /proc/<pid>/environ door dezelfde gebruiker, waardoor alles wat de agent uitvoert ze kan inzien zonder dat de agent ze expliciet doorgeeft. Een bestand dat op het moment van gebruik wordt gelezen, met LoadCredential= of een gateway, beperkt de blootstelling tot dat specifieke moment.
Lost het opslaan van geheimen in een vault dit op zichzelf op?
Slechts gedeeltelijk. Een vault lost de opslag op. Als u die vault zelf host, heeft deze een eigen hardening-traject nodig, omdat een Vaultwarden-server meestal wordt gecompromitteerd via het admin-token of het back-upbestand, en niet via de versleutelde items zelf. Het lost de laatste stap niet op, waarbij iets het geheim uit de vault haalt en aan de agent geeft als omgevingsvariabele; daarmee bent u terug bij af. Waar het om gaat is wie de substitutie uitvoert. Als de agent het geheim ophaalt, heeft de agent het geheim. Als een gateway of het init-systeem de substitutie buiten het proces van de agent uitvoert, krijgt de agent het geheim nooit in handen.
Hoe weet ik of een agent al iets heeft gelekt?
Meestal kunt u dit achteraf niet vaststellen; dat is het argument voor de gateway. Zonder gateway is uw bewijslast verspreid over de shell-geschiedenis, het transcript van de agent en uitgaande verbindingslogs die u waarschijnlijk niet bijhoudt. Met een credential-gateway is elk gebruik van een credential één regel met een agent-identiteit en een tijdstempel. Als u een lek vermoedt, roteer dan eerst de key en onderzoek daarna pas. Rotatie is goedkoop, zekerheid niet.
Wat is het minimale dat ik vandaag moet doen?
Verplaats elk .env-bestand uit de mappen waarin uw agents werken en maak één gebruiker zonder privileges aan per agent. Deze twee wijzigingen kosten ongeveer tien minuten en sluiten het meest voorkomende pad af: een agent die een credential-bestand leest dat geen reden had om naast de code te staan. De gateway en de kortstondige tokens zijn de volgende stap, niet de eerste. Hetzelfde startpunt geldt voor elke agent-runtime, inclusief het veilig draaien van een autonome agent op een VPS.