Skip to main content Suchen

Vertraut KI-Programmieragenten nicht blind

KI-Coding-Agents

Ich habe meinen Coding-Agent in einem Repository laufen lassen. Eine einfache Aufgabe: herausfinden, wie wir eine Menge Bash-basierter Ops-Logik nehmen und sie in eine stärker Kubernetes-nativen Form überführen können. Langweilige, repetitive Arbeit – genau die Art von Aufgabe, die man gerne delegiert. Also habe ich ihn laufen lassen und zwischendurch immer wieder reingeschaut.

Dann bemerkte ich, dass kubectl-Befehle ausgeführt wurden – gegen einen Produktions-Cluster. Es ist zwar nichts passiert, aber es fühlte sich trotzdem falsch an, denn mein Agent hätte theoretisch eigenständig Cluster-Secrets exfiltrieren können. Er hatte Zugriff.

Das war meine Schuld. Der Agent hat nicht falsch funktioniert. Er ist nicht „ausgebrochen“. Er hat einfach versucht, so viel Kontext wie möglich für seine Aufgabe zu sammeln. Dafür hat er sich einen echten Cluster angeschaut, um zu verstehen, womit er arbeitet. Absolut nachvollziehbar. Das Problem war, dass ich die Schlüssel auf der Theke liegen gelassen hatte – und wie ein neugieriges Kleinkind hatte er die Hände, um sie zu greifen.

Wenn man einen KI-Coding-Agenten aus dem Terminal startet, erbt er die gesamte Umgebung: SSH-Keys, Cloud-Credentials, kubeconfig, alle Projektverzeichnisse auf der Festplatte. Nicht weil man das bewusst entschieden hat, sondern weil ein Prozess, der unter dem eigenen User läuft, genau so funktioniert. Das Betriebssystem unterscheidet nicht zwischen dir und etwas, das als du läuft. Es kennt kein „dieser Prozess ist ein Agent und sollte anders behandelt werden“. Gleicher User, gleiches Vertrauen.

Das ist keine Sicherheitslücke. Kein Bug im Agenten. Es ist einfach das, was passiert, wenn man Dinge in Reichweite von etwas lässt, das nicht weiß, dass es sie nicht anfassen sollte. Das Kleinkind versteht nicht den Unterschied zwischen Spielzeug und Reisepass. Es nimmt einfach, was da ist.

Explizit vor implizit

Vertrauen ist in der Systemtechnik nichts, das standardmäßig einfach entsteht. Es ist etwas, das man bewusst festlegt. Man entscheidet, welche Systeme miteinander kommunizieren dürfen. Man entscheidet, welche Daten wohin fließen. Man definiert, welche Grenzen existieren und aus welchem Grund. Diese Entscheidungen sind in Konfigurationen, Policies und Audit-Logs festgehalten – also an Orten, an denen man sie lesen, überprüfen und ändern kann.

Das Zugriffsmodell für Agenten sollte genauso funktionieren. Nicht: „Der Agent erbt alles, was er erreichen kann, und wir hoffen, dass nichts schiefgeht.“ Sondern: „Wir haben explizit und schriftlich festgelegt, auf welche Ressourcen der Agent Zugriff hat – und alles andere ist absichtlich nicht erreichbar.“

Das ist das Prinzip der minimalen Rechtevergabe (Least Privilege), seit Jahrzehnten ein Grundpfeiler der Sicherheit. Gleichzeitig ist es auch das Prinzip von explizit statt implizit, angewendet auf Vertrauen in agentische Systeme: Der Standard ist kein Zugriff. Zugriff wird durch eine bewusste, dokumentierte und überprüfbare Entscheidung vergeben. Die Welt des Agenten ist vor seinem Start auf dem Papier definiert.

Genau diese Explizitheit ist der eigentliche Mehrwert. Nicht nur, weil sie entschlossene Angreifer erschwert (auch wenn sie das tut), sondern weil sie eine echte Frage erzwingt: Was braucht dieser Agent tatsächlich? Nicht, was er haben könnte. Was braucht er wirklich? Wenn man diese Frage beantwortet und die Differenz zum Ausgangszustand sieht, erkennt man, was das eigene System tatsächlich tut.

Grenzen real machen

Unter Linux ist die eigentliche „Sperre“, die wirklich greift,  Landlock LSM – ein Kernel-Sicherheitsmodul, das unprivilegierten Prozessen erlaubt, sich selbst irreversible Zugriffsbeschränkungen aufzuerlegen. Kein Root erforderlich. Kein Daemon. Die Regeln werden vom Kernel erzwungen, bevor der Prozess überhaupt startet, und sobald sie angewendet sind, gibt es keine Möglichkeit mehr, sie zu erweitern oder zu entfernen. Jeder Kindprozess erbt diese Einschränkungen vollständig bis ganz nach unten. Man kann sich nicht per Prompt daraus „herausarbeiten“.

Tools wie nono nutzen Landlock, um genau diese explizite Entscheidung durchzusetzen. Der Agent kommuniziert nur über einen Proxy ins Netzwerk, der vom Supervisor kontrolliert wird. Auf das Dateisystem greift er nur in die Pfade zu, die die Policy ausdrücklich erlaubt. Das Audit-Log – also was der Agent getan hat, was er versucht hat zu erreichen und was blockiert wurde – wird vom Supervisor geschrieben. Der Agent kann seinen eigenen Verlauf nicht verändern. Er kann den kubectl-Aufruf nicht auslassen. Er kann nicht nach sich selbst aufräumen.

Die Sandbox macht die explizite Policy praktisch unverletzbar. Der Agent kann nicht versehentlich auf etwas zugreifen, das ihm nicht bewusst erlaubt wurde. Noch wichtiger: Wenn er es versucht, entsteht ein Signal. Die Grenze ist nicht nur ein Sicherheitsnetz, sondern ein Beobachtungsinstrument.

Entscheiden, was in Reichweite ist

Um eine explizite Entscheidung zu treffen, muss man wissen, was der Agent braucht.

nono learn (oder vergleichbare Tools) verfolgt die tatsächlichen Zugriffsmuster: gelesene Pfade, beschriebene Pfade, angesprochene Hosts. Das Ergebnis ist ein JSON-Snippet, das ihr direkt in eine Policy umwandeln könnt. Ihr rät nicht, was der Agent vielleicht tun könnte. Ihr beobachtet und dokumentiert, was er tatsächlich tut.

nono profile diff default my-agent

In diesem diff seht ihr das Zugriffsmodell zum ersten Mal deutlich. Alles, was der Agent braucht, wird aufgezählt, bevor ihr ihm die Schlüssel gebt. Nicht die Schlüssel für das ganze Haus. Es wird ein spezifischer Schlüssel für die spezifischen Räume benötigt.

Das Richtliniendokument wird zur expliziten Aufzeichnung: dieser Agent, diese Aufgabe, diese Zugangsgrenzen. Ihr könnt es lesen, überprüfen und jemand anderem zum Lesen geben. Ihr könnt es an einem Ort aufbewahren, wo ihr es einsehen, versionieren und prüfen könnt.

Wenn ein Agent legitimerweise Zugriff auf etwas benötigt, das standardmäßig blockiert ist, gewährt ihr ihn explizit:

{
"filesystem": {
"allow": ["$HOME/.docker"]
},
"policy": {
"override_deny": ["$HOME/.docker"]
}
}

Beide Felder sind vom Entwurf her erforderlich. Wenn ihr eine Verweigerung ohne eine explizite Erlaubnis entfernt, erhaltet ihr eine Richtlinie, die wie eine Sperre aussieht, es aber nicht ist. Der zweistufige Ansatz ist ein Sicherheitsmerkmal. Jede Ausnahme wird am Ende bewusst, aufgeschrieben und im Diff sichtbar.

Was ich gesehen habe

Nach dem Vorfall mit kubectl habe ich angefangen, Sitzungen mit einer Sandbox durchzuführen. Derselbe Agent, dieselben Repos, andere Aufgaben. Der Unterschied war, dass ich jetzt sehen konnte, was an der Grenze passierte.

Die Menge der blockierten Versuche hat mich überrascht. Nicht kubectl gegen die Produktion. Das ist ziemlich offensichtlich. Es waren die leiseren Sachen. Bash-Befehle und Leseaufrufe, die auf Blöcke stießen, während etwas, das von außen betrachtet, wie völlige Routinearbeit aussah. Die Auflösung von Abhängigkeiten berührte Pfade, die keinen Grund hatten, in der Nähe zu sein. Dateiscans, die in Richtung Anmeldedateien drifteten. Nichts Dramatisches. Nichts, was in der Ausgabe aufgetaucht wäre oder als ungewöhnliches Verhalten aufgefallen wäre. Nur ein stetiger Hintergrund von Umgebungsreichweiten, die immer da waren, in jeder Sitzung, unsichtbar, weil nie etwas im Weg gewesen war.

Nicht, dass der Agent etwas falsch gemacht hätte. Vielmehr hatte ich keine Ahnung, wonach eine normale Sitzung tatsächlich griff, weil ich nie etwas hatte, das mir das zeigte. Die Sandbox hat das Verhalten des Agenten nicht verändert. Er machte nur die Gefährdung zum ersten Mal sichtbar.

Die stillen Risiken sind es wert, sich Sorgen zu machen. Ein Lesevorgang gegen ~/.aws/credentials, der wie ein Dateiscan aussieht. Ein Schritt zur Auflösung von Abhängigkeiten, der eure kubeconfig berührt. Ein Bash-Befehl, der eine Umgebungsvariable überprüft. Nichts davon taucht in der Ausgabe des Agenten auf. Keiner fühlt sich wie ein Vorfall an. Aber sie sind potenzielle Exfiltrationsziele für spätere Prompt-Injektionen, sobald sie sich im Kontextfenster des Agenten befinden.

Die Grenze ist das Signal. Nicht "es ist etwas Gefährliches passiert", sondern "etwas ist weiter gegangen, als die Richtlinie erlaubt". Ohne die Begrenzung erhaltet ihr dieses Signal nicht. Ihr erhaltet lediglich einen Prozess, der alles getan hat, was er erreichen konnte, und keine Aufzeichnung darüber, wohin er gegangen ist.

Definiert, welche Schlüssel in die Reichweite gehören

Der Vorfall mit kubectl war offensichtlich, weil ich zufällig dabei war. Das meiste ist nicht offensichtlich. Das meiste davon sind routinemäßige Befehle, die still und leise Dinge lesen, für die sie keinen Grund haben, in Sitzungen, in denen man nicht genau aufpasst, und vor dem Hintergrund eines Zugriffs, der nie eine Entscheidung war. Es war einfach das, was es immer bedeutet hat, als Ihr Benutzer zu arbeiten.

Aber das muss nicht so bleiben. Das Prinzip ist klar: explizit vor implizit. Der Zugriff des Agenten sollte eine bewusste Entscheidung sein, schriftlich festgehalten, an der Grenze durchgesetzt und in den Aufzeichnungen nachprüfbar.

Wenn ihr das nächste Mal einen KI-Agenten von eurem Terminal aus startet, solltet ihr diese Entscheidung explizit machen. Entscheidet, was er tatsächlich braucht. Schreibt es auf. Setzet sie mit einer Sandbox durch. Beobachtet dann, was blockiert wird. Die Sandbox wird nichts daran ändern, was der Agent zu tun versucht. Es wird nur das erste Mal sein, dass ihr sehen könnt, was immer in Reichweite war – und das erste Mal, dass ihr eine echte Wahl habt, welche Schlüssel ihr auf dem Tresen liegen lassen wollt.

brew install nono
nono learn --profile default -- your-agent-command
nono profile diff default your-new-profile
# Bearbeiten Sie die Richtlinie nach Bedarf
nono run --profile your-new-profile -- your-agent-command

Die Schlüssel gehören euch. Die Entscheidung, sie zu vergeben, sollte es auch sein.

 

Veröffentlicht:

Software developmentSecurityAI