Houd API-sleutels buiten uw AI-agents
Een agent met uw API-sleutels kan ze in één toolaanroep lekken. Gebruik scoped tokens met korte geldigheid via een credential gateway, nooit echte sleutels.
Wat het betekent om geheimen buiten AI-agents te houden
Een AI-agent is een normaal Linux-proces dat opdrachten uitvoert. Elke omgevingsvariabele die dit proces bevat, is leesbaar voor de code die het uitvoert. Een API-sleutel in de omgeving van de agent is dus een sleutel die de agent naar elke bereikbare host kan verzenden. Geheimen buiten de agent houden betekent dat u de agent een verwijzing geeft in plaats van de sleutel: een token met beperkte geldigheidsduur en reikwijdte, of een tijdelijke aanduiding die iets anders bij de netwerkgrens vervangt door de echte waarde.
Dit gaat niet over een model dat vijandig wordt. Het mechanisme is eenvoudiger. Een agent leest een webpagina, een README of een opmerking bij een issue met instructies. Vervolgens volgt de agent deze instructies, omdat er voor een taalmodel geen verschil is tussen tekst die u hebt geschreven en tekst die het heeft opgehaald. Dit heet promptinjectie. Zodra dit gebeurt, wordt de schade exact door één factor begrensd: wat het proces kan lezen. Als u nog geen grens hebt ingesteld, behandelt een coding agent veilig uitvoeren op een server de isolatieniveaus waarop deze handleiding voortbouwt.
Het dreigingsmodel in eenvoudige bewoordingen
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 afdrukt, staat gelijk aan één HTTP-verzoek naar de server van een onbekende. Bekijk nu wat er op schijf in de buurt van de agent staat.
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 een shell heeft geen geavanceerde exploit nodig om die gegevens naar buiten te brengen. Vier normale methoden volstaan, en alle vier zien er in het logboek uit als normale werkzaamheden:
- Een uitgaand
curloffetchnaar een willekeurige host, met de waarde in een queryreeks. - Een
git commitengit pushnaar een repository waarnaar de agent kan schrijven. - Een installatiescript van een pakket, dat willekeurige code uitvoert als de gebruiker van de agent.
- Een DNS-lookup van een hostnaam die de waarde bevat, waardoor de gegevens ook naar buiten gaan wanneer HTTP-egress is geblokkeerd.
U kunt dit niet oplossen door alleen controles uit te voeren. Zorg ervoor dat er niets waardevols binnen bereik is.
Een geheim in de werkboom is een geheim in het contextvenster
Een agent leest bestanden. Een bestand .env in de repository waarin de agent werkt, wordt gelezen. Zodra het is gelezen, staat het in het contextvenster. Dit betekent dat het in het transcript staat, in elk logboek dat u bijhoudt en in alles wat de agent daarna schrijft.
Eerst, terwijl de sleutel in de werkboom stond:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingDaarna, nadat het bestand buiten bereik was geplaatst:
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 werkboom het niet meer bevat. Deny-regels in de eigen configuratie van de agent vormen een tweede beveiligingslaag, niet de eerste. Claude Code leest permissieregels uit .claude/settings.json in het project:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}Dit voorkomt dat de agent per ongeluk een bestand opent tijdens het verkennen. Het voorkomt niet dat een geïnjecteerde instructie base64 .env uitvoert, omdat dit een shellopdracht is en geen bestandslezing. Beschouw de configuratie als een beveiligingsmaatregel en de bestandssysteemrechten als de fysieke barrière. Dezelfde scheiding geldt in containers: env-bestanden en geheimen in Docker Compose behandelt de versie van dit probleem een laag lager.
Geef elke agent een eigen gebruiker zonder privileges
Als de agent onder uw account draait, krijgt deze toegang tot uw SSH-sleutels, uw cloudreferenties en uw shellgeschiedenis. Een afzonderlijke gebruiker kost één opdracht en voorkomt dat allemaal.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519De laatste regel moet mislukken met cat: /home/you/.ssh/id_ed25519: Permission denied. Als deze een sleutel afdrukt, zijn uw homedirectory en de inhoud ervan leesbaar voor de groep of alle gebruikers. chmod 700 ~ verhelpt dit. Voeg de agentgebruiker niet toe aan sudo en geef deze geen NOPASSWD-regel die ruimer is dan de ene opdracht die deze werkelijk nodig heeft. Gebruikers met minimale bevoegdheden op een VPS behandelt de details van groepen en sudoers.
Op een cloud-VPS is nog een grens belangrijk. De metadataservice van de instantie antwoordt op een vast link-local adres en verstrekt vaak rolreferenties aan elke aanvrager.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTControleer dit vanuit de agent. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ moet niets afdrukken en eindigen met een foutstatus, omdat het pakket wordt geweigerd voordat het de server verlaat.
Injecteer de referentie aan de grens
Het patroon dat dit daadwerkelijk oplost, is referentie-injectie. De agent bevat nooit een echte sleutel. De agent stuurt het verzoek via een lokale gateway. De gateway vervangt onderweg een plaatshouder door het echte geheim. Het geheim staat 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. Het draait als container naast de agent. In 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 poort 10255. U slaat de echte referentie eenmaal op. Daarna geeft u elke agent een plaatshouder in plaats van de sleutel, plus een eigen toegangs-token met beperkte scope. De agent verstuurt dit token in een Proxy-Authorization-header. De gateway koppelt het uitgaande verzoek aan de hand van host en pad, ontsleutelt de bijbehorende referentie en vervangt de plaatshouder. De omgeving van de agent bevat niets dat de moeite waard is om te stelen.
De waarde zit hier niet in de versleuteling. De waarde is dat de vraag "wat heeft deze agent gebruikt en wanneer" een logquery wordt. U leest één audittrail in plaats van te raden in welke van de zes omgevingen een kopie van de sleutel stond.
Geef het geheim aan het proces, niet aan de omgeving
Als u de agent onder systemd uitvoert, hebt u helemaal geen omgevingsvariabelen nodig. LoadCredential= plaatst het geheim in een privédirectory die alleen deze service kan lezen. In het unit-bestand is deze beschikbaar als %d en binnen het proces als $CREDENTIALS_DIRECTORY. De waarde verschijnt nooit in /proc/<pid>/environ. Daarom kan ps eww deze niet weergeven. De directory verdwijnt wanneer de service stopt.
Versleutel de referentie eerst voor de machine. Deze opdrachten komen uit de systemd-documentatie en werken met systemd 250 of hoger. Dat omvat 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_keyDe laatste opdracht geeft sk-example-value weer. Daarmee wordt bewezen dat het versleutelde bestand op deze host kan worden ontsleuteld. Verwijs er vervolgens vanuit de unit naar:
[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 agentcode opent het bestand op $AGENT_KEY_FILE wanneer de waarde nodig is. Het lezen van een bestand duurt slechts een moment. Een omgevingsvariabele blijft gedurende de volledige levensduur van het proces bestaan, in elk onderliggend proces dat het start.
Geef de voorkeur aan kort geldige tokens boven lang geldige sleutels
Een sleutel die nooit verloopt, blijft geldig wanneer deze maanden later in een logbestand of transcript opduikt. Als de service een sessietoken biedt, gebruikt u dat sessietoken en stelt u de kortst mogelijke geldigheidsduur in die de taak toelaat.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900Vijftien minuten is de minimale duur die AWS STS (security token service) accepteert. Dit is meestal voldoende voor één agenttaak. Geef de agentgebruiker voor GitHub een eigen gh-login met een fijnmazig token dat is beperkt tot de afzonderlijke repository waaraan de agent werkt. Zo retourneert gh auth token binnen die sessie iets waarmee niets anders kan worden benaderd. Beperk eerst op basis van resource en daarna op basis van tijd.
Eerst verifiëren, daarna blijven verifiëren
Na elke wijziging in de configuratie van een agent zijn drie controles nuttig. 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 mag helemaal niets afdrukken. De tweede moet ls: cannot open directory '/home/you/': Permission denied afdrukken. De derde geeft aan welke identiteit het netwerkpad van de agent presenteert. Dat is precies de vraag die het gatewaypatroon moet beantwoorden: een 401 betekent dat de agent geen eigen GitHub-referentie meestuurt, en een 200 betekent dat de agent er wel een meestuurt. U moet dan weten om welk token het gaat. Als u agents zonder toezicht uitvoert, behandelt AI-agentkosten op een VPS beheersen de budgetlimieten die bij deze toegangsbeperkingen horen.
FAQ
Kan ik er gewoon op vertrouwen dat het model mijn sleutels niet lekt?
Nee, omdat het model in dit dreigingsmodel niet de aanvaller is. De agent leest tekst van webpagina's, repositories en issue trackers. Die tekst kan instructies bevatten. Het model kan uw instructies niet betrouwbaar onderscheiden van tekst die het heeft opgehaald. Elke beveiligingsmaatregel die ervan uitgaat dat het model de juiste keuze maakt, faalt zodra een geïnjecteerde instructie overtuigend is. De beveiligingsmaatregel moet daarom in het besturingssysteem of het netwerk worden geïmplementeerd.
Zijn omgevingsvariabelen echt zo riskant voor agentgeheimen?
Ze zijn op één specifieke manier riskant: ze worden overgenomen. Elk child process dat de agent start, krijgt een kopie. Dat geldt ook voor een build script, een test runner en elke package install hook. De variabelen kunnen via /proc/<pid>/environ ook door dezelfde gebruiker worden gelezen. Alles wat de agent uitvoert, kan ze dus lezen zonder dat de agent ze expliciet doorgeeft. Een bestand dat pas op het moment van gebruik wordt gelezen, met LoadCredential= of via een gateway, beperkt de blootstelling tot dat moment.
Lost het opslaan van geheimen in een vault dit op zichzelf op?
Slechts gedeeltelijk. Een vault lost het opslagprobleem op. Het lost niet de laatste stap op, waarbij iets het geheim uit de vault haalt en het als omgevingsvariabele aan de agent doorgeeft. Daarmee bent u terug bij het oorspronkelijke probleem. Het gaat erom wie de substitutie uitvoert. Als de agent het geheim ophaalt, beschikt de agent over 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 dat achteraf niet vaststellen. Dat is juist een argument voor de gateway. Zonder gateway is uw bewijsmateriaal verspreid over de shell history, het transcript van de agent en logboeken van uitgaande verbindingen die u waarschijnlijk niet bijhoudt. Met een credential gateway is elk gebruik van een credential één regel met een agentidentiteit en een timestamp. Als u een lek vermoedt, roteert u eerst de key en onderzoekt u het incident daarna. Rotatie is goedkoop; zekerheid niet.
Wat moet ik vandaag minimaal doen?
Verplaats elk .env-bestand uit de directories waarin uw agents werken en maak één unprivileged user per agent aan. Deze twee wijzigingen kosten ongeveer tien minuten en sluiten de meest voorkomende route af. Daarbij leest een agent een credentialbestand dat niet naast de code had moeten staan. De gateway en de short-lived tokens zijn de volgende stap, niet de eerste. Hetzelfde uitgangspunt geldt voor elke agent runtime, inclusief een autonome agent veilig uitvoeren op een VPS.