Kurz nach zwei Uhr morgens schickt ein Coding-Agent eine Anfrage an seinen Modellserver, und die Antwort enthält einen Tool-Aufruf, der die Umgebungsvariablen des laufenden Containers auslesen will. Der Harness – also das Programm, das den Agenten steuert – führt ihn aus, weil es keinen Anlass sieht, daran zu zweifeln. Am nächsten Morgen sitzt eine Entwicklerin vor dem Protokoll und findet in Zeile 4812 einen curl-Befehl, den niemand geschrieben hat, und eine Datei, die sie nicht angelegt hat. An diesem Punkt hört ein praktisches Werkzeug auf, nur praktisch zu sein. Das Projekt safeagents setzt dort an; es ist unter dem Dach von Hugging Face auf GitHub veröffentlicht.
Ein Pförtner zwischen Agent und Modellserver
safeagents ist kein neuer Agent und kein neues Framework, sondern ein schlanker lokaler Proxy zwischen Harness und Modellserver. Man startet ihn im Hintergrund und trägt seine Adresse als Base-URL in den Agenten ein. Das funktioniert mit jedem Harness, der sich so umbiegen lässt – die Dokumentation nennt unter anderem Claude Code, Pi, Hermes und OpenCode, insgesamt gibt es Einrichtungsanleitungen für dreizehn Coding-Agenten. Auch das Protokoll ist nicht festgelegt: Der Proxy versteht OpenAI Chat Completions und Anthropic Messages, jeweils mit und ohne Streaming.
Am besten stellt man sich eine Zollstation vor. Der Modellserver liegt außerhalb, der Harness ist die Werkstatt im Inneren, und jeder Tool-Aufruf ist ein Paket, das von draußen hereinkommt. safeagents öffnet jedes Paket, bevor es die Werkstatt erreicht. Was durchgeht, wird unverändert weitergereicht. Was nicht durchgeht, verschwindet nicht stillschweigend, sondern wird durch einen kurzen Textbescheid ersetzt – der Agent erfährt, dass sein Aufruf nicht ausgeführt wurde, statt auf eine Antwort zu warten, die nie kommt. Für den Harness ist die Sache damit erledigt: Er sieht einen vollständigen Aufruf oder gar keinen.
Die Installation ist unspektakulär. Ein Skript für macOS, Linux und WSL beziehungsweise eines für PowerShell installiert bei Bedarf uv, legt safeagents als uv-Tool ab und sorgt dafür, dass es im PATH landet. Eine bestimmte Version oder einen Commit fixiert man vorher über SAFEAGENTS_VERSION, und ein erneuter Lauf des Skripts ist zugleich das Update. Wer zuerst nur zusehen will, startet mit --log-only: Dann meldet der Proxy, was er blockieren würde, ohne einzugreifen.
Drei Prüfstufen: vom deklarierten Werkzeug bis zum Richtermodell
Jeder Tool-Aufruf durchläuft drei Stufen, und die erste kommt ohne jedes Wissen über den Inhalt aus. Blockiert wird ein Aufruf schon dann, wenn er ein Werkzeug benutzt, das die Anfrage nie deklariert hat – ein Agent, der plötzlich ein Werkzeug aufruft, das im Request gar nicht angeboten wurde, ist per Definition auffällig. Ebenso wird ein Aufruf abgelehnt, dessen Argumente einen Host enthalten, also eine URL oder eine nackte IP-Adresse, die an keiner Stelle der Konversation auftaucht. Dabei zählen die früheren Äußerungen des Modells selbst nicht als Beleg: Ein Modell, das sich einen Host selbst ausgedacht hat, kann ihn im nächsten Schritt nicht legitimieren.
Die zweite Stufe arbeitet inhaltlich. Die Konversation wird in eine ATIF-Trace überführt, und die Detektoren von atif-scan laufen über den anstehenden Aufruf. Erkannt werden unter anderem das Lesen von /proc/1/environ, curl --noproxy, der Zugriff auf ein Kubernetes-Service-Account-Token sowie unshare und nsenter. Jeder Fund hat eine Schwere, und alles ab --block-severity – voreingestellt ist high – wird blockiert. Mildere Funde machen den Aufruf nur verdächtig; dieser Unterschied ist nötig, damit die dritte Stufe greifen kann.
Die dritte Stufe ist optional: ein Richtermodell, das verdächtige Aufrufe mit allow oder block beantwortet. Ohne Richter werden sie nur protokolliert. Der Richter sieht ausschließlich, was der Nutzer geschrieben hat, den Tool-Aufruf selbst und die Funde der Detektoren. Er sieht nie die Begründung des Agenten und nie die Ergebnisse früherer Tool-Aufrufe – aus einem pragmatischen Grund: Ein Agent, der einen Aufruf ausgeführt sehen will, würde genau dort argumentieren. Seine Entscheidung kann Blöcke ergänzen, aber keinen bestehenden Block aufheben. Saubere Aufrufe erreichen ihn gar nicht erst.
Warum Streaming keine Verzögerung erzeugt
Auf den ersten Blick klingt eine Prüfinstanz mitten im Datenstrom nach Wartezeit. Praktisch entsteht keine, weil der Proxy die Dinge trennt: Text wird durchgereicht, sobald er ankommt. Ein Tool-Aufruf dagegen wird zurückgehalten, bis seine Argumente vollständig sind – was ohnehin dem entspricht, was Harnesses tun, denn ein unvollständiger Aufruf wird nirgends ausgeführt. Ist der Aufruf komplett, geht er byte-genau weiter oder wird durch den Textbescheid ersetzt. Die Prüfung selbst kostet laut Projekt auf einem Mac mit M-Chip etwa zehn bis zwanzig Millisekunden pro Aufruf.
Eine Sicherheitsprüfung, die spürbar bremst, wird nach zwei Tagen abgeschaltet. Zehn bis zwanzig Millisekunden fallen nicht auf, schon gar nicht neben der Zeit, die ein Modell für seine Antwort braucht. Gewartet wird nur dort, wo es ohnehin nichts zu beschleunigen gibt.
Eigene Prüfungen als Plugin nachrüsten
Die eingebauten Detektoren decken allgemeine Muster ab, aber jede Umgebung hat ihre eigenen Empfindlichkeiten. Über --plugin MODULE:FACTORY oder --plugin path/to/file.py:FACTORY lässt sich eigenes Regelwerk nachladen; die Konvention ist dieselbe wie bei atif-scan. Die Factory-Funktion gibt eine Liste aus Detektoren, Regeln und Allowances zurück. Detektoren und Regeln ab der eingestellten Schwelle blockieren wie die eingebauten. Eine Allowance entschuldigt die Funde der Prüfungen, die sie abdeckt – gedacht für Aufgaben, die legitim genau das tun, was eine Prüfung ansonsten ankreidet.
Das Beispiel aus der Dokumentation zeigt das Muster: ein RegexDetector, der anschlägt, sobald der Dateiname task.toml in einem Kommando oder Argument auftaucht, weil dort die veröffentlichte Antwort einer Aufgabe stehen könnte. Dazu eine Allowance für den Fall, dass die Aufgabe selbst Recherche im Web verlangt. Über --allow-host lässt sich zusätzlich festlegen, welche Hosts ein Aufruf überhaupt erreichen darf. Aus Python heraus übergibt man die Prüfungen direkt an den Scanner. Geprüft wird im Code, nicht per Prompt.
Trajektorien bereinigen, bevor sie veröffentlicht werden
Der zweite Problemkreis ist weniger spektakulär, im Alltag aber häufiger: Agenten-Trajektorien sind voller Dinge, die man nicht teilen sollte. safeagents scrub durchsucht ATIF-Traces, Session-Logs sowie beliebiges JSON, JSON Lines oder Text nach Zugangsdaten – API-Keys, Token, Auth-Header, private Schlüssel, Werte mit verdächtigen Namen, Passwörter in URLs – und zusätzlich nach den Werten der eigenen geheimen Umgebungsvariablen, nach E-Mail-Adressen, Home-Verzeichnissen und öffentlichen IP-Adressen. Ein einmal gefundenes Geheimnis wird überall maskiert, auch wenn es an anderer Stelle nackt auftaucht.
Mit --show sieht man vorher, was entfernt würde: jeder verschiedene Wert einmal, Zugangsdaten zuerst, mit Anzahl und der Stelle des ersten Auftretens. --show-all listet jede Fundstelle, --reveal gibt die Werte im Klartext aus. safeagents publish macht dasselbe, prüft die redigierten Kopien anschließend noch einmal und lädt nur diese in ein Hugging-Face-Dataset – standardmäßig privat, mit --public öffentlich, authentifiziert über den Token aus hf auth login. JSON bleibt gültiges JSON, auch die Schlüssel werden redigiert, und Dateien ohne Fundstellen gehen byte-genau durch. Die Redaktion ist ausdrücklich als Best-Effort gekennzeichnet: Der Bericht sollte gelesen werden, bevor man veröffentlicht.
Benchmark, Live-Ansicht und was das praktisch bedeutet
Ob ein Richtermodell brauchbar blockiert, lässt sich messen. Der Benchmark safeagents-judge-bench bewertet Entscheidungsmodelle anhand von 562 echten Tool-Aufrufen, die der Scanner markiert hat, jeweils mit dem Label allow oder block und der Art des Missbrauchs – Reward Hacking, Harness-Introspektion, Missbrauch von Zugangsdaten und weitere Kategorien. Die zugehörige Rangliste ist der pragmatische Weg, eine --judge-model-Einstellung auszuwählen, statt sie zu raten.
Im Betrieb lässt man den Proxy im Vordergrund laufen; das Terminal zeigt ein laufendes Histogramm der Urteile und die letzten Aufrufe. Zwei Markierungen sind zu unterscheiden: WARN heißt, der Aufruf ist gelaufen, hat aber einen Fund ausgelöst; FLAG heißt, er wäre ohne --log-only blockiert worden. Im Hintergrund schreibt der Proxy dieselben Zeilen in sein Log, und blockierte, markierte und verdächtige Aufrufe landen zusätzlich in ~/.safeagents/calls.jsonl – nur für den eigenen Nutzer lesbar, weil in Argumenten Geheimnisse stehen können. Für Tests ohne echten Agenten gibt es scripts/simulate.py, das fiktive Sitzungen mit den Missbrauchsmustern aus dem Cohere-Report durchspielt, ohne etwas auszuführen. Lizenz: Apache 2.0.
Der Schutz ersetzt keinen vorsichtigen Umgang mit Agenten. Er legt eine Kontrollschicht dorthin, wo bisher keine war: zwischen dem Vorschlag des Modells und der Ausführung durch den Harness. Der Einstieg über --log-only ist die vernünftige Reihenfolge – erst sehen, welche Aufrufe im eigenen Arbeitsalltag auffallen, dann die Schwelle justieren, dann blockieren. Wer Coding-Agenten mit Zugriff auf ein echtes Dateisystem und echte Zugangsdaten laufen lässt, sollte diesen Zwischenschritt einplanen. Ein Modell, das einen Host erfindet oder ein Token weiterreicht, braucht keinen bösen Willen, um Schaden anzurichten.
Quelle: github.com
