SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor

Hoe voorkomt u dat AI-agents uw API-keys lekken?

Voorkom dat uw AI-agents gevoelige API-keys stelen via prompt injection. Gebruik kortstondige tokens en een credential gateway in plaats van statische omgevingsvariabelen.

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 curl of fetch naar een willekeurige host, waarbij de waarde in een query-string staat.
  • Een git commit en git push naar 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 map waarin de agent werkt:

cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing

Daarna, 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/.env

De gebruiker van de agent kan het bestand niet langer openen, omdat de werkmap het niet meer bevat. Ontzegregels in de eigen configuratie van de agent vormen een tweede laag, 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 reikt verder dan de bestandstools: Claude Code controleert deze regels ook tegen bestandscommando's die het herkent in Bash, zoals cat en head, en tegen de doelen van omleidingen zoals < .env. Het voorkomt niet dat een geïnjecteerde instructie grep -r KEY . uitvoert binnen de projectmap, of een kort Python-script dat zelf .env opent. De documentatie over rechten stelt namelijk dat ontzegregels niet van toepassing zijn op commando's die bestanden lezen zonder ze bij naam te noemen, of op subprocessen die zelfstandig bestanden openen. Voor die gevallen verwijst de documentatie naar de sandbox van Claude Code, die de blokkade op besturingssysteemniveau afdwingt. Of u wordt gevraagd voordat een dergelijk commando wordt uitgevoerd, hangt af van de toestemmingsmodus van de sessie, en auto mode is nu de ingebouwde startmodus voor interactieve Claude Code-sessies. Een server die u niet in de gaten houdt, zal dus meer van dergelijke commando's zonder prompt 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 werkende wijziging voorkomt dat een run afdwaalt naar bestanden die het niet hoefde te openen, maar het blijft advies waar het model van kan worden afgepraat. Behandel 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_ed25519

De laatste regel moet falen met cat: /home/you/.ssh/id_ed25519: Permission denied. Als er een sleutel wordt getoond, heeft de gebruiker van de agent toegang tot het bestand. Het openen van een bestand op naam vereist x-rechten op elke bovenliggende map, niet alleen leesrechten. Een homedirectory met modus 711 die voor anderen niet inzichtelijk is, laat de sleutel alsnog door als het sleutelbestand zelf leesbaar is. chmod 700 ~ sluit het pad af en chmod 600 ~/.ssh/id_ed25519 herstelt de sleutel. 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, omdat één Claude Code-sessie tekst direct naar een andere kan sturen. Alles wat de eerste sessie bevat, kan via dat kanaal in één enkel bericht worden verstuurd.

Op een cloud-VPS is nog een extra grens aan te raden. De instance metadata service reageert op een vast link-local adres en verstrekt vaak rol-inloggegevens aan iedereen die erom vraagt. Het eindpunt controleert niet welke lokale gebruiker de aanvraag doet: op EC2 documenteert AWS dat IMDSv1-aanroepen geen token vereisen. Het advies van AWS om een gebruiker bij deze service weg te houden, is een lokale firewallregel op basis van het ID van die gebruiker.

sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

Voeg de regel bovenaan toe met -I in plaats van deze toe te voegen aan het einde met -A. Op een host die ufw draait met het standaardbeleid voor uitgaand verkeer (allow outgoing), accepteert de laatste OUTPUT-chain van ufw elke nieuwe TCP-verbinding. Een toegevoegde regel aan het einde wordt daardoor nooit bereikt. iptables-regels verdwijnen na een herstart tenzij u ze persistent maakt. Als u het IPv6-eindpunt van de metadata-service heeft ingeschakeld, [fd00:ec2::254], voeg dan dezelfde regel toe met ip6tables.

Controleer dit vanuit het perspectief van de agent. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ zou niets moeten tonen en met een non-zero status moeten afsluiten, omdat het pakket wordt geweigerd voordat het de server verlaat. Als er een lijst wordt getoond, wordt de regel niet bereikt.

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 verstuurt 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, beheerd door een andere gebruiker.

OneCLI is een open-source implementatie hiervan. Het is gelicentieerd onder Apache-2.0, met uitzondering van de ee/-mappen, die een afzonderlijke zakelijke licentie hebben waarvoor een abonnement vereist is voor productiegebruik. Het draait in containers naast de agent. Sinds oktober 2026 documenteert het project deze Docker Compose-configuratie, die drie gegenereerde geheimen leest uit een .env-bestand naast het compose-bestand:

git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --wait

Maak een back-up van dat .env-bestand, want het verlies van SECRET_ENCRYPTION_KEY maakt elk geheim dat ermee is opgeslagen onbruikbaar. Het dashboard luistert op poort 10254 en de gateway op 10255; beide zijn standaard gebonden aan 127.0.0.1. U slaat de echte inloggegevens eenmalig op en geeft elke agent vervolgens een tijdelijke waarde in plaats van de sleutel, samen met een eigen scoped toegangstoken, dat 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 zit in het feit dat de vraag "wat heeft deze agent gebruikt, en wanneer" een logboekquery 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 uitvoert, heeft u helemaal geen omgevingsvariabelen nodig. LoadCredential= plaatst het geheim in een privémap die alleen door de gebruiker van de service kan worden gelezen, blootgesteld als %d in het unit-bestand en als $CREDENTIALS_DIRECTORY binnen het proces. Die beperking geldt per gebruiker, niet per service; geef daarom elke agentservice een eigen gebruiker. Elk ander proces dat onder dezelfde gebruiker draait, kan de map openen, tenzij de unit ook gebruikmaakt van filesystem namespacing, zoals PrivateMounts=yes, wat de map verbergt voor andere services. De waarde verschijnt nooit in /proc/<pid>/environ, dus ps eww kan deze niet tonen, en de map verdwijnt zodra de service stopt.

Versleutel de inloggegevens eerst voor de machine. Deze commando's zijn afkomstig uit de systemd-documentatie en werken op systemd 250 of nieuwer, wat van toepassing is op Ubuntu 24.04 en Debian 13:

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_key

Het laatste commando print sk-example-value. Dit bewijst dat het versleutelde bestand op deze host kan worden ontsleuteld. 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-worker

Uw agentcode opent het bestand op $AGENT_KEY_FILE wanneer de waarde nodig is. Een bestand lezen is een tijdelijke handeling. Een omgevingsvariabele blijft gedurende de gehele levensduur van het proces bestaan, 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 900

Vijftien 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

Het is raadzaam om na elke wijziging in de configuratie van een agent drie controles uit te voeren. 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/user

De eerste controle hoort niets weer te geven. De tweede hoort ls: cannot open directory '/home/you/': Permission denied te tonen, al bewijst dit enkel dat de map niet kan worden weergegeven; de eerdere cat-test bewijst dat een bestand in die map niet kan worden gelezen. De derde controle toont met welke identiteit het netwerkpad van de agent zich presenteert; dit is de vraag waarvoor het gateway-patroon is ontworpen: een 401 betekent dat het verzoek GitHub heeft bereikt zonder een door GitHub geaccepteerd legitimatiebewijs (geen, ongeldig of verlopen), en een 200 betekent dat er een geldig bewijs wordt meegegeven, waardoor u moet weten om welk token het gaat. 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.

Was dit artikel nuttig?