limen
Die kleine Sandbox. Fuer den taeglichen Gebrauch.
Es gibt diese Horrorgeschichten: Der Agent löscht Dateien, zerschießt das Repository oder formatiert im schlimmsten Fall irgendeine Festplatte.
Was ist die Alternative? Jeden möglicherweise gefährlichen Befehl einzeln absegnen? Oder den Agenten gleich in eine VM sperren?
Ein Agent, der an einem Repository arbeitet, braucht Zugriff auf dieses Repository. Er braucht aber nicht nebenbei meine persönlichen Dateien, meine normalen SSH-Schlüssel oder jedes andere Projekt auf dem Rechner.
Ich benutze dafür Bubblewrap.
Damit kann ich dem Agenten genau die Umgebung geben, die er für seine Arbeit braucht. Bestimmte Verzeichnisse verschwinden komplett, andere sind nur lesbar oder beschreibbar. Bei Bedarf bekommt der Agent eigene SSH-Keys. Und wenn ich Teile meines $HOME freigeben will, geht das auch.
Aus diesem Bedarf ist limen entstanden. Das Tool ist Open Source.
Open Source · veröffentlicht
Wofür ich es einsetze
Ich habe unterschiedliche Profile für lokale und externe Agenten.
Wenn ein lokales Modell einige meiner SSH-Keys kennt, kann das für mich okay sein. Bei Codex oder anderen externen Modellen möchte ich das nicht unbedingt.
Mit limen bekommt der jeweilige Agent einfach andere Schlüssel. Mein echtes ~/.ssh kann dabei komplett unsichtbar bleiben.
Andere typische Fälle:
Code bearbeiten und Tests ausführen, während persönliche Dateien verborgen bleiben. Ein fremdes Repository nur zum Lesen öffnen und für einen Bericht ein eigenes Ausgabeverzeichnis freigeben. Einzelne Dateien oder Verzeichnisse als Kontext bereitstellen, etwa Projektnotizen oder Referenzdokumentation. Werkzeuge ohne Netzwerkzugriff ausführen, wenn sie für ihre Aufgabe kein Netz brauchen.
Ein Beispiel
Ein Agent soll eine Funktion in einer Webanwendung ergänzen.
Im Projekt darf er Dateien bearbeiten und Tests starten. Referenzdokumentation bekommt er nur zum Lesen, mein Wiki darf er ergänzen. Für SSH bekommt er ein eigenes Verzeichnis im Projekt. Der Zustand des Coding Agents Pi bleibt zwischen den Sitzungen erhalten.
Diese Zugriffe stehen in der Projektkonfiguration.
persist .pi erhält das Verzeichnis ~/.pi innerhalb der Sandbox. Mein persönliches ~/.pi auf dem Host wird dabei nicht eingebunden.
Auch die SSH-Dateien kommen aus .limen/ssh im Projekt. Mein normales ~/.ssh bleibt verborgen.
# .limen/config
profile external
# Projekt bearbeiten
cwd write
# Persönliche Dateien verbergen
home hidden
# Dokumentation nur lesen
ro ~/referenzen/webapp
# Wiki lesen und bearbeiten
rw ~/wiki
# Eigene SSH-Dateien fürs Projekt
ssh-dir .limen/ssh
# Pi-Zustand dauerhaft speichern
persist .pi Profile und Presets
Drei Profile geben einen Ausgangspunkt vor. Die Zugriffe kann ich danach für jedes Projekt anpassen. Der Netzwerkzugriff wird unabhängig davon eingestellt.
Wiederkehrende Einstellungen und Startbefehle landen in Presets. limen -p pi lädt zum Beispiel mein Preset für Pi.
Globale Einstellungen gelten für meine normale Arbeitsumgebung. .limen/config und .limen/presets/ enthalten die Abweichungen eines Projekts.
Damit muss ich längere Aufrufe nicht bei jeder Sitzung neu zusammensetzen.
`external`: Das Projekt ist beschreibbar, das persönliche Home-Verzeichnis verborgen. Das ist die Voreinstellung und meistens genau richtig für Codex. local: Das persönliche Home-Verzeichnis wird zusätzlich lesbar. Schreibzugriffe brauchen weiterhin eine eigene Freigabe. Das nutze ich eher für lokale Modelle. * locked: Das persönliche Home-Verzeichnis bleibt verborgen und auch das Projekt ist zunächst nur lesbar.
Beispiele
limen -p codex -- codex --dangerously-bypass-approvals-and-sandboxInnerhalb dieser Umgebung kann Codex im aktuellen Projekt weitgehend selbstständig arbeiten.
Außerhalb des Projekts gelten die Regeln von limen: Mein echtes Home-Verzeichnis ist verborgen, der übrige Host standardmäßig nur lesbar und zusätzlicher Schreibzugriff muss ausdrücklich freigegeben werden.
limen -p pi -- piDamit starte ich Pi.
Mein Pi-Profil bekommt andere SSH-Keys und darf mein $HOME grundsätzlich lesen. Das liegt daran, dass ich Pi häufig mit lokalen Modellen benutze und die Daten dabei den Rechner nicht verlassen.
Mittlerweile nutze ich in Pi auch externe Modelle. Dafür gibt es dann ein eigenes Profil.
Wie es funktioniert
Prompts und Freigabedialoge sind nützlich. Die eigentliche Grenze im Dateisystem sollte aber nicht davon abhängen, ob ein Modell eine Anweisung richtig interpretiert.
limen ist ein Bash-Tool auf Basis von Bubblewrap.
Bubblewrap baut die Dateisystemansicht für den gestarteten Prozess auf und setzt die Zugriffsrechte durch. limen macht daraus Profile, Presets und Projektkonfigurationen, die ich nicht jedes Mal von Hand als langen Bubblewrap-Aufruf schreiben muss.
Mit limen --dry-run kann ich vorher sehen, welche Pfade freigegeben werden, welche Rechte sie bekommen und woher die jeweilige Einstellung stammt.
Projektdateien dürfen zusätzliche Zugriffe nicht einfach unbemerkt einschalten. limen zeigt solche Freigaben an und verlangt eine Bestätigung. Weiterreichende Einstellungen müssen ausdrücklich mit allow erlaubt werden.
Der Dry Run zeigt außerdem die Reihenfolge der Mounts und mögliche Überlagerungen. Dabei wird kein Agent gestartet und es werden keine Zustandsverzeichnisse angelegt.
Ein gekürztes Beispiel:
$ limen --dry-run
limen dry run
Command:
bash
Settings:
profile: external
agent: default
network: enabled
host root: read
working directory: /home/user/projekt
home: hidden behind tmpfs at /home/user
SSH:
writable shared keys:
/home/user/.local/share/limen/ssh -> /home/user/.ssh
Writable host paths:
/home/user/projekt -> /home/user/projekt
/home/user/.local/state/limen/default
-> /home/user/.local/state/limen/default
/home/user/.local/share/limen/ssh -> /home/user/.ssh
/home/user/wiki -> /home/user/wiki
/home/user/wiki -> /home/user/agents/wiki
Read-only host paths:
/ -> /
/home/user/.local/share/limen/tools
-> /home/user/.local/share/limen/tools
/home/user/.tools -> /home/user/.tools
Writable scratch:
/tmp
/var/tmp
Mount order and permission sources:
Later mounts replace earlier access at their destination.
1. ro / -> / [default host root]
2. tmpfs (virtual) -> /home/user [default profile]
3. rw /home/user/projekt -> /home/user/projekt
[default profile; project: CWD or PROJECT environment]
...
9. rw /home/user/wiki -> /home/user/wiki
[project config; target of /home/user/agents/wiki]
10. rw /home/user/wiki -> /home/user/agents/wiki
[project config; symlink /home/user/agents/wiki -> /home/user/wiki]
...In diesem Beispiel ist das Wiki zusätzlich freigegeben. Weil es auch über einen symbolischen Link erreichbar ist, zeigt limen beide Pfade.
Entwicklung
limen ist bewusst ein einfaches Tool.
Bubblewrap übernimmt die Isolation. limen übersetzt Profile, Projektdateien, Presets und Kommandozeilenoptionen in den entsprechenden Aufruf.
Es braucht Linux, Bash, Bubblewrap und GNU realpath.
It's Linux, my friends.
Der Teufel steckt trotzdem im Detail: symbolische Links auflösen, widersprüchliche Freigaben erkennen und sichtbar machen, welche Regel am Ende tatsächlich gilt.
Dazu kommen Bash-Syntaxprüfung und, wenn vorhanden, ShellCheck.
Der Quellcode steht unter der MIT-Lizenz auf GitHub.
TODO: verlinken
Grenzen
limen ist ein Sicherheitsgurt.
Es kann verhindern, dass ein übereifriger Agent versehentlich Dateien verändert, auf die er überhaupt keinen Zugriff haben sollte. Mit einem locked-Profil kann ich die Umgebung noch weiter einschränken.
Backups ersetzt das nicht. Backups sind notwendig. Immer.
limen teilt sich außerdem den Kernel mit dem Host. Je nach Konfiguration können Netzwerkzugriff, lesbarer Host-Zugriff, geerbte Zugangsdaten oder erreichbare SSH-Agent-Sockets weitere Zugriffswege offenlassen.
Ich behandle limen deshalb nicht als Sicherheitsgrenze gegen gezielt bösartigen Code. Dafür gibt es VMs und andere, stärkere Formen der Isolation.
Fazit
Ein nützliches Werkzeug sollte nicht automatisch alle Berechtigungen meines Benutzerkontos erben, nur weil ich es gestartet habe.
Ein Coding Agent braucht genug Freiheit, um seine Arbeit zu machen. Das heißt nicht, dass er aus Bequemlichkeit gleich meinen ganzen Rechner bekommen muss.