Heute soll der Agent tun, was er eigentlich nicht darf
Mein Agent will einen Befehl als root ausführen. Er hat keinen sudo-Eintrag, kein Passwort, nichts in /etc/sudoers. Das ist Absicht. Hier ist der Weg, den ich gebaut habe, damit er trotzdem genau ein Kommando ausführen kann — approved von einem Menschen, auditiert, nicht cachebar, nicht wiederverwendbar.
Heute soll mein Agent etwas tun, was er eigentlich nicht darf.
Das ist nicht metaphorisch gemeint. openclaw läuft auf einem Mini-PC zuhause als eigener OS-User namens openclaw. Dieser User hat keinen sudo-Eintrag. Kein Passwort. Nichts in /etc/sudoers. Kein NOPASSWD. Das ist Absicht — der ganze Sinn davon, den Agent als eigenen unprivilegierten User laufen zu lassen, ist dass ich seinem Handeln niemals implizit vertrauen muss.
Heute braucht er einen Befehl als root.
Der Standard-Reflex
Wenn man im Internet nach "how to give an AI agent sudo" sucht, bekommt man bei so ziemlich jedem Tutorial denselben Vorschlag: trage den Agent-User in /etc/sudoers.d/ ein, setz ein NOPASSWD: ALL, und fertig. Manchmal wird die Zeile etwas eingeschränkt auf bestimmte Binaries, aber die Grundhaltung ist dieselbe: einmal vertraut, immer vertraut, keine Rückfragen.
Ich habe das nie gemacht und werde es nie machen. Die Gründe sind nicht Paranoia, sondern Architektur.
NOPASSWD: ALL ist keine Sicherheitsaussage. Es ist der Verzicht auf Sicherheit. Der sudo-Mechanismus existiert, um einen Nachweis der Authorisierung einzufordern. Wenn dieser Nachweis entfällt, ist der verbliebene Effekt von sudo nur noch das Umschalten der Effective UID. Der gesamte Audit- und Policy-Anteil ist weg.
Der Agent wird zum ewig-privilegierten User. Wer Zugriff auf den laufenden Agent-Prozess bekommt — ein kompromittiertes npm-Package in einem Tool, eine Prompt-Injection in einem Remote-Dokument, ein fehlerhafter Hook — hat ab diesem Moment Root auf der Maschine. Nicht eine Stunde, nicht "wenn der Agent aktiv arbeitet", sondern strukturell und permanent.
Cache macht aus einmal approved automatisch immer approved. Standard-sudo hat ein timestamp_timeout von 5 bis 15 Minuten. Für interaktive Menschen ist das bequem. Für einen Agent, der theoretisch im Sekundentakt Commands absetzen könnte, bedeutet es: ein einziges approved Kommando genügt, und der komplette Zeitraum danach ist offen.
Zusammen ergibt das: NOPASSWD ist nicht eine etwas schwächere Variante von richtigem sudo. Es ist kategorial etwas anderes. Es gibt den User auf als Sicherheitsgrenze.
Was sudo annimmt, und warum diese Annahmen bei Agents brechen
sudo ist aus einer Welt gewachsen, in der der User vor dem Terminal sitzt und tippt. Drei Annahmen sind in dieses Design eingeschrieben:
- Der Invoker ist die authorisierte Person. Das Passwort ist die Brücke zwischen Körper vor Tastatur und Eintrag in /etc/passwd. Wenn der Invoker ein Prozess ohne Gedächtnis ist, gibt es keine solche Brücke. Ein Prozess kann ein Passwort halten, aber das Halten ist keine Authentifizierung — es ist nur Speicherung.
- Cache-Optimierung ist gut. Stimmt für interaktive Menschen, die im Laufe einer Admin-Session mehrere Kommandos absetzen und nicht jedes Mal ihr Passwort wiederholen wollen. Ist katastrophal für Agents, für die "mehrere Kommandos hintereinander" der Normalfall ist und genau die Situation, die nicht-pauschal approved werden sollte.
- Policy ist zur Konfigurationszeit entscheidbar. Stimmt für Admin-Workflows mit bekannter Rollen-Matrix. Stimmt nicht für Agent-Workflows, die per Definition unvorhersehbar sind — niemand weiß um 14:32, welches Kommando um 14:37 nötig sein wird, also kann niemand es um 14:00 vorab in
/etc/sudoers.d/schreiben.
Die drei Annahmen sind nicht falsch — sie sind für eine andere Welt gebaut. Die Welt der Menschen. Wenn man sie unverändert für Agents übernimmt, übernimmt man implizit das Vertrauensmodell der Menschen-Welt, ohne die Feedback-Schleifen, die es in der Menschen-Welt tragfähig machen (Sozialdruck, Auditierbarkeit auf Person, Gedächtnis).
Die Inversion
Ich brauchte also einen Mechanismus, der die Frage "darf dieser Prozess jetzt gerade diesen einen Befehl als root ausführen?" live beantwortet — nicht vorab, nicht gecacht, sondern einmalig pro Kommando, und nicht vom Agent selbst, sondern von einem Menschen.
Das Ding, das ich dafür gebaut habe, heißt escapes. Es ist ein setuid-root Binary, geschrieben in Rust, liegt öffentlich auf GitHub und hat genau eine Aufgabe: Einen Command mit elevated privileges ausführen, aber nur, wenn ein signierter Grant-Token aus einem OpenApe-IdP vorliegt, der für genau dieses Kommando, auf genau dieser Maschine, genau einmal gültig ist.
Die harte Grenze bleibt hart. Nur der Übertritt wird protokolliert.
Der Agent-User bekommt nichts dazu. Er ist weiter unprivilegiert. Er hat weiter keinen Eintrag in sudoers. Was er bekommt, ist die Möglichkeit, einen Grant zu requesten — und wenn ich als Approver zustimme, dann ist für genau einen Bruchteil der Zeit, für genau einen Command, die Grenze offen. Nicht der User wird approved, sondern der Crossing.
Der konkrete Flow
Hier ist, was passiert, wenn openclaw heute whoami als root ausführen will.
Der Agent ruft:
apes run --as root -- whoami
Ein einziger Command. Kein sudo. Kein Passwort-Prompt. Keine bestehende Session. Die CLI apes sieht das --as root Flag und schaltet auf den escapes-Audience-Flow um. Sie erstellt einen Grant-Request am IdP mit einer Payload in dieser Form:
{
"audience": "escapes",
"target_host": "mini.local",
"command": ["whoami"],
"decided_by": "patrick@hofmann.eco"
}
Der IdP schickt diesen Request an mich zur Approval. In der Browser-UI sehe ich den vollständigen Command, den Ziel-Host, den Agent, und die Approve/Deny-Buttons. Ich entscheide.
Wenn ich approve, signiert mein Passkey den Grant, der IdP gibt ein JWT zurück, die CLI nimmt das JWT und ruft:
escapes --grant <jwt> -- whoami
escapes läuft mit Effective UID 0 (setuid-Bit), verifiziert sieben Eigenschaften des Grants bevor es überhaupt daran denkt, irgendetwas auszuführen:
- Issuer ist in
allowed_issuers— nur JWKS dieser IdPs werden gefetcht - JWT-Signatur ist gültig gegen die JWKS
- Approver ist in
allowed_approvers(das ist die Entsprechung zusudoers— aber für Menschen, nicht für Prozesse) - Audience ist in
allowed_audiences(Default:["escapes"]) target_hostmatched den tatsächlichen Hostnamen dieser Maschine — ein Grant fürmini.localfunktioniert nicht aufserver01- Command /
cmd_hashmatched exakt den tatsächlich übergebenen Command - IdP
/consumebestätigt: dieses Grant-Token wurde noch nie eingelöst — Replay-Schutz
Erst wenn alle sieben Checks grün sind, sanitized escapes das Environment (weg mit LD_PRELOAD, PATH auf Default, etc.) und ruft execvp("whoami", []). Der Command läuft als root, genau einmal, sieht genau die argv, die ich approved habe, auf der Maschine, auf der ich approved habe, und schreibt einen vollständigen Audit-Log-Eintrag in /var/log/openape/audit.log.
Nach dem Exit ist die Sache vorbei. Der Grant ist consumed. Wenn der Agent zwei Minuten später ein weiteres root-Kommando braucht, fängt der ganze Tanz wieder von vorne an. Keine Cache. Kein Timestamp. Kein Rest-Vertrauen.
sudoers ist leer geblieben
Während ich diese Serie schreibe, habe ich mir die sudo-History angeschaut, um zu sehen wann und wofür ich auf meinem Mini-PC in den letzten Wochen manuell sudo verwendet habe. Und um zu prüfen, dass der openclaw-User tatsächlich leer durch jede Zeile geht.

Das ist der Punkt, auf den es ankommt. Die sudoers-Konfiguration dieser Maschine hat sich durch den gesamten Prozess nicht verändert. Kein neuer Eintrag. Kein NOPASSWD. Kein privilegierter User. Was sich geändert hat, ist dass es einen zweiten Pfad gibt — nicht durch sudo, sondern neben sudo — auf dem Commands per Grant statt per Passwort elevated werden können. sudo bleibt wofür es gedacht war: interaktive Menschen, die am Terminal sitzen und tippen. escapes übernimmt den Agent-Fall, den sudo nie modelliert hat.
Der kategorische Unterschied
Die Frage, die unter meinem ape-shell-Post kam: "Geht das nicht auch einfacher? Sudoers mit Command-Whitelisting?" Die Antwort ist nein, und der Unterschied ist nicht graduell. Er ist strukturell:
| Achse | sudo (mit NOPASSWD) | escapes |
|---|---|---|
| Wann ist die Policy entschieden? | Zur Konfigurationszeit, statisch in /etc/sudoers | Zur Laufzeit, pro Kommando, fresh |
| Wer entscheidet? | Der Invoker — also der Agent selbst | Ein separater Approver, vom Invoker getrennt |
| Credential-Lifetime | Cache, 5-15 Minuten Standard | Single-use JWT, /consume sperrt Replay |
| Command-Binding | Path-Prefix Matching (notorisch leaky) | cmd_hash im signierten JWT |
| Host-Binding | Statisch in Host_Alias | Kryptographisch im JWT verankert |
| Audit | Lokal, oft nicht aggregiert | JSONL mit grant_id, approver, cmd_hash, issuer, target_host |
Jede einzelne Zeile ist ein Vertrauens-Delegations-Punkt, den sudo an die Konfigurationszeit verlagert und escapes an die Laufzeit. Diese Verlagerung ist der eigentliche Inhalt. Alles andere — das Rust-Binary, das JWT, die sieben Checks — sind die Mechanismen, mit denen die Verlagerung technisch umgesetzt wird.
Mensch und Agent sind auf Protokoll-Ebene gleich
Ein Nebeneffekt, der mir erst im Bauen klar wurde: ich benutze apes run --as root -- jetzt auch für mich selbst. Wenn ich auf einem der Hosts, die meinem Team gehören, ein privilegiertes Kommando brauche, tippe ich denselben Command, den mein Agent tippen würde. Derselbe Flow. Dieselbe Grant-Anfrage. Derselbe Approval-Schritt.
Der einzige Unterschied: wenn ich das Kommando anstoße, ist der Approver ein Team-Kollege. Wenn der Agent es anstößt, bin ich der Approver. Gleiche Infrastruktur, andere Rolle. Das ist nicht zufällig so. Es ist das Prinzip Mensch und Agent sind auf Protokoll-Ebene gleich, aus dem die ganze OpenApe-Story herkommt, konkret angewandt auf den Privilegien-Layer.
Die Konsequenz ist, dass escapes kein Agent-Spezial-Tool ist. Es ist allgemeine Infrastruktur, die nebenbei auch von Agents benutzt werden kann. Dieselbe Gleichbehandlung, die OpenApe beim Login-Flow in den Vordergrund gestellt hat ("der Mensch hat eine Session, der Agent hat eine Session — die Infrastruktur weiß nicht, welcher ist welcher"), wiederholt sich hier beim Elevation-Flow.
Was das kostet
Wer im Grant-System arbeitet, wartet auf Menschen. Wenn openclaw um 3 Uhr nachts entscheidet, dass er ein privilegiertes Kommando braucht, dann wartet er. Er weckt mich nicht auf. Er fährt nicht fort ohne mich. Das gilt nicht nur für escapes, das gilt für jeden Grant im gesamten OpenApe-Stack, solange sich der Agent am Rand von dem bewegt was er darf. Ask first ist der ganze Punkt. Aber es ist Reibung, und wer diese Reibung nicht will, ist hier falsch.
Es braucht Infrastruktur. Ohne einen laufenden OpenApe-kompatiblen Identity Provider funktioniert nichts — jemand muss die Signatur-Keys halten, die JWKS veröffentlichen, /consume als Replay-Schutz fahren. Kein IdP, kein escapes.
Und escapes ist Vibe-Coded Software. Das Sicherheits-Konzept dahinter ist das, was zählt — nicht ob mein Rust-Code fehlerfrei ist. sudo hat über 40 Jahre Open-Source-Audit-Zeit hinter sich. escapes hat wahrscheinlich Bugs, und Linus Torvalds würde mir da vermutlich recht geben. Der Code ist klein, MIT-lizenziert, und auf GitHub — wer es produktiv einsetzen will, soll es sich vorher ansehen.
Wie man anfängt
cargo install openape-escapes
Dann das Binary privilegieren — entweder via Linux Capabilities:
sudo setcap cap_setuid+ep $(which escapes)
Oder klassisch via setuid-Bit:
sudo chown root:root $(which escapes) && sudo chmod u+s $(which escapes)
Dann die Trust-Beziehung einrichten — /etc/openape/config.toml definiert, welchem IdP escapes vertraut und wer Grants approven darf:
[security]
allowed_issuers = ["https://id.openape.at"]
allowed_approvers = ["patrick@hofmann.eco"]
Zwei Zeilen. allowed_issuers ist die Liste der IdPs deren JWKS akzeptiert werden. allowed_approvers ist das Äquivalent zu sudoers — aber für Menschen, nicht für Prozesse. Alles andere hat sinnvolle Defaults.
Dann apes installieren, einen IdP konfigurieren, und als erstes Kommando:
apes run --as root -- whoami
Wenn alles richtig aufgesetzt ist, bekommst du einen Approval-Request im Browser, approvst, und siehst:
root

Das ist alles. Ein Wort. Und dahinter stehen sieben Verifikations-Schritte, ein signierter JWT, ein Audit-Log-Eintrag, und ein ausdrücklich zustimmender Mensch. Derselbe Output wie bei sudo whoami, aber ein fundamental anderes Vertrauensmodell.
Warum das für mich das richtige Muster ist
Ich habe in den letzten Wochen mehrere Layer von OpenApe öffentlich gebaut — Identity, ape-shell, Claude Grant Gate, jetzt escapes. Jeder einzelne Layer ist eine Variation auf dieselbe These: Infrastructure over Instructions. Nicht der Agent wird gebeten, sich an Regeln zu halten. Die Umgebung lässt Regelverletzungen strukturell nicht zu.
escapes ist der Layer, auf dem das am deutlichsten wird, weil er am meisten wehtut, wenn man es falsch macht. Wer einem Agent root gibt, hat effektiv die Maschine abgegeben. Wer einem Agent einen Single-Use-Grant gibt, hat nur diese eine Operation abgegeben. Der Unterschied ist alles.
Die harte Grenze bleibt hart. Nur der Übertritt wird protokolliert.
Und ja — cat /etc/shadow geht auch. Audit. Denied.
openape-escapes@0.4.0 ist auf crates.io und auf GitHub verfügbar, MIT-lizenziert. Die vorigen Artikel dieser Serie erzählen wie OpenApe, ape-shell, und die Grant-Integration dahinter entstanden sind.