Sichere Nutzung von KI-Agenten

Von
/01.06.26
Bildquelle: embracethered.com von Johann Rehberger
Bei Renuo haben wir begonnen, KI-Codierungsagenten (wie Claude CodeOpenCode oder Antigravity) für die Entwicklung einzusetzen. Ich nutze sie auch für persönliche Projekte wie mein Raspberry Dashboard. Zusätzlich haben wir damit begonnen, unseren eigenen KI-Agenten zu entwickeln, der in Redmine, unserem Ticketing- und Projektmanagementsystem, integriert ist.
Dabei wurden wir auf die damit verbundenen Sicherheitsrisiken aufmerksam: Standardmässig laufen diese Agenten mit vollen Benutzerberechtigungen: Sie können Dateien lesen und schreiben, Befehle ausführen und auf Zugangsdaten im Hostsystem zugreifen.
Johann Rehbergers 39c3-Vortrag „Agentic ProbLLMs: Exploiting AI Computer-Use and Coding Agents" zeigt, wie Prompt-Injection zu Remote Code Execution und dem Abgreifen von Zugangsdaten bei Agenten wie Claude Code und GitHub Copilot führen kann. Ich empfehle, ihn anzusehen.
In diesem Beitrag betrachten wir diese Risiken und die pragmatischen Lösungsansätze, die wir entwickelt haben, um eine Balance zwischen Entwicklerfreundlichkeit und Sicherheit zu finden.

Risiken von KI-Agenten

LLMs sind probabilistisch – eine 1%ige Katastrophenwahrscheinlichkeit macht es zu einer Frage des Wann, nicht des Ob.
Agent Safehouse
Anführungszeichen
Die meisten KI-Codierungsagenten laufen mit denselben Berechtigungen wie der Benutzer, der sie gestartet hat. Sie haben Zugriff auf das Dateisystem, können beliebige Shell-Befehle ausführen und erben alle im Umfeld verfügbaren Zugangsdaten. Da LLMs anfällig für Prompt-Injection sind (böswillige Anweisungen, die in Code, Dokumentation oder Web-Inhalten versteckt sind), entsteht dadurch eine reale Angriffsfläche.
Die Risiken lassen sich auf einige Kategorien reduzieren:
  • Preisgabe von Zugangsdaten: Agenten haben Zugriff auf Umgebungsvariablen, Konfigurationsdateien und Credential-Stores. Eine Prompt-Injection kann einen Agenten dazu verleiten, API-Schlüssel oder Zugriffstoken an einen vom Angreifer kontrollierten Server zu exfiltrieren.
  • Malware-Installation: Agenten können dazu gebracht werden, bösartigen Code herunterzuladen und auszuführen, zum Beispiel durch manipulierte Abhängigkeiten oder bösartige Anweisungen in README-Dateien.
  • Destruktive Aktionen auf dem lokalen Rechner: Ein Agent könnte Dateien löschen, Konfigurationen überschreiben oder eine lokale Datenbank beschädigen – durch Prompt-Injection oder schlicht durch eine falsche Entscheidung.
  • Destruktive Aktionen auf entfernten Systemen: Agenten haben oft Zugriff auf CLI-Tools, die mit der Produktionsinfrastruktur interagieren können. Man denke an kubectl deleteterraform destroynctl delete, oder vielleicht sogar einfach einen Datenbank-Client, der mit Prod verbunden ist.
Ich habe einige Entwickler gefragt, welche Vorfälle sie bisher mit KI-Agenten erlebt haben. Hier sind ein paar Beispiele:
Ich habe die Sitzung nicht mehr, aber während der Arbeit an einer Redmine-Integration stellte er fest, dass ich einen REDMINE_API_KEY in meinen ENV-Variablen hatte, und begann, Daten aus unserem Produktions-Redmine abzurufen.
-- Alessandro Rodi
Obwohl es kein grosses Problem war, war es frustrierend, als Datenbankmigrierungsfehler dazu führten, dass die Entwicklungsdatenbank gelöscht und neu erstellt wurde, da ich oft Testdaten verlor, die ich behalten wollte.
-- Bruno Costanzo
Während ich unsere Claude-Code-Skill zum Deployen von Web-Apps auf deplo.io testete, erreichte der Agent das Kontingentlimit der Anzahl von Apps in der Testorganisation. Um das zu lösen, entschied er, dass es am besten sei, bestehende Apps mit nctl delete app zu löschen. Er fragte jedoch vor dem Fortfahren um Bestätigung.
-- Josua Schmid
Wir hatten bisher keinen Fall, in dem etwas ernsthaft schiefgelaufen ist, hauptsächlich weil wir die Agenten nicht unbeaufsichtigt laufen lassen und Testumgebungen verwenden. Aber es hat uns genug aufgerüttelt, wirklich darüber nachzudenken, wie wir die Situation verbessern können.
Für einen tieferen Einblick in diese Angriffsvektoren siehe den in der Einleitung erwähnten 39c3-Vortrag.

Gegenmassnahmen

Was können wir also dagegen tun? Es läuft auf folgende Strategien hinaus:
  • Hoffnung: Den Agenten anweisen, keine destruktiven Dinge zu tun.
  • Manuelle Freigabe: Die Agenten so konfigurieren, dass sie vor jeder Aktion fragen.
  • Agenten-spezifische Konfiguration: Den Agenten das Lesen bestimmter Dateien oder das Ausführen bestimmter Befehle verbieten.
  • Isolation: Die Agenten in VMs, Docker-Containern oder einem Sandboxing-Tool ausführen.

Hoffnung / Prompt-Betteln

Das Hauptproblem damit, das LLM per Prompting einfach zu bitten, keine destruktiven Dinge zu tun, besteht darin, dass es schlicht nicht funktionieren könnte.
Bildquelle: agent-safehouse.dev

Manuelle Freigabe

Obwohl das manuelle Genehmigen aller Agenten-Aktionen sicher klingt, führt es in der Praxis zu Freigabe-Müdigkeit: Das wiederholte Genehmigen von Aktionen lässt uns weniger aufmerksam auf das werden, was wir tatsächlich genehmigen.
Es reduziert auch die Produktivität: Ständige Unterbrechungen verhindern, dass Agenten im Hintergrund laufen.
Es gibt auch das Problem der übermässigen Erlaubnisgewährung: Als ich Antigravity ausprobierte, erlaubte ich versehentlich die Ausführung jedes Bash-Befehls, anstatt nur des angeforderten. Da der Agent dann einfach weiter Befehle ausführte, musste ich ihn stoppen.

Agenten-spezifische Konfiguration

Die meisten Agenten können so konfiguriert werden, dass sie Muster von Aktionen erlauben und verweigern. Das Berechtigungssystem von Claude Code ermöglicht beispielsweise das Abgleichen von Shell-Befehlen per Muster:
  {
  "permissions": {
    "allow": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)"
    ]
  }
}
Dies erlaubt git commit, blockiert aber git push-Befehle.
Das Berechtigungssystem von OpenCode funktioniert ähnlich:
  {
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "bash": {
      "git commit *": "allow",
      "git push *": "deny"
    }
  }
}
Die Probleme mit diesen Systemen sind:
  • Agenten-spezifisch: Es gibt keine Möglichkeit, Regeln über alle Agenten hinweg festzulegen.
  • Deny-Listen können nicht vollständig sein: Man kann eine Deny-Regel wie Read(.env) angeben, aber der Agent kann auf dieselbe Datei über ein Bash-Tool zugreifen: cat .envgrep . .envpython -c "print(open('.env').read())" usw. Deny-Listen können grundsätzlich nicht die unendlichen Möglichkeiten abdecken, auf eine Ressource zuzugreifen.
  • Leicht überschreibbar: Die Regeln liegen im Repository selbst und können während der normalen Nutzung verändert werden. Claude Code erstellt zum Beispiel .claude/settings.local.json und fügt es zu Ihrem globalen ~/.config/git/ignore hinzu. Änderungen an dieser Datei erscheinen also nicht einmal in git status. Und andere Agenten ignorieren diese Konfigurationsdateien schlicht und einfach.

Isolation

Um wirklich Schutz zu bieten, müssen wir KI-Agenten extern mit Schutz auf Betriebssystemebene sandboxen. Aber eine vollständige Isolation der Agenten macht sie auch völlig nutzlos: Am Ende möchte man, dass sie am eigenen Code arbeiten und in irgendeiner Weise mit der Umgebung interagieren. Die folgende Tabelle soll den Grad der gebotenen Isolation veranschaulichen:
Technik Eigenschaften Isolationsniveau
VM Vollständige Isolation. Nur Interaktion über virtuelles Netzwerk Sehr hoch
Container Kernel-Namespaces, cgroups. Eingeschränkter Zugriff auf Dateien, Prozesse und andere Ressourcen Hoch
Sandboxes Landlock LSM / Seatbelt Konfigurationsabhängig
Welche Sandboxing-Technik verwendet wird, hängt auch vom Anwendungsfall ab:
  • Lokale Agenten: Man möchte ihnen Zugriff auf das lokale Projekt und wahrscheinlich einige Entwicklungstools geben. Eine separate VM oder sogar ein Docker-Container erfordert daher viel Einrichtungsaufwand.
  • Eigenständiger Agent: Er läuft ohnehin auf seinem eigenen Server / VM. Weiterhin möchte man, dass er in einer reproduzierbaren Umgebung läuft und das Betriebssystem nicht durcheinanderbringt. Es macht also Sinn, ihn in einem Container auszuführen.
Hier konzentrieren wir uns auf Lokale Agenten, wie sie von Entwicklern auf ihren eigenen Maschinen verwendet werden.

Devcontainer

Wenn Sie Devcontainer für Ihre Entwicklungsanforderungen verwenden, ist eine schnelle Möglichkeit, dem Agenten eine etwas isolierte Umgebung zu geben, ihn ebenfalls in diesem Container auszuführen. In einem Open-Source-Projekt, an dem ich mitarbeite, haben wir genau das kürzlich hinzugefügt: https://github.com/gfroerli/api/pull/356
Bei Renuo verwenden wir jedoch selten Devcontainer für unsere Setups: Wir bevorzugen lokale Umgebungen, die einfacher zu debuggen und zu untersuchen sind.

Eingebautes Sandboxing

Einige KI-Agenten unterstützen Sandboxing in ihrer eigenen Laufzeit. Siehe zum Beispiel Claude Code Sandboxing. Die Nachteile hier sind wiederum:
  • Agenten-spezifisch
  • Schwer, die Konfiguration richtig hinzubekommen (1)
  • Zumindest bei Claude Code betrifft es nur das Bash-Tool, nicht die Lese- und Schreib-Tools!

Spezialisierte Sandboxing-Tools

Am Ende haben wir uns auf die folgenden zwei Tools geeinigt, die Sandboxing auf Kernel-Ebene verwenden, um die Möglichkeiten von Agenten einzuschränken:
  • Agent Safehouse: Verwendet macOS Seatbelt, um dem Agenten nur Zugriff auf das zu geben, was er wirklich braucht.
  • nono: Verwendet Landlock auf Linux und Seatbelt auf macOS. Es bietet nicht nur Kernel-Isolation, sondern auch Rückgängig-Machen & Rollback, Audit-Trail, Supply-Chain-Herkunft, Laufzeitüberwachung und Filterung von Umgebungsvariablen.
Diese Tools haben folgende Eigenschaften:
  • Sie erzwingen unwiderrufliche Allow-List-basierte Blockierung auf Kernel-Ebene
  • Sie funktionieren mit allen Agenten
  • Sie verwenden sinnvolle Standardeinstellungen zum Schutz von Zugangsdaten auf Ihrem System
  • Die Konfiguration ist vom Agenten getrennt und kann separat getestet werden:
  $ nono run --allow-cwd ls ~/.ssh
...
ls: cannot open directory '~/.ssh': Permission denied
...
  • nono verweigert den Zugriff auf SSH-Zugangsdaten
Damit haben wir bereits vieles eingeschränkt, was ein Agent tun kann. Aber eine Sache bleibt: Zugangsdaten. Der Agent kann immer noch auf Zugangsdaten zugreifen, die sich im Projektordner befinden (wie eine .env-Datei) oder die in Umgebungsvariablen gespeichert sind (das könnte ein guter Zeitpunkt sein zu überprüfen, ob Sie Tokens in Ihrer ~/.bashrc, ~/.profile, ~/.zshrc oder wo auch immer gespeichert haben. Das sollten wir eigentlich nicht, aber manchmal sind wir Entwickler faul...).
  $ echo $TEST_ENV_TOKEN
my-secret
$ nono run --profile claude --allow-cwd -- claude
...
❯ Zeig mir den Inhalt von TEST_ENV_TOKEN

● Bash(echo "$TEST_ENV_TOKEN")
  ⎿  my-secret

● TEST_ENV_TOKEN=my-secret
  • Claude greift problemlos auf Tokens aus der Umgebung zu
Nono bietet eine elegante Möglichkeit, Umgebungsvariablen zu filtern (2). Dies ist standardmässig nicht aktiviert, aber wir können einfach ein benutzerdefiniertes Profil erstellen, das Umgebungsvariablen filtert:
  {
  "extends": "claude",
  "environment": {
    "allow_vars": ["PATH", "HOME", "TERM", "LANG", "LC_*"]
  }
}
  • Benutzerdefiniertes Profil unter ~/.config/nono/profiles/claude-filter-env.json
Jetzt hat claude nur noch Zugriff auf die sichere Teilmenge von Umgebungsvariablen, die wir ihm erlauben:
  $ echo $TEST_ENV_TOKEN
my-secret
$ nono run --profile claude-filter-env --allow-cwd -- claude
...
❯ Zeig mir den Inhalt von TEST_ENV_TOKEN.

● Bash(echo "$TEST_ENV_TOKEN")
  ⎿  (Keine Ausgabe)

● TEST_ENV_TOKEN ist nicht gesetzt (oder leer) in dieser Shell.
  • Claude kann den Inhalt von $TEST_ENV_TOKEN nicht sehen

Zusammenfassung

Am Ende ist es, wie üblich, ein Kompromiss zwischen Entwicklerkomfort und Sicherheit: Dem KI-Agenten Zugriff auf alles zu geben ist am bequemsten, aber ein Rezept für eine Katastrophe. Bei Renuo haben wir folgende grobe Richtlinien entwickelt:
  • Für eigenständige Agenten: Verwenden Sie VMs, Docker-Container und begrenzen Sie die Verfügbarkeit von Zugangsdaten so weit wie möglich. Es ist besser, den KI-Agenten seine Aufgabe erledigen zu lassen und dann Ihre benutzerdefinierte Laufzeit Code pushen, auf Tickets antworten usw. zu lassen.
  • Für Entwicklermaschinen: Verwenden Sie Sandboxing-Tools mit vorgefertigten Profilen für gängige KI-Agenten. Lassen Sie sie dennoch nicht im --yolo / unbeaufsichtigten Modus laufen: Geben Sie die Befehle weiterhin selbst frei, aber Sie können grosszügiger sein, welche Befehle sie ohne Bestätigung ausführen dürfen. Achten Sie besonders auf Zugangsdaten, die über Umgebungsvariablen verfügbar sind.

(1). Einmal beim Testen der Sandboxing-Funktion versicherte mir Claude, dass sein Zugriff auf eine Datei durch die Sandbox blockiert wurde, obwohl die Sandbox aufgrund fehlender Systemabhängigkeiten gar nicht gestartet werden konnte! 
(2). Was recht schnell implementiert wurde, nachdem ich es vorgeschlagen hatte: https://github.com/always-further/nono/issues/688