SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor

AI-Agenten ohne geheime API-Schlüssel absichern

API-Schlüssel in Umgebungsvariablen können durch einen einzigen Tool-Aufruf abfließen. Nutzen Sie bereichsbeschränkte, kurzlebige Tokens hinter einem Credential-Gateway.

Was es bedeutet, Geheimnisse von AI-Agenten fernzuhalten

Ein AI-Agent ist ein normaler Linux-Prozess, der Befehle ausführt. Jede Umgebungsvariable, die dieser Prozess enthält, kann der von ihm ausgeführte Code lesen. Ein API-Schlüssel in der Umgebung des Agenten ist daher ein Schlüssel, den der Agent an jeden erreichbaren Host senden kann. Geheimnisse vom Agenten fernzuhalten bedeutet, ihm statt des Schlüssels eine Zugriffsmöglichkeit zu geben: ein kurzlebiges, bereichsbeschränktes Token oder einen Platzhalter, den etwas anderes an der Netzwerkgrenze durch den tatsächlichen Wert ersetzt.

Dabei geht es nicht darum, dass ein Modell feindselig wird. Der Mechanismus ist unspektakulärer. Ein Agent liest eine Webseite, eine README-Datei oder einen Issue-Kommentar mit Anweisungen und befolgt diese, weil es für ein Sprachmodell keinen Unterschied zwischen von Ihnen geschriebenem und abgerufenem Text gibt. Das ist Prompt Injection. Sobald dies geschieht, wird der Schaden genau durch eine einzige Sache begrenzt: durch das, was der Prozess lesen kann. Wenn Sie noch keine Grenze eingerichtet haben, beschreibt einen Coding-Agenten sicher auf einem Server auszuführen die Isolationsebenen, auf denen dieser Leitfaden aufbaut.

Das Bedrohungsmodell in einfachen Worten

Führen Sie dies als der Benutzer aus, unter dem Ihr Agent läuft.

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

Jede von diesem Befehl ausgegebene Zeile entspricht einer HTTP-Anfrage an den Server eines Fremden. Sehen Sie sich nun an, welche Daten auf dem Datenträger in der Nähe des Agents liegen.

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

Ein Agent mit einer Shell benötigt keinen ausgeklügelten Exploit, um diese Daten nach außen zu übertragen. Vier gewöhnliche Wege reichen aus. Alle vier sehen im Log wie normale Arbeit aus:

  • Ein ausgehendes curl oder fetch an einen beliebigen Host, wobei der Wert in einer Query-Zeichenfolge steht.
  • Ein git commit und git push an ein Repository, in das der Agent schreiben kann.
  • Ein Installationsskript eines Pakets, das beliebigen Code unter dem Benutzer des Agents ausführt.
  • Eine DNS-Abfrage für einen Hostnamen, der den Wert enthält. Dieser Weg funktioniert auch dann, wenn der ausgehende HTTP-Verkehr blockiert ist.

Sie können dieses Problem nicht durch eine nachträgliche Prüfung lösen. Stellen Sie stattdessen sicher, dass keine wertvollen Daten erreichbar sind.

Ein Secret im Arbeitsverzeichnis ist ein Secret im Kontextfenster

Ein Agent liest Dateien. Eine .env-Datei in dem Repository, in dem er arbeitet, wird gelesen. Sobald sie gelesen wurde, befindet sie sich im Kontextfenster. Das bedeutet, sie ist im Transkript enthalten, in jedem Protokoll, das Sie führen, und in allem, was der Agent als Nächstes schreibt.

Davor, mit dem Key im Arbeitsverzeichnis des Agenten:

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

Danach, mit der Datei außerhalb der Reichweite:

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

Der Benutzer des Agenten kann die Datei nicht mehr öffnen, da sie sich nicht mehr im Arbeitsverzeichnis befindet. Deny-Regeln in der Konfiguration des Agenten selbst sind eine zweite Ebene, nicht die erste. Claude Code liest Berechtigungsregeln aus .claude/settings.json im Projekt:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
  }
}

Dies verhindert den versehentlichen Fehler, dass der Agent beim Erkunden eine Datei öffnet. Es geht über die Dateitools hinaus: Claude Code prüft diese Regeln auch gegen Dateibefehle, die er in Bash erkennt, wie cat und head, sowie gegen Ziele von Umleitungen wie < .env. Es verhindert nicht, dass eine injizierte Anweisung grep -r KEY . innerhalb des Projektverzeichnisses ausführt oder ein kurzes Python-Skript, das .env selbst öffnet, da die Berechtigungsdokumentation besagt, dass Deny-Regeln nicht für Befehle gelten, die Dateien lesen, ohne sie zu benennen, oder für Subprozesse, die Dateien eigenständig öffnen. Für diese verweist die Dokumentation auf die Sandbox von Claude Code, die die Blockade auf Betriebssystemebene durchsetzt. Ob Sie gefragt werden, bevor ein solcher Befehl ausgeführt wird, hängt vom Berechtigungsmodus der Sitzung ab, und der Auto-Modus ist jetzt der integrierte Startmodus für interaktive Claude Code-Sitzungen, sodass ein Server, den Sie nicht überwachen, mehr dieser Befehle ungefragt ausführen wird. Die gleiche Einschränkung gilt für alles, was die Gewohnheiten des Agenten formt, anstatt seine Berechtigungen: Eine Fähigkeit, die den Agenten auf die kleinste funktionierende Änderung beschränkt, verhindert, dass ein Durchlauf in Dateien abdriftet, die er nicht hätte öffnen dürfen, aber es bleibt ein Rat, von dem das Modell abgebracht werden kann. Betrachten Sie die Konfiguration als Leitplanke und die Dateisystemberechtigung als Mauer. Die gleiche Trennung gilt innerhalb von Containern: Env-Dateien und Secrets in Docker Compose behandelt die Version dieses Problems eine Ebene tiefer.

Geben Sie jedem Agenten einen eigenen Benutzer ohne Privilegien

Wenn der Agent unter Ihrem Benutzerkonto läuft, übernimmt er Ihre SSH-Schlüssel, Ihre Cloud-Anmeldedaten und Ihre Shell-Historie. Ein separater Benutzer erfordert einen einzigen Befehl und entfernt all das.

sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519

Die letzte Zeile muss mit cat: /home/you/.ssh/id_ed25519: Permission denied fehlschlagen. Wenn stattdessen ein Schlüssel ausgegeben wird, kann der Benutzer des Agenten auf die Datei zugreifen. Zum Öffnen einer Datei anhand ihres Namens ist für jedes übergeordnete Verzeichnis die Suchberechtigung (x) erforderlich, nicht die Leseberechtigung. Deshalb kann ein Home-Verzeichnis mit dem Modus 711, das niemand sonst auflisten kann, den Zugriff auf den Schlüssel trotzdem ermöglichen, wenn die Schlüsseldatei selbst lesbar ist. chmod 700 ~ sperrt den Pfad, und chmod 600 ~/.ssh/id_ed25519 korrigiert die Berechtigungen des Schlüssels. Fügen Sie den Agentenbenutzer nicht zu sudo hinzu, und geben Sie ihm keine NOPASSWD-Regel, die über den einen Befehl hinausgeht, den er tatsächlich benötigt. Benutzer mit geringsten Privilegien auf einem VPS beschreibt die Details zu Gruppen und sudoers. Behalten Sie diese Trennung im Blick, sobald Sie mehr als eine Sitzung auf dem Server ausführen, denn eine Claude Code-Sitzung kann Text direkt an eine andere senden, und alles, was die erste Sitzung enthält, kann in einer einzigen Nachricht diesen Kanal passieren.

Auf einem Cloud-VPS sollte außerdem eine weitere Grenze eingerichtet werden. Der Instanz-Metadatendienst antwortet unter einer festen Link-Local-Adresse und gibt häufig Rollen-Anmeldedaten an jeden aus, der sie anfordert. Der Endpunkt prüft nicht, welcher lokale Benutzer den Aufruf ausführt: Für EC2 dokumentiert AWS, dass IMDSv1-Aufrufe kein Token benötigen. AWS empfiehlt selbst eine lokale Firewall-Regel, die anhand der Benutzer-ID verhindert, dass ein bestimmter Benutzer auf den Dienst zugreift.

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

Fügen Sie die Regel mit -I am Anfang ein, statt sie mit -A anzuhängen. Auf einem Host mit ufw und der standardmäßigen Richtlinie zum Zulassen ausgehender Verbindungen akzeptiert die letzte Regel der OUTPUT-Kette von ufw jede neue TCP-Verbindung. Eine angehängte Regel wird daher nie erreicht. iptables-Regeln gehen außerdem beim Neustart verloren, wenn Sie sie nicht dauerhaft speichern. Wenn Sie den IPv6-Endpunkt des Metadatendienstes aktiviert haben, [fd00:ec2::254], fügen Sie dieselbe Regel mit ip6tables hinzu.

Prüfen Sie die Konfiguration aus Sicht des Agenten. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ darf nichts ausgeben und muss mit einem Fehlercode ungleich null beendet werden, weil das Paket zurückgewiesen wird, bevor es den Server verlässt. Wenn eine Auflistung ausgegeben wird, wird die Regel nicht erreicht.

Anmeldedaten an der Grenze injizieren

Das Muster, das dieses Problem tatsächlich löst, ist die Injektion von Anmeldedaten. Der Agent besitzt zu keinem Zeitpunkt einen echten Schlüssel. Er sendet seine Anfrage über ein lokales Gateway, und das Gateway ersetzt auf dem ausgehenden Weg einen Platzhalter durch das echte Geheimnis. Das Geheimnis befindet sich im Speicher des Gateways, in einem anderen Prozess, der einem anderen Benutzer gehört.

OneCLI ist eine Open-Source-Implementierung dieses Konzepts. Sie steht unter der Apache-2.0-Lizenz, mit Ausnahme der ee/-Verzeichnisse, die eine separate Unternehmenslizenz tragen, welche für den produktiven Einsatz ein Abonnement erfordert. OneCLI läuft in Containern neben dem Agenten. Seit Oktober 2026 dokumentiert das Projekt dieses Docker-Compose-Setup, das drei generierte Geheimnisse aus einer .env-Datei neben der Compose-Datei liest:

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

Erstellen Sie ein Backup dieser .env-Datei, denn der Verlust von SECRET_ENCRYPTION_KEY macht jedes darin gespeicherte Geheimnis unbrauchbar. Das Dashboard lauscht standardmäßig auf Port 10254 und das Gateway auf Port 10255, beide sind an 127.0.0.1 gebunden. Sie speichern die echten Anmeldedaten einmalig und geben jedem Agenten anstelle des Schlüssels einen Platzhalterwert sowie ein eigenes, begrenztes Zugriffstoken, das er in einem Proxy-Authorization-Header sendet. Das Gateway gleicht die ausgehende Anfrage anhand von Host und Pfad ab, entschlüsselt das passende Geheimnis und ersetzt den Platzhalter. Die Umgebung des Agenten enthält somit nichts, was sich zu stehlen lohnt.

Der Mehrwert liegt hier nicht in der Verschlüsselung. Er liegt darin, dass die Frage „Was hat dieser Agent wann verwendet?“ zu einer Log-Abfrage wird. Sie lesen einen einzigen Audit-Trail, anstatt zu raten, welche der sechs Umgebungen eine Kopie des Schlüssels enthielt.

Übergeben Sie das Geheimnis an den Prozess, nicht an die Umgebung

Wenn Sie den Agenten unter systemd ausführen, benötigen Sie überhaupt keine Umgebungsvariablen. LoadCredential= legt das Geheimnis in einem privaten Verzeichnis ab, das nur der Benutzer des Dienstes lesen kann; es wird in der Unit-Datei als %d und innerhalb des Prozesses als $CREDENTIALS_DIRECTORY bereitgestellt. Diese Einschränkung gilt pro Benutzer, nicht pro Dienst. Weisen Sie daher jedem Agenten-Dienst einen eigenen Benutzer zu: Jeder andere Prozess, der unter demselben Benutzer läuft, kann das Verzeichnis öffnen, es sei denn, die Unit verwendet zusätzlich Filesystem-Namespacing, wie etwa PrivateMounts=yes, wodurch es vor anderen Diensten verborgen bleibt. Der Wert erscheint niemals in /proc/<pid>/environ, daher kann ps eww ihn nicht anzeigen, und das Verzeichnis verschwindet, sobald der Dienst stoppt.

Verschlüsseln Sie die Anmeldedaten zuerst für die Maschine. Diese Befehle stammen aus der systemd-Dokumentation und funktionieren ab systemd 250, was Ubuntu 24.04 und Debian 13 einschließt:

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

Der letzte Befehl gibt sk-example-value aus. Dies belegt, dass die verschlüsselte Datei auf diesem Host entschlüsselt werden kann. Referenzieren Sie sie anschließend in der 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

Ihr Agenten-Code öffnet die Datei unter $AGENT_KEY_FILE, wenn er den Wert benötigt. Ein Dateilesevorgang ist ein flüchtiger Vorgang. Eine Umgebungsvariable hingegen bleibt während der gesamten Laufzeit des Prozesses bestehen und wird an jeden Kindprozess vererbt, den er startet.

Kurzlebige Tokens sind langlebigen Schlüsseln vorzuziehen

Ein Schlüssel ohne Ablaufdatum bleibt gültig, wenn er Monate später in einem Log oder Transkript auftaucht. Wenn der Dienst ein Session-Token anbietet, verwenden Sie dieses und setzen Sie die kürzeste für die Aufgabe ausreichende Gültigkeitsdauer.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

Fünfzehn Minuten ist die kürzeste von AWS STS (security token service) akzeptierte Dauer und reicht normalerweise für eine einzelne Agent-Aufgabe aus. Geben Sie dem Agent-Benutzer für GitHub einen eigenen gh-Login mit einem fein granularen Token, das auf das einzelne Repository beschränkt ist, in dem er arbeitet. Dadurch liefert gh auth token innerhalb dieser Sitzung etwas, das auf nichts anderes zugreifen kann. Beschränken Sie zuerst nach Ressource und anschließend nach Zeit.

Überprüfen und kontinuierlich kontrollieren

Nach jeder Änderung an der Einrichtung eines Agents sollten drei Prüfungen durchgeführt werden. Führen Sie diese als der Benutzer des Agents aus, nicht als Sie selbst.

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

Die erste Prüfung sollte keinerlei Ausgabe erzeugen. Die zweite sollte ls: cannot open directory '/home/you/': Permission denied ausgeben, wobei dies lediglich zeigt, dass das Verzeichnis nicht aufgelistet werden kann; der vorherige cat-Test belegt, dass eine Datei darin nicht gelesen werden kann. Die dritte Prüfung zeigt Ihnen, welche Identität der Netzwerkpfad des Agents präsentiert. Dies ist die Frage, die das Gateway-Muster beantworten soll: Ein 401 bedeutet, dass die Anfrage GitHub ohne ein von GitHub akzeptiertes Credential erreicht hat – entweder gar keines, ein ungültiges oder ein abgelaufenes. Ein 200 bedeutet, dass ein gültiges Credential übertragen wird; Sie sollten also wissen, um welches Token es sich handelt. Wenn Sie Agents unbeaufsichtigt betreiben, behandelt Kontrolle der Kosten für KI-Agents auf einem VPS die Budgetlimits, die mit diesen Zugriffsbeschränkungen einhergehen.

FAQ

Kann ich dem Modell einfach vertrauen, dass es meine Schlüssel nicht weitergibt?

Nein, denn in diesem Bedrohungsmodell ist das Modell nicht der Angreifer. Der Agent liest Text aus Webseiten, Repositories und Issue-Trackern. Dieser Text kann Anweisungen enthalten. Das Modell kann Ihre Anweisungen nicht zuverlässig von abgerufenen Texten unterscheiden. Jede Kontrolle, die davon abhängt, dass das Modell die richtige Entscheidung trifft, versagt, sobald eine eingeschleuste Anweisung überzeugend formuliert ist. Die Kontrolle muss daher stattdessen im Betriebssystem oder im Netzwerk umgesetzt werden.

Sind Umgebungsvariablen für Agent-Secrets wirklich so problematisch?

Sie sind in einer bestimmten Hinsicht problematisch: Sie werden vererbt. Jeder vom Agenten gestartete Kindprozess erhält eine Kopie. Dazu gehören Build-Skripte, Test-Runner und Hooks für die Paketinstallation. Die Variablen sind über /proc/<pid>/environ außerdem für denselben Benutzer lesbar. Alles, was der Agent ausführt, kann sie daher lesen, ohne dass der Agent sie weitergeben muss. Eine Datei, die erst zum Verwendungszeitpunkt gelesen wird, begrenzt die Offenlegung auf diesen Zeitpunkt. Das ist mit LoadCredential= oder einem Gateway möglich.

Ist das Problem damit gelöst, Secrets in einem Vault abzulegen?

Nur teilweise. Ein Vault löst das Speicherproblem. Wenn Sie diesen Vault selbst hosten, benötigt er eine eigene Härtung, denn ein Vaultwarden-Server wird üblicherweise über sein Admin-Token oder seine Backup-Datei kompromittiert und nicht über die verschlüsselten Einträge, die er speichert. Der letzte Schritt bleibt davon unberührt: Etwas ruft das Secret aus dem Vault ab und übergibt es dem Agenten als Umgebungsvariable. Damit befinden Sie sich wieder am Ausgangspunkt. Entscheidend ist, wer die Ersetzung vornimmt. Ruft der Agent das Secret ab, verfügt der Agent über das Secret. Führt ein Gateway oder das Init-System die Ersetzung außerhalb des Prozesses des Agenten durch, hält der Agent das Secret nie selbst.

Wie stelle ich fest, ob ein Agent bereits etwas weitergegeben hat?

Nachträglich können Sie das meistens nicht feststellen. Das spricht für ein Gateway. Ohne Gateway sind Ihre Belege auf die Shell-History, das Transkript des Agenten und ausgehende Verbindungsprotokolle verteilt. Letztere werden Sie wahrscheinlich nicht erfassen. Mit einem Credential-Gateway ist jede Verwendung eines Credentials eine einzelne Zeile mit der Identität des Agenten und einem Zeitstempel. Wenn Sie eine Weitergabe vermuten, rotieren Sie den Schlüssel zuerst und untersuchen Sie den Vorfall danach. Eine Rotation ist günstig, Gewissheit nicht.

Was sollte ich heute mindestens tun?

Verschieben Sie jede .env-Datei aus den Verzeichnissen, in denen Ihre Agenten arbeiten, und erstellen Sie für jeden Agenten einen eigenen unprivilegierten Benutzer. Diese beiden Änderungen dauern etwa zehn Minuten. Sie schließen den häufigsten Angriffsweg: Ein Agent liest eine Credential-Datei, die keinen Grund hatte, neben dem Code zu liegen. Das Gateway und kurzlebige Tokens sind der nächste Schritt, nicht der erste. Dieser Ausgangspunkt gilt für jede Agent-Laufzeitumgebung, einschließlich dem sicheren Betrieb eines autonomen Agenten auf einem VPS.

War dieser Artikel hilfreich?