
Sandbox für KI-Coding-Agents. Führt Copilot CLI, Claude Code, OpenCode, Gemini CLI, Antigravity, Pi, goose oder eine einfache Shell in einer Sandbox auf Kernel-Ebene aus, mit git- und gh-Guards und einer im Repository festgeschriebenen Sandbox-Richtlinie.
Vom Kernel erzwungene Sandbox für KI-Coding-Agenten. cplt umhüllt GitHub Copilot CLI, OpenCode, Gemini CLI, Antigravity CLI, Pi, Claude Code, goose, DeepSeek Harness oder jede beliebige Shell, sodass der Agent Code schreiben kann, aber keine Anmeldedaten stehlen, auf main pushen, PRs mergen oder Secrets exfiltrieren kann.
sandbox-exec
KI-Agenten führen beliebigen Code aus. Ein kompromittierter Agent, sei es durch Prompt-Injection, einen Supply-Chain-Angriff oder einen bösartigen MCP-Server, kann ~/.ssh lesen, auf main pushen, PRs mergen oder deinen Code exfiltrieren – es sei denn, das Betriebssystem selbst sagt Nein.
cplt bietet dir Durchsetzung auf Kernel-Ebene mit teamkonfigurierbarer Policy:
.cplt.toml, in die Versionskontrolle eingecheckt, sodass sie manipulationssicher und auditierbar istDetaillierte Dokumentation: Konfiguration · Proxy & Domain-Filterung · gh-Befehlswächter · git-Befehlswächter · Bekannte Auswirkungen · Sicherheitsdetails · Sicherheitsmodell
brew install navikt/tap/cplt # macOS. On Debian or Ubuntu, see apt below cplt --shell-install # make 'copilot' run sandboxed (persistent) # --agent opencode for any other agent cplt doctor # check your environment cplt -- -p "fix the tests" # run Copilot in sandbox
Andere Agenten und Sandbox-Befehle:```bash
cplt --agent opencode # OpenCode (Copilot subscription)
cplt --agent opencode --pass-env ANTHROPIC_API_KEY # third-party provider
cplt --agent shell # interactive sandboxed shell (no AI)
cplt exec -- npm install # sandbox any command directly
cplt exec -c "npm install && npm test" # compound commands in sandbox
alias npm="cplt exec -- npm" # sandboxed npm for every invocation
cplt init --write
cplt trust accept --all
cplt config set git_guard.protect_default_branch_only false # block every push, not just main cplt config set git_guard.mode warn # observe instead of blocking
## Was es blockiert
Die Sandbox blockiert den Zugriff auf Anmeldedaten und Secrets im Kernel. Kommando-Guards blockieren destruktive Operationen. Jede Einschränkung gilt für den Agenten und für jeden Prozess, den er startet.
| Ressource | Status | Hinweise |
| --- | --- | --- |
| Lesen/Schreiben des Projektverzeichnisses | ✅ Erlaubt | |
| Lesen/Schreiben/Löschen von `.env*`, `.pem`, `.key` im Projekt | 🔒 Kernel-blockiert | Verhindert Secret-Exfiltration und -Zerstörung. `--allow-env-files` hebt dies auf |
| Schreiben von `.git/hooks`, `.git/config`, `.gitmodules` | 🔒 Kernel-blockiert (macOS), ⚠️ teilweise auf Linux | Verhindert Persistenz über Git-Hooks, hooksPath-Umleitung, Submodul-Hijacking. **Linux:** Landlock kann einen Unterpfad innerhalb eines erlaubten Baums nicht verweigern, daher bleiben diese auf dem reinen Landlock-Pfad beschreibbar. `bwrap` bindet `.git/hooks` schreibgeschützt neu ein, lässt aber `.git/config` und `.gitmodules` bewusst beschreibbar, sodass `core.hooksPath` eine Persistenzroute bleibt, siehe [Linux-Einschränkungen](https://github.com/navikt/cplt/blob/main/docs/security.md#linux). Gilt für **jeden** beschreibbaren Root, das Projekt und jede `allow.write`-Gewährung, einschließlich eines gewährten Worktrees oder Bare-Repos, dessen echte Hooks außerhalb von `<root>/.git` liegen |
| Ausführung aus `/tmp`, `/var/folders` | 🔒 Kernel-blockiert | Verhindert Write-then-Exec. Das Scratch-Verzeichnis leitet TMPDIR an einen sicheren Ort um, standardmäßig aktiviert |
| Schreiben in PATH-aufgelöste bin/shim-Verzeichnisse (`~/.bun/bin`, `~/.deno/bin`, `$PNPM_HOME`, mise `shims/` und das gesamte `installs/`) | 🔒 Kernel-blockiert (macOS), ⚠️ mise teilweise auf Linux | Verhindert das Trojanisieren einer Binärdatei, die dein nächster *nicht-sandboxed* Befehl über PATH auflöst. Aus demselben Grund sind `~/.cargo/bin` und `~/go/bin` schon immer schreibgeschützt. Bricht `bun install -g`, `deno install`, `pnpm add -g`, `mise install`, `mise upgrade`, `mise use -g` innerhalb von cplt, absichtlich, und ein Repo, das eine nicht installierte Toolchain pinnt, bootstrappt nicht mehr. Projektlokale Installationen sind nicht betroffen. **Linux:** mise's beide laufen über das schreibgeschützte `bwrap`-Overlay; der Rest hält nativ. Siehe [Globale Tool-Installationen](https://github.com/navikt/cplt/blob/main/docs/known-impacts.md#global-tool-installs) |
| Ausführung aus `~/Library/Caches` | 🔒 Standardmäßig Kernel-blockiert | Verhindert Binary-Drop-Staging. Copilot-native Module sind über eine Ausnahme befreit. Gezielte Ausnahmen mit `--allow-cache-exec <SUBDIR>` hinzufügen, z. B. `ms-playwright` |
| Ändern von `.vscode/tasks.json`, `launch.json` | ⚠️ Erlaubt, bekanntes Risiko | IDE-Vertrauensgrenze. Siehe [SECURITY.md](https://github.com/navikt/cplt/blob/main/SECURITY.md) für Gegenmaßnahmen |
| Lesen/Schreiben von `~/.copilot` (Auth, Einstellungen) | ✅ Erlaubt | Enthält `file-map-executable` für `keytar.node`, `pty.node`, `computer.node` |
| Schreiben von `~/.copilot/pkg` (native Module) | 🔒 Kernel-blockiert | Verhindert Persistenz durch Ersetzen nativer Module |
| Umgebungsvariablen | 🔒 Bereinigt + gehärtet | Nur eine sichere Allowlist wird durchgelassen. Lifecycle-Skripte blockiert. `--pass-env VAR` fügt eine wieder hinzu |
| Lesen von `~/.config/gh/hosts.yml` + `config.yml` | ✅ Erlaubt (schreibgeschützt) | Nur diese beiden Dateien. Der Rest von `.config/gh` ist blockiert |
| Lesen von `~/.config/mise` | ✅ Erlaubt (schreibgeschützt) | Tool-Versionen und PATH, keine Secrets |
| Lesen von `~/.gitconfig`, `~/.config/git/config` | ✅ Erlaubt (schreibgeschützt) | Ein Dotfiles-Symlink wird zu seinem Ziel verfolgt, sodass ein gestowtes `~/.gitconfig` funktioniert |
| Lesen von `~/.git-credentials` | 🔒 Kernel-blockiert | `credential.helper = store` hält Klartext-Token hier. Kein `--allow-read` öffnet es wieder, wie `~/.netrc`. **Linux:** eine Gewährung auf einen *Vorfahren* (`$HOME` selbst) legt es weiterhin offen, weil Landlock einen Unterpfad innerhalb eines erlaubten Baums nicht verweigern kann |
| Lesen globaler Git-Hooks (`core.hooksPath`) | ✅ Erlaubt (schreibgeschützt, Schreiben verweigert) | Automatisch erkannt. Muss unter `$HOME` mit Tiefe ≥3 liegen. Schreibvorgänge sind explizit blockiert |
| Commit/Tag-Signierung (`commit.gpgsign`, `tag.gpgsign`) | 🔒 Deaktiviert | Private Schlüssel in `~/.ssh` und `~/.gnupg` sind blockiert, daher wird die Signierung über eine Env-Var-Überschreibung deaktiviert |
| Lesen von `~/Library/Application Support/Microsoft` | ✅ Erlaubt (schreibgeschützt) | Geräte-ID für Telemetrie |
| Zugriff auf macOS Keychain | ⚠️ Erlaubt (Lesen+Schreiben) für Agenten, die dort Auth speichern | Die Gewährung kann nicht auf einen Eintrag beschränkt werden, daher erreicht sie jeden Keychain-Eintrag, den der Agent entsperren kann. Opt-in zu `sandbox.keychain_substitute` (EXPERIMENTELL, standardmäßig aus), um sie bei Läufen wegzulassen, bei denen der Agent ohne sie authentifizieren kann — `CLAUDE_CODE_OAUTH_TOKEN` für Claude Code, eine vorhandene Fallback-Token-Datei für Antigravity. Siehe [SECURITY.md](https://github.com/navikt/cplt/blob/main/SECURITY.md#keychain-access-is-all-or-nothing) |
| Ausgehendes Netzwerk (Port 443) | ✅ Erlaubt | Jeder andere Port ist blockiert. Weitere mit `--allow-port` hinzufügen |
| Localhost ausgehend | 🔒 Kernel-blockiert (macOS), ⚠️ portbasiert auf Linux | Verhindert Zugriff auf lokale Dienste. Eingehend funktioniert weiterhin für den Proxy. **Linux:** Landlock-Regeln sind nur Portnummern und können `localhost:443` nicht von `remote:443` unterscheiden, daher ist ein lokaler Dienst auf einem erlaubten Port erreichbar und es gibt keine localhost-spezifische Verweigerung. `--with-proxy` für SSRF-Schutz verwenden, siehe [Linux-Einschränkungen](https://github.com/navikt/cplt/blob/main/docs/security.md#linux) |
| SSH-Agent (Unix-Socket) | 🔒 Kernel-blockiert (macOS), ⚠️ nur Env auf Linux | Verhindert Signierung von Git-Operationen oder SSH zu Hosts. **Linux:** Unix-Socket-`connect()` wird nicht gegated, daher ist das zurückgehaltene `SSH_AUTH_SOCK` die einzige Barriere und ein Agent, der es selbst setzt, kann die geladenen Schlüssel verwenden. `bwrap` versteckt den Standard-OpenSSH-Socket unter `/tmp`, aber nicht einen gnome-keyring/gcr- oder systemd-Agent unter `$XDG_RUNTIME_DIR`. Siehe [Linux-Einschränkungen](https://github.com/navikt/cplt/blob/main/docs/security.md#linux) |
| Entwickler-Tools (`~/.cargo`, `~/.gradle`, `~/.m2`, `~/.sdkman`, `~/.jenv`, `~/.pyenv`, `~/.konan`, etc.) | ✅ Erlaubt (Lesen+Schreiben für Caches) | Nur Verzeichnisse, die auf der Festplatte existieren. Zur Laufzeit verschärft durch das, was `cplt doctor` erkennt |
| Registry-Anmeldedateien (`~/.m2/settings.xml`, `~/.gradle/gradle.properties`, `~/.cargo/credentials`) | 🔒 Kernel-blockiert auf macOS. Auf Linux bleibt das übergeordnete Tool-Verzeichnis lesbar | Mit `--allow-read` überschreiben. Siehe [Private Registries](https://github.com/navikt/cplt/blob/main/docs/known-impacts.md#private-registries) |
| Lesen von `~/.npmrc` | 🔒 Kernel-blockiert (beide Plattformen) | Mit `--allow-read` überschreiben. Bricht yarn 1, siehe [yarn 1](https://github.com/navikt/cplt/blob/main/docs/known-impacts.md#yarn-1-and-unreadable-home-rc-files) |
| Go-Quellcode (`~/go/src`) | 🔒 Kernel-blockiert | Nur `~/go/bin` und `~/go/pkg` sind lesbar |
| Lesen von `~/.ssh`, `~/.gnupg`, `~/.aws`, `~/.azure` | 🔒 Kernel-blockiert | |
| Lesen von `~/.kube`, `~/.docker`, `~/.nais` | 🔒 Kernel-blockiert | |
| Lesen von `~/.password-store`, `~/.terraform.d` | 🔒 Kernel-blockiert | |
| Lesen von `~/.config/gcloud`, `~/.config/op` | 🔒 Kernel-blockiert | Einzelne Dateien sind mit `--allow-read` überschreibbar. Siehe [Cloud-Anmeldedaten](https://github.com/navikt/cplt/blob/main/docs/known-impacts.md#cloud-credential-directories) |
| Lesen oder Schreiben von `~/.config/cplt`, `~/.nav-pilot` | 🔒 Kernel-blockiert | Tool-Zustand, der entscheidet, was der *nächste* Start tun darf. `~/.config/cplt` ist als ganzer Teilbaum nicht überschreibbar; innerhalb von `~/.nav-pilot` bleibt ein benannter Pfad gewährbar, sodass eine gepinnte agentpakke-Nutzlast gelesen werden kann |
| Lesen von `~/.netrc`, `~/.pypirc`, `~/.vault-token` | 🔒 Kernel-blockiert | Auf beiden Plattformen nicht überschreibbar. Eine davon in `allow.read` zu nennen ist ein Startfehler |
| Lesen von `~/.gem/credentials` | 🔒 Kernel-blockiert | Auf beiden Plattformen nicht überschreibbar. Eine davon in `allow.read` zu nennen ist ein Startfehler |
| Destruktive `gh` CLI-Operationen (merge, delete, release) | 🔒 Kommando-gegated (standardmäßig an) | Opt-out mit `--no-gh-guard`. Siehe [gh guard](https://github.com/navikt/cplt/blob/main/docs/gh-guard.md) |
| `git push` auf den Default-Branch | 🔒 Kommando-gegated (standardmäßig an) | Blockiert Pushes auf `main`/`master`; Feature-Branch-Pushes funktionieren weiterhin. `protect_default_branch_only = false` blockiert jeden Push, `git_guard.mode = "warn"` warnt nur, `--no-git-guard` macht Opt-out |
| Vererbung durch Kindprozesse | ✅ Alle Einschränkungen gelten für Subprozesse | |
Diese Tabelle ist eine Zusammenfassung. Die Sandbox erlaubt außerdem Zugriff auf Systemdateien (SSL-Zertifikate, `/etc/hosts`), temporäre Verzeichnisse (Lesen und Schreiben, kein Exec) und System-Tool-Pfade (`/usr/bin`, `/opt/homebrew`). Führe `cplt --print-profile` für die vollständigen SBPL-Regeln aus.
Für das vollständige Sicherheitsmodell, die Bedrohungsanalyse und die Teststrategie lies [SECURITY.md](https://github.com/navikt/cplt/blob/main/SECURITY.md).
## Wie cplt sich vergleicht
### Codex CLI's Sandbox
| Bereich | cplt | Codex CLI Sandbox |
| --- | --- | --- |
| Kontrolle ausgehender Netzwerkverbindungen | CONNECT-Proxy mit Domain-Allow/Block-Listen | Keine Filterung auf Domain-Ebene |
| Umgebungsbehandlung | Allowlist plus gehärtete Env-Injektion | Einfacheres Pass-Through-Modell |
| Schutz von Secret-Dateien | Deny-Muster wie `.env*`, `.pem`, `.key` innerhalb des Repos | Primär verzeichnisbezogener Zugriff |
| Repo-Richtlinie | [`.cplt.toml`](https://github.com/navikt/cplt/blob/main/docs/configuration.md#per-repo-configuration-cplttoml) mit explizitem Trust/Approval-Flow | Keine Richtliniendatei auf Repo-Ebene |
| Agent-Unterstützung | Copilot, OpenCode, Gemini CLI, Antigravity CLI, Pi, Claude Code, goose, DeepSeek Harness oder Shell | Nur Codex |
cplt ist nicht überall stärker. Codex CLI hat heute Linux-Namespace-Isolation und bietet bereits explizite Sandbox-Modi wie read-only und workspace-write. cplt hat diese Modus-Matrix noch nicht.
### Docker-basierte Sandboxes
| Bereich | cplt | Docker-basierte Sandbox |
| --- | --- | --- |
| Startzeit | Ungefähr sofort für normale CLI-Nutzung | Normalerweise langsamerer Container-Start |
| Netzwerkkontrolle | Ausgehende Filterung pro Anfrage über Proxy | Normalerweise Alles-oder-Nichts-Netzwerkzugriff |
| Dateikontrollen | Regeln pro Pfad und pro Muster | Kontrollen pro Mount |
| Host-Anforderungen | Einzelne Binärdatei | Docker-Daemon erforderlich |
| Eignung für Firmenlaptops | Funktioniert, wo Docker nicht verfügbar oder eingeschränkt ist | Oft durch lokale Richtlinie blockiert |
Docker bietet in manchen Umgebungen weiterhin stärkere Isolation, besonders wenn du ein vollständig getrenntes Dateisystem und Prozess-Namespace willst. cplt tauscht das gegen leichteres Setup und engere Integration mit der Maschine, auf der du bereits entwickelst.
### VS Code Agent-Modus-Berechtigungen
Tools wie der VS Code Agent-Modus verlassen sich hauptsächlich auf UI-Berechtigungen. cplt erzwingt seine Einschränkungen im Kernel, sodass der Agent sich nicht mit einem Prompt oder einer geänderten Anweisung darum herumreden kann. Das ist am wichtigsten für CLI-Agenten und Credential-Exposition:
- cplt funktioniert außerhalb der IDE
- Env-Vars werden gefiltert, bevor der Agent startet
- sensible Dateien können blockiert werden, auch wenn sie innerhalb des Repos liegen
- dieselben Einschränkungen gelten für Kindprozesse
### Claude Code's Sandbox (Anthropic Sandbox Runtime)
[Anthropic Sandbox Runtime](https://github.com/anthropic-experimental/sandbox-runtime) (`srt`) ist die Sandboxing-Schicht, die von Claude Code verwendet wird. Gleicher High-Level-Ansatz wie cplt, macOS Seatbelt plus Kernel-Level-Linux-Enforcement plus ein HTTP-Proxy, andere Implementierung.
| Bereich | cplt | Anthropic srt |
| --- | --- | --- |
| Sprache / Auslieferung | Einzelne Rust-Binärdatei | Node.js + npm-Paket + externe Abhängigkeiten |
| Linux-Backend | Landlock LSM (keine Abhängigkeiten, keine Namespaces) | bubblewrap (Container über User-Namespaces) |
| Umgebungsfilterung | Strikte Allowlist + Suffix-Deny (`_TOKEN`, `_SECRET`) | Erbt vollständige Parent-Env (Secrets gehen durch) |
| Schutz von Credential-Verzeichnissen | 15+ Verzeichnisse standardmäßig verweigert | Benutzer muss manuell konfigurieren |
| DNS-Rebinding-Schutz | ✅ Post-DNS-IP gegen private Bereiche geprüft | ❌ Nicht implementiert |
| Netzwerk-Proxy | HTTP CONNECT + Domain-Allow/Block | HTTP + SOCKS5 + experimentelles TLS MITM |
| SSH-Git | Auf macOS im Kernel blockiert (Agent-Socket verweigert); auf Linux wird nur `SSH_AUTH_SOCK` zurückgehalten | Über SOCKS5 proxied |
| Paketmanager-Skripte | Standardmäßig blockiert (`npm_config_ignore_scripts`) | Nicht blockiert |
| Agent-Unterstützung | Copilot, OpenCode, Gemini, Antigravity, Pi, Claude Code, goose, DSH, Shell | Claude Code |
| Konfiguration | TOML (global + pro Repo) | JSON (nur global) + `--control-fd` Live-Updates |
| Bibliotheks-API | ❌ Nur Binärdatei | ✅ Einbettbare TypeScript-Bibliothek |
cplt ist out of the box sicherer: Env-Filterung, Credential-Schutz, DNS-Rebinding-Prüfungen, Blockierung von Lifecycle-Skripten. srt ist flexibler: SOCKS5, TLS-Inspektion, Callbacks pro Anfrage, Bibliothekseinbettung. Die Wahl des Linux-Backends ist wichtig. bwrap braucht Workarounds auf Ubuntu 24.04+ wegen AppArmor-Userns-Einschränkungen, während Landlock Kernel 5.13 oder neuer erfordert, aber null externe Abhängigkeiten hat.
### GitHub Copilot CLI's eigene Sandbox
Copilot CLI wird seit Juni 2026 mit einer lokalen Sandbox ausgeliefert, im
Standard-Seat enthalten. Sie führt Shell-Befehle über Microsoft MXC mit
eingeschränktem Dateisystem-, Netzwerk- und Systemzugriff aus, auf macOS, Linux und Windows.
`/sandbox enable` schaltet sie ein.
Wenn das für dich ausreicht, nutze es. Es kostet nichts extra und läuft auf Windows,
was cplt nicht tut.
Zwei Dinge tut es nicht.
Die Richtlinie liegt beim Administrator, nicht beim Repository. Unternehmen setzen
Sandbox-Richtlinien über Intune oder ein anderes MDM. Nichts liegt neben dem Code,
sodass eine Regel, die für ein Repository wichtig ist, nicht zu einem Contributor,
zur CI oder zu einem Laptop folgen kann, den das MDM nicht verwaltet. In cplt ist die
Richtlinie `.cplt.toml` im Repository. Reviewer sehen Änderungen daran im Pull
Request, und die Datei kann die eigene Konfiguration eines Entwicklers verschärfen, aber nie
lockern.
Es beschränkt den Prozess, nicht das, was der Prozess mit den Anmeldedaten tut, die er
hält. Die `/sandbox`-Tabs decken das Dateisystem, das Netzwerk und System-
fähigkeiten ab, und innerhalb eines Git-Repositories erhält der Agent standardmäßig Lese- und Schreibzugriff
auf `.git`. Ein sandboxed Agent hat weiterhin dein `gh`-Token und deinen
Push-Zugriff. Einen Branch pushen, einen Pull Request mergen und ein
Repository löschen sind alles wohlgeformte API-Aufrufe von einem autorisierten Client, und eine
Dateisystem- oder Netzwerkregel hat dazu keine Meinung. cplt umhüllt stattdessen `git` und
`gh`. Der Agent committet, brancht und rebased frei. `gh pr merge`,
`gh repo delete` und `gh release create` sind standardmäßig blockiert. Ebenso
`git push` auf `main`/`master`; Feature-Branch-Pushes funktionieren weiterhin, weil
`protect_default_branch_only` an ist. Setze es auf `false`, um jeden Push zu blockieren, oder
`git_guard.mode = "warn"`, um nur zu warnen.
Beides laufen zu lassen ist vernünftig. MXC beschränkt den Prozess. Die Guards entscheiden, was
der Agent mit den Anmeldedaten tun darf, die er hält.
### Ehrliche Lücken
- macOS hat heute die stärkste Durchsetzung auf Dateiebene. Linux-Abdeckung verbessert sich, ist aber nicht identisch.
- cplt bietet noch keine einfachen Richtlinien-Presets für read-only / workspace-write / full-access.
- Wenn du vollständige Container-Isolation willst, versucht cplt nicht, Docker zu ersetzen.
## Installation
### Homebrew (empfohlen)```bash
brew install navikt/tap/cplt
mise use -g 'github:navikt/cplt@'
mise wählt das richtige Release-Asset für deine Plattform und verifiziert dessen Build-Provenienz-Attestierung.
Pinne die Version. Unsere Versionsstrings sind nicht vergleichbares Semver — sie enthalten führende Nullen und zwei Bindestriche — daher kann `mise latest` eine ältere Version als die neueste auflösen ([navikt/copilot#818](https://github.com/navikt/copilot/issues/818)).
### apt (Debian/Ubuntu, empfohlen unter Linux)
[navikt/apt](https://navikt.github.io/apt/) ist ein signiertes Archiv, das über GitHub Pages bereitgestellt wird und cplt und nav-pilot für amd64 und arm64 enthält:```bash
curl -fsSL https://navikt.github.io/apt/keyring/navikt-archive-keyring.gpg \
| sudo tee /usr/share/keyrings/navikt-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/navikt-archive-keyring.gpg] https://navikt.github.io/apt stable main" \
| sudo tee /etc/apt/sources.list.d/navikt.list
sudo apt update && sudo apt install cplt
Es ist ein reines apt-Repository, das unsere Releases spiegelt, kein Distributionspaket
mit eigenem Maintainer. Sein Publish-Job läuft stündlich und zieht das neueste .deb
aus dem jeweils neuesten Release jedes Tools, sodass ein vor Minuten erstelltes Release bis zu einer
Stunde braucht, um auf diesem Weg installierbar zu werden.
Das Paket legt die Binärdatei unter /usr/bin/cplt ab, und Upgrades laufen
von da an über sudo apt upgrade. cplt update weigert sich, eine apt-Installation anzufassen,
und verweist stattdessen auf sudo apt upgrade: Das Ersetzen der Binärdatei hinter dem Rücken von dpkg
würde durch den nächsten apt-Lauf rückgängig gemacht.
Ohne das Archiv ist dasselbe .deb ein Release-Asset:```bash
arch=$(dpkg --print-architecture) # amd64 or arm64
gh release download --repo navikt/cplt --pattern "${arch}.deb"
sudo apt install ./cplt_"${arch}".deb
### curl | bash
Für Distributionen, die keine Debian-Derivate sind, und für CI:```bash
curl -fsSL https://raw.githubusercontent.com/navikt/cplt/main/install.sh | bash
Optionen:```bash
curl -fsSL ... | bash -s -- --version 2026.05.05-174753-75bae5b
curl -fsSL ... | bash -s -- --dir ~/.local/bin
curl -fsSL ... | bash -s -- --no-brew
### Von Releases herunterladen
Holen Sie sich den neuesten Build für Ihre Plattform von [GitHub Releases](https://github.com/navikt/cplt/releases/latest):```bash
# macOS, Apple Silicon (M1/M2/M3/M4)
curl -fsSL https://github.com/navikt/cplt/releases/latest/download/cplt-aarch64-apple-darwin.tar.gz | tar xz
sudo mv cplt /usr/local/bin/
# macOS, Intel
curl -fsSL https://github.com/navikt/cplt/releases/latest/download/cplt-x86_64-apple-darwin.tar.gz | tar xz
sudo mv cplt /usr/local/bin/
# Linux, x86_64
curl -fsSL https://github.com/navikt/cplt/releases/latest/download/cplt-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv cplt /usr/local/bin/
# Linux, ARM64
curl -fsSL https://github.com/navikt/cplt/releases/latest/download/cplt-aarch64-unknown-linux-gnu.tar.gz | tar xz
sudo mv cplt /usr/local/bin/
Jede Release-Binärdatei enthält eine Build-Provenienz-Attestierung. Verifiziere sie:```bash gh attestation verify cplt -o navikt
### Aus dem Quellcode erstellen```bash
git clone https://github.com/navikt/cplt.git && cd cplt
cargo build --release
sudo cp target/release/cplt /usr/local/bin/
Oder mit mise:```bash mise run install
`mise run install` und manuelle Builds legen cplt in `/usr/local/bin/cplt` ab. Wenn du zusätzlich den Homebrew-Build unter `/opt/homebrew/bin/cplt` hast, setze `/usr/local/bin` an den Anfang von `PATH`, damit dein Entwicklungs-Build gewinnt:```bash
# Check which cplt is active
which cplt
# If it shows /opt/homebrew/bin/cplt, reorder your PATH:
export PATH="/usr/local/bin:$PATH"
Oder führen Sie einfach /usr/local/bin/cplt explizit aus und umgehen Sie die PATH-Auflösung vollständig.
cplt hat kein Windows-Sandbox-Backend. Die Durchsetzung erfolgt über Apple Seatbelt unter macOS und Landlock LSM unter Linux, sodass es nichts gibt, was nativ unter Windows ausgeführt werden könnte. Der unterstützte Weg ist WSL2, wo cplt eine gewöhnliche Linux-Installation ist und die Sandbox kernel-erzwungen wird. Jeder Microsoft-Kernel-Zweig baut CONFIG_SECURITY_LANDLOCK=y und listet landlock an erster Stelle in CONFIG_LSM (config-wsl), ausgeliefert seit Kernel 5.15.57.1, und die Standard-Kernel-Befehlszeile von WSL setzt keine lsm=-Überschreibung.
In PowerShell, einmalig:```powershell wsl --install # WSL2 + the default distro (now Ubuntu 26.04 LTS), then reboot wsl --install -d Ubuntu-24.04 # ...or pin an older release wsl --update # keep the Microsoft kernel current, see the ABI note below
Everything unten läuft **innerhalb der Distribution** (`wsl` oder das Ubuntu-Profil im Windows Terminal), nicht in PowerShell:```bash
# 1. Node. Copilot CLI requires Node 22+
# Ubuntu 26.04 ships 22.x, so apt is enough:
sudo apt update && sudo apt install -y nodejs npm
# Ubuntu 24.04 ships Node 18, too old. Use nvm, fnm, or NodeSource there instead.
# 2. GitHub CLI, and log in. Ubuntu's universe package works but lags
# (2.45 on 24.04); add GitHub's apt repo if you want a current gh:
# https://github.com/cli/cli/blob/trunk/docs/install_linux.md
sudo apt install -y gh
gh auth login
# 3. The agent, installed in the distro, never on the Windows side
npm install -g @github/copilot
# 4. cplt, from the apt archive. The default distro is Ubuntu, so this is
# the same route as on any other Debian derivative.
curl -fsSL https://navikt.github.io/apt/keyring/navikt-archive-keyring.gpg \
| sudo tee /usr/share/keyrings/navikt-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/navikt-archive-keyring.gpg] https://navikt.github.io/apt stable main" \
| sudo tee /etc/apt/sources.list.d/navikt.list
sudo apt update && sudo apt install cplt
# 5. Check the result
cplt doctor
Copilot CLI nicht auf der Windows-Seite installieren. Bei aktiviertem Interop (dem Standard) wird der Windows-PATH an den der Distribution angehängt, sodass ein Windows-seitiges npm install -g @github/copilot innerhalb der Distribution als /mnt/c/Users/<user>/AppData/Roaming/npm/copilot auftaucht. Das ist eine Windows-Installation, die über Interop erreicht wird. Sie kann nicht in der Linux-Sandbox ausgeführt werden, und der npm-Shim führt ein node aus, das die Distribution nicht haben wird, es sei denn, du hast dort ebenfalls eines installiert. Das Symptom war früher ein unzusammenhängender Laufzeit-Extraktionsfehler. cplt benennt jetzt die Ursache, wenn es einen Agenten unter /mnt/<drive>/ auflöst und unter WSL läuft, und cplt doctor meldet es als fehlgeschlagenen Check statt als bestandenen (#188). WSL wird aus kernel-eigenem Zustand erkannt, entweder /run/WSL oder der Kernelname in /proc/sys/kernel/osrelease und /proc/version, nicht aus WSL_DISTRO_NAME, das unter sudo und in systemd-Units fehlt und das jeder Prozess setzen kann. Auf einer reinen Linux-Maschine wird /mnt/c in Ruhe gelassen. Dort ist es ein gewöhnlicher Einhängepunkt.
Dieser Check hat zwei Grenzen, beide bewusst gewählt. Er schlägt auf dem Standard-Automount-Root an, sodass eine verschobene Installation ([automount] root in /etc/wsl.conf) die Windows-seitige Installation nicht erkennt und du den alten, weniger hilfreichen Fehler mit dem Pfad darin bekommst. Und das Abschalten von Interop verhindert zwar, dass der Windows-PATH durchsickert, hängt aber /mnt/c nicht aus.
Kernel und Landlock-ABI. Aktuelles WSL (2.7.x und später) liefert Linux 6.18, was Landlock-ABI 7 ergibt — alles, was cplt nutzt, außer dem Unix-Socket-connect()-Recht, das ABI 9 (Kernel 7.1) benötigt. Eine Installation, die noch auf der 6.6-Kernel-Linie läuft, bekommt ABI 3: Dateisystemregeln werden durchgesetzt, aber TCP-Port-Regeln (ABI 4), ioctl-Einschränkung (ABI 5) und Signal-/Abstract-Socket-Scoping (ABI 6) sind nicht verfügbar, und die Netzwerkfilterung fällt auf den CONNECT-Proxy zurück. wsl --update bringt dich voran. cplt doctor gibt die Kernelversion und das gefundene ABI aus, was der Check ist, der auf deiner Maschine zählt.
Landlock nicht in
.wslconfigdeaktivieren. Eine[wsl2] kernelCommandLinemit einerlsm=-Liste, dielandlockauslässt, oder ein benutzerdefinierter[wsl2] kernel=, der ohneCONFIG_SECURITY_LANDLOCKgebaut wurde, entfernt die Kernel-Durchsetzung, auf die cplt angewiesen ist, undcplt doctorwird Landlock als nicht verfügbar melden.
Das Projekt im Linux-Dateisystem halten. Arbeite in ~/src/... innerhalb der Distribution statt in /mnt/c/Users/.... Microsofts eigene Empfehlung ist, dass dateisystemübergreifender Zugriff deutlich langsamer ist, und /mnt/c wird ab WSL 2.9.x standardmäßig über 9p bereitgestellt (virtiofs ist Opt-in über [wsl2] virtiofs=true). Wichtiger noch: Wir haben nicht verifiziert, wie Landlock Regeln auf diesem Einhängepunkt durchsetzt. Der Kernel dokumentiert keine Ausnahme für netzwerk- oder FUSE-gestützte Dateisysteme, nur Pipes, Sockets und nsfs, und Landlocks eigene Testsuite übt 9p und FUSE aus, sodass wir erwarten, dass es funktioniert. Niemand hier hat es bestätigt. Behandle ein Projekt unter /mnt/c als ungeprüft statt als unterstützt.
Bubblewrap. Ubuntu 23.10+ blockiert unprivilegierte User-Namespaces über kernel.apparmor_restrict_unprivileged_userns, was bwrap bricht. Dieses Sysctl stammt aus einem Ubuntu-Kernel-Patch, der im Microsoft-Kernel fehlt, sodass die optionale Bubblewrap-Schicht unter Ubuntu-unter-WSL2 voraussichtlich funktioniert. Das ist eine Schlussfolgerung aus dem Kernel-Quellcode, nicht etwas, das wir ausgeführt haben. Falls bwrap dort fehlschlägt, sag es bitte in #189. cplts eigener seccomp-Filter ist ein einfaches PR_SET_SECCOMP-BPF-Programm, das sich auf den Filter stapelt, den WSL in jedem Prozess installiert.
Noch nicht auf einer echten WSL2-Installation verifiziert. Aus dem Quellcode verifiziert: Landlock ist einkompiliert und steht an erster Stelle in
CONFIG_LSMim Microsoft-Kernel; die/mnt/<drive>/-Erkennung, die verwendeten WSL-Signale und ihr Fehlertext; dasscplt doctorbei einem solchen Agenten fehlschlägt und Kernel + Landlock-ABI ausgibt; die 5.13+/6.7+-Anforderungen; und dassinstall.shdas Linux-Release-Binary installiert. Noch von niemandem hier verifiziert: wie sich Landlock auf/mnt/cverhält, ob Bubblewrap unter WSL2 funktioniert, die genauen Paketversionen, die dein Distributions-Release mitbringt, und die obige Sequenz von Anfang bis Ende. Falls du es ausführst, berichte bitte, was tatsächlich passiert ist, in #189.
Standardmäßig bekommst du die Sandbox, indem du cplt eintippst. Damit auch einfaches copilot sandboxed läuft:```bash
cplt --shell-install
Das erkennt deine Shell, hängt den Alias an deine rc-Datei an und gibt aus, was es getan hat. Führe es so oft aus, wie du möchtest, es werden keine Duplikate hinzugefügt.
`--agent` legt fest, welcher Befehl den Alias erhält, und jeder Agent, den cplt starten kann, ist verfügbar:```bash
cplt --shell-install --agent opencode # 'opencode' runs sandboxed
cplt --shell-install --agent claude # and 'claude', alongside the others
Jede Installation ergänzt deine rc-Datei, anstatt das Vorhandene zu ersetzen, sodass du so viele Agents sandboxen kannst, wie du verwendest. Ohne --agent erhältst du copilot, was das Flag schon immer installiert hat.
| Shell | Geänderte Datei | Was hinzugefügt wird (für --agent opencode) |
|---|---|---|
| zsh (macOS-Standard) | ~/.zshrc | eval "$(cplt --shell-setup --agent opencode)" |
| bash | ~/.bashrc | eval "$(cplt --shell-setup --agent opencode)" |
| fish | ~/.config/fish/conf.d/cplt.fish | alias opencode 'cplt --agent opencode' |
--agent antigravity installiert Aliase für sowohl antigravity als auch agy, da beide Namen denselben Agent starten.
Starte deine Shell neu oder source die Datei, um sie zu aktivieren.
Es gibt keinen Alias für --agent shell: Es gibt keine shell-Binärdatei zum Überschreiben. Gib cplt --agent shell für eine gesandboxte Shell ein, oder cplt exec -- <command> für einen einzelnen Befehl.
Wenn du --shell-install lieber nicht verwenden möchtest, füge die Zeile selbst hinzu:```bash
eval "$(cplt --shell-setup --agent opencode)"
alias opencode 'cplt --agent opencode'
Dasselbe Muster, das mise, direnv und starship verwenden.
</details>
**Warum jeder Alias seinen Agenten benennt.** `alias opencode=cplt` würde nicht das tun, wonach es aussieht. Ein einfaches `cplt` wählt seinen Agenten aus `--agent`, dann aus der Konfigurationsdatei und schließlich aus dem, was es in PATH findet – und die PATH-Erkennung bevorzugt `copilot`. Wenn du `opencode` eingibst, würde stattdessen Copilot in einer Sandbox ausgeführt, ohne dass auf dem Bildschirm etwas darauf hinweist. Der Alias übergibt `--agent`, damit der Befehl, den du eingibst, auch der Agent ist, den du bekommst.
**Warum ein Alias statt eines Symlinks?** cplt und Copilot CLI installieren sich in dasselbe Homebrew-bin-Verzeichnis (`/opt/homebrew/bin/`), und dort kann nur eine Datei namens `copilot` liegen, sodass ein Symlink einen Konflikt verursachen würde. Ein Alias umgeht das. Die echte `copilot`-Binärdatei bleibt in PATH, wo cplt sie finden und umschließen kann, und der Alias leitet deinen Befehl um.
> **Hinweis:** cplt weigert sich zu verschachteln. Wenn es erkennt, dass es bereits in einer Sandbox läuft (über die Umgebungsvariable `__CPLT_WRAPPED`), startet es nicht erneut. Schreibgeschützte Unterbefehle wie `--print-profile` und `cplt doctor` funktionieren weiterhin innerhalb einer bestehenden Sandbox.
## Verwendung```
cplt [OPTIONS] [-- <AGENT_ARGS>...]
Alles nach -- geht direkt an den Agentenprozess (copilot, opencode, gemini, antigravity, pi, claude, goose, dsh oder shell).
Ein Preset setzt eine Baseline für die fünf Haupt-Sandbox-Toggles mit einem einzigen Flag statt einer Liste von ihnen. Einzelne Flags gewinnen weiterhin gegen das Preset, also tut --preset permissive --no-allow-tmp-exec genau das, was es sagt. Auch als [sandbox] preset = "..." in der Config setzbar.
Vollständige Preset-Matrix und Auflösungsreihenfolge: docs/configuration.md.
Das Projektverzeichnis ist der beschreibbare Arbeitsbereich, plus eine schmale Allowlist, die für Auth, Runtime und Tooling benötigt wird (siehe Tabelle oben). Der Kernel blockiert alles andere, SSH-Keys und Cloud-Credentials eingeschlossen.
cplt bereinigt standardmäßig die Kindumgebung. Nur sichere Variablen werden durchgelassen, und Cloud-Credentials, Datenbank-URLs und Package-Tokens werden entfernt. Es injiziert außerdem Härtungsvariablen, die npm/yarn/pnpm-Lifecycle-Skripte blockieren (Postinstall-Hooks, der Supply-Chain-Angriffsvektor Nummer eins), Git-Commit- und Tag-Signierung deaktivieren (da ~/.ssh und ~/.gnupg innerhalb der Sandbox unerreichbar sind) und Developer-Tooling-Telemetrie abwählen (DO_NOT_TRACK=1, NEXT_TELEMETRY_DISABLED=1, TURBO_TELEMETRY_DISABLED=1, CHECKPOINT_DISABLE=1 und andere).
Was durchgelassen wird:
Präfix-Allowlist mit Secret-Suffix-Schutz. Eine Variable, die einem erlaubten Präfix wie COPILOT_* oder YARN_* entspricht, wird trotzdem verworfen, wenn sie auf ein secret-tragendes Suffix endet: _TOKEN, _AUTH, _SECRET, _SECRET_KEY, _KEY, _PASSWORD oder _CREDENTIALS. Also wird COPILOT_DEBUG durchgelassen und COPILOT_API_KEY nicht.
Immer blockiert: AWS_*, AZURE_*, NPM_TOKEN, DATABASE_URL, VAULT_TOKEN, SSH_AUTH_SOCK, Docker-Variablen, CI-Tokens und alles, was nicht in der Allowlist steht.
| Flag | Was es tut |
|---|---|
--pass-env <VAR> | Eine Umgebungsvariable an den Agenten durchreichen. Wiederholbar |
--inherit-env | ⚠️ Gefährlich. Die vollständige Elternumgebung erben. Entfernt nur , , , . Nur zum Debuggen |
cplt entdeckt installierte Tools automatisch und schreibt passende Sandbox-Regeln. Im Allgemeinen erhalten nur Verzeichnisse, die auf der Festplatte existieren, Regeln, sodass es keine Phantom-Pfade gibt. Unter macOS werden beschreibbare App-Verzeichnisse bei Entdeckung einbezogen, auch wenn sie noch nicht existieren, sodass sie bei der ersten Verwendung erstellt werden können. Linux kann einen Schreibzugriff auf einen nicht existierenden Pfad nicht erlauben, also muss die Erstellung dort außerhalb der Sandbox erfolgen.
Führe cplt doctor aus, um zu sehen, ob cplt hier für deinen Agenten funktionieren wird, und cplt doctor --verbose für alles, was es auf deinem Rechner entdeckt hat.
Diese werden in die eigenen Session-Flags des Agenten übersetzt, sodass du kein ---Trennzeichen benötigst.
--continue und --resume werden auch für OpenCode, Antigravity und Claude Code gemappt:
¹ Weder OpenCode noch Antigravity hat einen interaktiven Session-Picker, also bedeutet ein bloßes --resume „letzte Sitzung fortsetzen". Claude Code hat einen, also wird es direkt übernommen.
--remote und --name sind nur für Copilot. Pi und Shell-Modus erhalten überhaupt keine Übersetzung, also werden alle vier Flags für sie verworfen. Auto-Resume ist ein separater Mechanismus: Wenn du cplt ohne Pass-Through-Args und ohne Session-Flags aufrufst, hängt es --resume für dich an, und das gilt nur für Copilot.
Kombiniere sie mit Sandbox-Flags und ---Pass-Through-Args:```bash
cplt --resume=my-task # resume by name
cplt --remote --name my-task -- -p "fix tests" # remote + named + prompt
### Agents
Wähle einen mit `--agent <name>` aus oder mache ihn mit `cplt config set sandbox.agent <name>` zum Standard. Copilot, OpenCode und Antigravity werden automatisch aus `PATH` in dieser Reihenfolge erkannt, wenn du keinen benennst.
| Agent | `--agent`-Wert | Automatisch erkannt | Auth |
| --- | --- | --- | --- |
| GitHub Copilot CLI | `copilot` | ja, Priorität 1 | GitHub-Token, aus dem Keychain oder `gh` |
| [OpenCode](https://opencode.ai/) | `opencode` | ja, Priorität 2 | Copilot-Abonnement via `/connect` oder `--pass-env ANTHROPIC_API_KEY` |
| [Antigravity CLI](https://github.com/google-antigravity/antigravity-cli) | `antigravity`, Aliase `agy` und `agi` | ja, Priorität 3 | Google OAuth im Browser |
| [Pi](https://github.com/earendil-works/pi) | `pi` | nein | `--pass-env ANTHROPIC_API_KEY` und Verwandte |
| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | `claude`, Aliase `cc` und `claude-code` | nein | Abonnement-OAuth in `~/.claude` oder dem Keychain, `CLAUDE_CODE_OAUTH_TOKEN` (entfernt die Keychain-Berechtigung) oder `--pass-env ANTHROPIC_API_KEY` |
| [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | `dsh`, Aliase `deepseek` und `deepseek-harness` | nein | `--pass-env DEEPSEEK_API_KEY` oder `$DSH_HOME/.env` (`~/.dsh/.env`) |
| Deine Shell | `shell` | nein | keine |
- **Pi, Claude Code, goose und DeepSeek Harness werden nie automatisch erkannt.** `pi` und `dsh` sind generische Binärnamen, die mit etwas anderem auf deinem Rechner kollidieren könnten, und Claude Code muss bewusst ausgewählt werden.
- **API-Schlüssel von Drittanbietern sind Opt-in.** `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN` und die Bedrock/Vertex-Routing-Variablen (`CLAUDE_CODE_USE_BEDROCK`, `AWS_BEARER_TOKEN_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `ANTHROPIC_VERTEX_PROJECT_ID`, `GOOGLE_CLOUD_PROJECT`) werden nie durchgereicht, es sei denn, du benennst sie mit `--pass-env`.
- **Abonnement-Auth benötigt keine Umgebungsvariable.** Der `/connect`-Device-Flow von OpenCode speichert sein Token in `~/.local/share/opencode/auth.json`, und das OAuth-Token von Claude Code liegt in `~/.claude` (`.credentials.json` unter Linux) oder im macOS-Keychain. Beide sind innerhalb der Sandbox erreichbar, sodass cplt bei keinem von beiden wegen eines fehlenden API-Schlüssels nervt.
- **OAuth-Browser-Flows benötigen `--allow-browser`**, wenn eine Anmeldeaufforderung erscheint. Das betrifft Antigravity; jeder andere Agent hier verwendet einen Device-Flow, der einen Code und eine URL ausgibt und keinen Browser benötigt. Das Flag erlaubt dem Agenten, jede Anwendung außerhalb der Sandbox zu starten, und kann nicht auf URLs eingeschränkt werden, also aktiviere es für die Anmeldung und deaktiviere es danach wieder — siehe die [Flag-Tabelle](#sandbox-toggles) und [docs/security.md](https://github.com/navikt/cplt/blob/main/docs/security.md#--allow-browser-is-a-sandbox-escape-and-cannot-be-scoped).
- **Claude Code Auto-Update ist deaktiviert** mit `DISABLE_AUTOUPDATER=1`. Claude Code hat kein `--no-auto-update`-Flag, Selbstaktualisierung innerhalb der Sandbox ist ein Persistenzvektor, und sie würde ohnehin an schreibgeschützten Installationspfaden scheitern.
- **`CLAUDE_CONFIG_DIR` wird berücksichtigt.** Wenn es gesetzt ist, gewährt cplt dieses Verzeichnis anstelle von `~/.claude` und reicht die Variable durch, sodass ein verschobenes Konfigurationsstammverzeichnis weiterhin funktioniert.
- OpenCode ist [ein offiziell unterstützter Copilot-Client](https://github.blog/changelog/2026-01-16-github-copilot-now-supports-opencode/), sodass dein bestehendes Copilot-Abonnement mit `/connect` innerhalb von OpenCode funktioniert.
Konfigurationsverzeichnisse pro Agent, Keychain-Nutzung, Exec-Berechtigungen und Env-Isolation findest du in [SECURITY.md](https://github.com/navikt/cplt/blob/main/SECURITY.md#supported-agents).
### goose-Unterstützung
cplt kann [goose](https://github.com/aaif-goose/goose) sandboxen, den Open-Source-KI-Agenten (Binärdatei `goose`). Verifiziert gegen goose 1.48.0.```bash
# Run goose (must be explicit — not auto-detected)
cplt --agent goose
# goose is provider-agnostic — pass your provider's API key
cplt --agent goose --pass-env ANTHROPIC_API_KEY
cplt --agent goose --pass-env OPENAI_API_KEY
# Skip the keyring entirely: keep the key in the environment
GOOSE_DISABLE_KEYRING=1 cplt --agent goose --pass-env OPENAI_API_KEY --pass-env GOOSE_DISABLE_KEYRING
# Set goose as your default agent
cplt config set sandbox.agent goose
Sicherheitshinweise für goose:
--agent goose oder sandbox.agent = "goose" in der Konfiguration setzenANTHROPIC_API_KEY, OPENAI_API_KEY, AZURE_OPENAI_API_KEY, GOOGLE_API_KEY, DATABRICKS_HOST/DATABRICKS_TOKEN, GROQ_API_KEY, OPENROUTER_API_KEY, XAI_API_KEY, AWS_BEARER_TOKEN_BEDROCK) werden als Auth-Hinweise erkannt und müssen über --pass-env übergeben werden. goose liest , nicht . Jeder Provider außerhalb dieser Teilmenge funktioniert trotzdem: Benennen Sie seine Variable mit cplt kann DeepSeek Harness (Binary dsh) sandboxen, das plugin-orientierte Agent-Harness von DeepSeek. Upstream liefert es als Developer Preview aus, und seine eigene SAFETY.md besagt, dass man sich nicht auf seine Kontrollen als einzige Grenze verlassen sollte — genau der Fall, für den cplt existiert.```bash
cplt --agent dsh
cplt --agent dsh --pass-env DEEPSEEK_API_KEY
cplt config set sandbox.agent dsh
**Sicherheitshinweise für DSH:**
- **Nicht automatisch erkannt**: Wähle es mit `--agent dsh` (Aliase `deepseek`, `deepseek-harness`) aus oder setze `sandbox.agent = "dsh"`. `dsh` ist ein kurzer, generischer Befehlsname, der auf deinem System zu etwas anderem gehören könnte
- **Schalte DSHs eigene Sandbox innerhalb von cplt aus**: DSH verpackt jeden Shell- und Datei-Tool-Aufruf in seine eigene Prozess-Sandbox — Seatbelt unter macOS, bwrap oder Landlock unter Linux. Keine davon lässt sich in cplt verschachteln. macOS unterstützt keine verschachtelten `sandbox-exec`-Aufrufe (dieselbe Einschränkung, die cplt dazu bringt, Gradles innere Sandbox zu deaktivieren, siehe [Limitations](#limitations)), und bwrap baut seinen Namespace mit `unshare` auf, was cplts seccomp-Filter verweigert. cplt ist in jedem Fall die durchsetzende Grenze, also wähle für sandboxed Sessions das von DSH mitgelieferte Berechtigungs-Preset `danger-full-access`. Lass den inneren Runner aktiviert und Tool-Aufrufe schlagen mit einem Sandbox-Runner-Fehler statt einem Task-Fehler fehl
- **Ein Home-Root, und cplt folgt der Überschreibung**: DSH speichert Sessions, Einstellungen, Cache und Profile unter `$DSH_HOME` (standardmäßig `~/.dsh`). `DSH_HOME` steht auf der Env-Allowlist, sodass das Kind denselben Root auflöst, den cplt gewährt. Ein Wert, der auf ein System-Root oder dein Home-Verzeichnis zeigt, wird vor dem Start abgelehnt — dasselbe Veto, das `CLAUDE_CONFIG_DIR` durchläuft
- **Host-Persistenz-Guard**: `$DSH_HOME/cordis.patch.yml`, das Home-Level-Overlay, das der Loader beim Boot liest, ist schreibgeschützt. `$DSH_HOME/profiles/` bleibt beschreibbar, weil DSH bei jedem Boot den Include-Root von `cordis.yml` jedes Profils neu schreibt, sodass ein profilweises `cordis.patch.yml` und installierte Plugins ein dokumentiertes Residuum sind — nimm Profil- und `dsh plugin`-Änderungen außerhalb von cplt vor und starte `dsh` immer über cplt, damit alles Eingeschleuste trotzdem sandboxed läuft
- **Standard-Domains**: nur `deepseek.com`. Der mitgelieferte `dsh-llm-deepseek`-Adapter verwendet standardmäßig `https://api.deepseek.com`. Richte `DEEPSEEK_BASE_URL` auf ein Gateway und du musst die Domain dieses Gateways über `allowed_domains` hinzufügen
- **Auth**: Übergib den Key mit `--pass-env DEEPSEEK_API_KEY` oder bewahre ihn in `$DSH_HOME/.env` auf. Ein Key, der über DSHs eigene Models-UI gespeichert wird, landet in `$DSH_HOME/.credentials.yaml`, innerhalb desselben beschreibbaren Roots. Der macOS Keychain ist gesperrt, also benötigt `git push` über HTTPS das Token von `gh` in `hosts.yml` oder `--pass-env GH_TOKEN`
### Shell-Modus
Führe eine einfache sandboxed Shell ohne KI-Agent und mit denselben Einschränkungen aus. Praktisch zum Testen von Build-Tools, zum Debuggen von Sandbox-Problemen oder einfach zum sorgfältigen manuellen Arbeiten.```bash
# Interactive sandboxed shell (uses $SHELL: fish, zsh, bash)
cplt --agent shell
# Inspect what's allowed without entering the shell
cplt --agent shell --print-profile
Die gleichen Deny-by-default-Regeln gelten: Dateisystem-Isolierung, Netzwerkbeschränkungen, Env-Bereinigung. Shell-Konfigurationsverzeichnisse (fish-Variablen und -History, zsh-History) bleiben beschreibbar.
Für einen einzelnen Befehl ist cplt exec sauberer als cplt --agent shell -- -c 'cmd'.
Führe einen beliebigen Befehl innerhalb der Sandbox aus, ohne einen Agenten zu starten. Kein Startbanner, keine Bestätigungsaufforderung, daher eignet es sich für Skripte, Pipes und Shell-Aliase.```bash
cplt exec -- npm install cplt exec -- make build cplt exec -- go test ./...
cplt exec -c "npm install && npm test"
cplt exec --allow-lifecycle-scripts -- npm install cplt exec --project-dir /path/to/repo -- make build cplt exec --with-proxy -- curl https://example.com
alias npm="cplt exec -- npm" alias node="cplt exec -- node" alias python="cplt exec -- python"
Jedes Top-Level-`cplt`-Flag gilt: `--project-dir`, `--allow-read`, `--deny-path`, `--with-proxy`, `--pass-env` und der Rest. Füge `--no-quiet` hinzu, um die vollständige Zusammenfassung der Sandbox-Konfiguration zu sehen, bevor der Befehl ausgeführt wird.
### Beispiele```bash
# The common case: Copilot in the sandbox
cplt -- -p "fix the tests"
# Sessions
cplt --resume # pick one interactively
cplt --resume=my-refactor # by name
cplt --continue # most recent in this directory
cplt --remote --name my-task -- -p "fix tests" # named remote session
# Check the environment before the first run
cplt doctor
# Let Copilot read a shared library directory
cplt --allow-read ~/shared-libs -- -p "use shared-libs"
# Block a path you don't want Copilot to see
cplt --deny-path ~/.config/gh -- -p "refactor auth"
# Extra outbound port, e.g. an external API
cplt --allow-port 8443 -- -p "test the API"
# Localhost for MCP servers or dev servers
cplt --allow-localhost 3000 --allow-localhost 8080 -- -p "use the MCP server"
# All of localhost, needed by Next.js/Turbopack and Vite builds
cplt --allow-localhost-any -- -p "fix the build"
# Pass specific env vars through
cplt --pass-env MY_CUSTOM_VAR --pass-env ANOTHER_VAR -- -p "run with custom config"
# Inherit the full environment (dangerous, debugging only)
cplt --inherit-env -- -p "debug the build"
# Network
cplt --no-proxy -- -p "fix the tests" # proxy is on by default
cplt --blocked-domains ./blocked-domains.txt -- -p "refactor"
cplt --allow-private-domain intern.nav.no -- -p "use mcp-onboarding"
# Non-interactive / CI (skip the confirmation prompt)
cplt --yes -- -p "fix the tests"
# Inspect and debug the sandbox itself
cplt --print-profile
cplt --show-denials -- -p "fix the tests"
Die Konfiguration erfolgt auf zwei Ebenen: global, für Entwicklerpräferenzen, und pro Repository, für Teamrichtlinien.```bash
cplt settings
cplt config set sandbox.quiet true cplt config set proxy.blocked_domains "~/.config/cplt/blocked-domains.txt" cplt config set git_guard.mode warn # observe pushes instead of blocking them cplt config set gh_guard.enabled false # opt out of the gh guard entirely
cplt config set --repo sandbox.allow_jvm_attach true cplt config set --repo deny.paths "~/secrets"
cplt config show # effective config (file + defaults) cplt config explain # every key with its description
`cplt settings` ist der interaktive Editor mit den Ansichten Effective, Global und Repository, Suche, gestaffelten Änderungen und einer expliziten Bestätigung, bevor sicherheitsrelevante Einstellungen gespeichert werden. `cplt config` bleibt die stabile nicht-interaktive Schnittstelle für Skripte und CI. Repository-Vorschläge werden weiterhin separat mit `cplt trust` committet und genehmigt. Der Editor committet oder genehmigt sie niemals automatisch.
Die Rangfolge verläuft über CLI-Flags, dann die globale Konfigurationsdatei unter `~/.config/cplt/config.toml`, dann integrierte Standardwerte. Die Konfiguration pro Repository in `.cplt.toml` ist eine separate Ebene und keine Sprosse auf dieser Leiter: `[deny]` verschärft bedingungslos, und genehmigte Berechtigungen sind ausschließlich additiv, sodass ein Repository eine Funktion aktivieren, aber niemals etwas deaktivieren kann, das durch ein CLI-Flag oder die globale Konfiguration festgelegt wurde.
Eine `.cplt.toml` im Repository-Stammverzeichnis enthält die Team-Richtlinie:```toml
[deny] # Applied automatically, no opt-in needed
paths = ["~/secrets", "~/.vault-token"]
env = ["VAULT_TOKEN", "DATABASE_URL"]
[propose] # Requires developer approval (cplt trust accept)
gh_guard = true
git_push_prevention = true
allow_jvm_attach = true
allow_docker = true
[propose.allow]
ports = [5432]
localhost = [3000]
socket = ["/var/run/docker.sock"]
cplt liest sie aus git HEAD, sodass der Agent seine eigene Richtlinie nicht mitten in der Sitzung manipulieren kann und Trust-Genehmigungen an den Inhalt der Datei gebunden sind. Eine nicht committete .cplt.toml gewährt nichts, bis sie committet wird, obwohl ihre [deny]-Schlüssel weiterhin gelten. In CI und Skripten, wo niemand eine Eingabeaufforderung beantworten kann, genehmigt --accept-repo-config die Vorschläge der committeten Datei für diesen einen Lauf, ohne einen Trust zu persistieren. cplt init schreibt eine für dich, indem es die Tooling-Landschaft des Projekts erkennt:```bash
cplt init # preview detected permissions
cplt init --write # write .cplt.toml to disk
cplt init --quiet # output only TOML (pipe-friendly)
cplt init --global # generate a personal ~/.config/cplt/config.toml
Es kennt JVM (Gradle/Maven), Node.js, Docker, Python, Rust, Go, Playwright, Spring Boot, Ktor, TestContainers, Next.js, Vite, Flyway, Cypress und Umgebungsgeheimnisse aus `.env.example`. Gefährliche Berechtigungen kommen mit einer Risikowarnung versehen aus dem Generator heraus. `--global` betrachtet stattdessen Dinge auf Maschinenebene: Playwright-Browser, GPG-Signierung, Registry-Anmeldedaten, alternative Agenten.
Einige Schlüssel sind nur global gültig und werden aus `.cplt.toml` abgelehnt, weil sie maschinenspezifisch oder eine lokale Präferenz sind: `sandbox.agent`, `sandbox.quiet`, `sandbox.yes`, `sandbox.validate`, `sandbox.scratch_dir`, `sandbox.pass_env`, `sandbox.inherit_env`, `sandbox.allow_cache_exec`, `sandbox.allow_cache_exec_any`, `proxy.enabled`, `proxy.port`, `proxy.log_file`, `proxy.log_level`, `proxy.blocked_domains`, `proxy.allowed_domains` und jeder `[gh_guard]`- und `[git_guard]`-Schlüssel.
Vollständige Details, einschließlich des Vertrauensmodells, der Pfaderweiterungsregeln und der vollständigen Konfigurationsdateireferenz: [docs/configuration.md](https://github.com/navikt/cplt/blob/main/docs/configuration.md).
## Architektur```
┌──────────────────────────────────┐
│ cplt (Rust binary) │
│ ┌───────────┐ ┌─────────────┐ │
│ │ Policy │ │ CONNECT │ │
│ │ Generator │ │ Proxy │ │
│ └─────┬─────┘ │ (optional) │ │
│ │ └─────────────┘ │
│ ▼ │
│ ┌─────────────┬────────────┐ │
│ │ macOS │ Linux │ │
│ │ Seatbelt │ Landlock │ │
│ │ sandbox- │ + seccomp │ │
│ │ exec │ pre_exec │ │
│ └─────────────┴────────────┘ │
│ │ │
│ ▼ │
│ copilot (sandboxed) │
│ ├── All child processes │
│ ├── Cannot read ~/.ssh │
│ ├── Network port-restricted │
│ ├── SSH agent blocked │
│ └── Filesystem = primary ctrl │
└──────────────────────────────────┘
Das Sicherheitsmodell ist ein Dateisystem mit Deny-by-Default und Kernel-Durchsetzung. Unter macOS und unter Linux mit Kernel 6.7+ (Landlock ABI v4) ist das Netzwerk standardmäßig auf Port 443 beschränkt, mit --allow-port für zusätzliche Ports. Auf älteren Linux-Kernels übernimmt der CONNECT-Proxy diese Beschränkung, weshalb er standardmäßig aktiviert ist. SSH-Agent-Zugriff und ausgehende Verbindungen zu localhost werden unter macOS im Kernel blockiert. Unter Linux ist weder das eine noch das andere der Fall: Port-basierte Landlock-Regeln können localhost nicht von einem entfernten Host unterscheiden, und unix socket connect() wird von Landlock unterhalb von Kernel 7.1 nicht abgefangen, sodass – abgesehen von den Sockets, die bubblewrap maskiert – der zurückgehaltene SSH_AUTH_SOCK das Einzige ist, was zwischen dem Agenten und deinen geladenen Schlüsseln steht. Der Profilgenerator erkennt deine Umgebung (cplt doctor --verbose zeigt dieselben Probe-Ergebnisse) und erzeugt Regeln nur für Tool-Verzeichnisse, die tatsächlich auf der Festplatte existieren. Weniger Regeln, engere Sandbox.
sandbox-exec übergebenpre_exec (Kernel 5.13+, TCP-Port-Filterung ab 6.7+)Interna und Modulaufbau: docs/architecture.md. Bedrohungsmodell, Verteidigungsschichten und ehrliche Lücken: SECURITY.md.
Ein Binary, minimale Abhängigkeiten, keine Laufzeitdienste, keine Telemetrie. Drei Verteidigungsschichten mit klaren Grenzen dazwischen:
Wogegen cplt schützt:
.env-Dateien): kernel-blockiert.git/hooks ist unter macOS im Kernel schreibgeschützt. Unter Linux bleibt es mit Landlock und ohne Bubblewrap beschreibbar, und cplts eigenes parent-seitiges git läuft dann mit core.hooksPath=/dev/null, sodass es niemals einen platzierten Hook ausführt – ein git, das du selbst ausführst, jedoch schonPNPM_HOME, ~/.deno/bin, ~/.bun/bin): Schreibzugriff wird dort gewährt, damit pnpm add -g und Verwandte in der Sandbox funktionieren, sodass ein Agent ein Binary hinterlassen kann, das eine spätere Shell aus deinem PATH aufgreiftWogegen cplt nicht schützt:
sandbox.keychain_substitute kann die Gewährung aufgeben, wo ein Agent eine andere Anmeldedatenquelle hatUnsere Prioritäten, in dieser Reihenfolge: korrekt (jede Behauptung ist getestet, jeder Edge Case hat eine CVE- oder Forschungsreferenz), transparent (SECURITY.md verbirgt nichts), einfach (ein Binary, keine Konfiguration erforderlich, vernünftige Standardwerte) und nützlich (aus dem Weg gehen und den Agenten sicher arbeiten lassen).
Mehr: docs/security.md · SECURITY.md
Der Proxy ist standardmäßig aktiviert. Der gesamte ausgehende Datenverkehr von Copilot CLI, gh und curl läuft über einen localhost-CONNECT-Proxy via HTTP_PROXY/HTTPS_PROXY und NODE_USE_ENV_PROXY=1. Er lauscht auf einem vom Betriebssystem zugewiesenen ephemeren Port, sodass nichts kollidiert. Du erhältst Verbindungsprotokollierung in Echtzeit, Domain-Blockierung, Domain-Allowlisting, ein persistentes Audit-Log und dieselbe Port-Richtlinie, die die Sandbox durchsetzt (443 plus alles in allow.ports).```bash
cplt --proxy-forced -- -p "fix tests" # force all egress through the proxy
cplt --no-proxy -- -p "fix tests" # disable for one run
cplt --blocked-domains blocked-domains.txt -- -p "x" # block known-bad domains
cplt --allowed-domains allowed-domains.txt -- -p "x" # allowlist mode
cplt --default-allowlist -- -p "x" # fail-closed: only the agent's own domains
cplt --observe-domains -- -p "x" # record what the agent contacts, block nothing
cplt --proxy-upstream http://proxy.corp:8080 -- -p "x" # chain through a corporate proxy
`--observe-domains-out <FILE>` schreibt die beobachtete Menge, eine Domain pro Zeile, und
`--proxy-upstream-no-proxy <HOST>` listet Hosts auf, die direkt statt über
das Upstream erreicht werden sollen.```bash
cplt config set proxy.enabled false
cplt config set proxy.blocked_domains "~/.config/cplt/blocked-domains.txt"
cplt config set proxy.allowed_domains "~/.config/cplt/allowed-domains.txt"
cplt config set proxy.log_file "~/.config/cplt/proxy.log"
Der Proxy-erzwungene Modus ist Opt-in. Er beschränkt den Kernel-Egress auf den Proxy-Port, sodass ein direkt geöffneter Socket oder ein env -u HTTPS_PROXY nicht vorbeischlüpfen kann. Die Durchsetzung ist auf macOS vollständig, wo auf localhost:<proxy_port> festgelegt wird. Unter Linux blockiert er direktes TCP :443, und eine seccomp-Regel erlaubt nur SOCK_STREAM mit Protokoll 0 oder IPPROTO_TCP für AF_INET/AF_INET6, sodass auch UDP, Raw, SCTP und DCCP geschlossen sind — auf Kosten von allem, was einen solchen Socket öffnet, nicht nur von Code, der UDP sendet. Was bleibt, ist ein portbasierter Rest, evil.com:<proxy_port>, bis #114.
Außerhalb des Proxy-erzwungenen Modus schränkt Linux UDP nicht ein. Landlocks Netzwerkrechte sind bis ABI v10 nur TCP, cplt behandelt AccessNet::ConnectTcp allein, und die obige seccomp-Regel wird bewusst nicht angewendet — SOCK_DGRAM dort zu verweigern würde getaddrinfo(3) und damit alles DNS für jedes nicht über den Proxy laufende Tool brechen. Ausgehendes UDP zu jedem Host, eingehender UDP-Bind, DNS-Tunneling und QUIC/HTTP-3 sind daher im Standardmodus unvermittelt, und der CONNECT-Proxy überträgt nur TCP, sodass nichts davon im Proxy-Log erscheint. macOS schränkt UDP im Standardmodus ein, leitet es aber auch nicht weiter: remote ip "*:443" deckt UDP ab, sodass QUIC/HTTP-3 auf 443 auch dort den Proxy nicht berührt. Unter proxy.forced ist das Proxy-Log auf macOS eine vollständige Aufzeichnung des Egress. Unter Linux ist es vollständig, außer für den obigen Rest evil.com:<proxy_port>, der den Proxy nicht durchläuft und daher nicht in seinem Log erscheint.
Beide Listen stimmen auf dieselbe Weise überein: example.com deckt die exakte Domain und jede Subdomain ab, der Abgleich ist case-insensitive, und abschließende Punkte werden entfernt. Blocklist- und Allowlist-Dateien werden alle fünf Sekunden neu eingelesen, sodass du sie live bearbeiten kannst. Localhost-Verkehr umgeht den Proxy über NO_PROXY und erscheint nie im Audit-Log. --proxy-timeout <SECONDS> begrenzt Anfrage- und Header-Lesevorgänge (Standard 60) und baut keine etablierten CONNECT-Tunnel ab, die bis zu einer Stunde im Leerlauf bleiben können.
Jedes Proxy-Flag, jedes Detail zur Domain-Filterung, Upstream-Corporate-Proxy-Verkettung und das Format des Verbindungslogs: docs/proxy.md.
Aktiviere sie, und cplt fängt gh und git über Wrapper-Skripte in $PATH ab:
Dies ist Layer 3, eine weiche Barriere. Sie hält einen regelkonformen Agenten davon ab, versehentlich etwas Destruktives zu tun. Für eine harte Grenze stütze dich auf die Kernel-Sandbox und den serverseitigen Branch-Schutz.
Mit aktiviertem gh-Guard cached cplt außerdem das GitHub-Token beim Start und liefert es einmal über den gh auth token-Callback aus, dann löscht es den Cache. Das reduziert versehentliches und umgebungsbedingtes Leaken. Es ist keine Grenze gegen einen feindseligen Agenten, denn der Cache liegt im eigenen TMPDIR des Agenten, und ein Agent, der ihn vor dem legitimen Konsumenten liest, bekommt das Token trotzdem. SECURITY.md enthält die vollständige Erklärung zu block_auth_token.
Vollständiges Verhalten: docs/gh-guard.md · docs/git-guard.md
Die Sandbox blockiert einige Workflows absichtlich. Die häufigen und ihre Fixes:
Playwright Chromium benötigt cplt config set sandbox.allow_cache_exec ms-playwright, und Chromium muss ohne seine eigene verschachtelte Sandbox laufen. Auf macOS können seine Helper keine zweite Seatbelt-Sandbox innerhalb von cplt initialisieren (forbidden-sandbox-reinit); unter Linux blockiert cplts seccomp-Filter die Namespace-Syscalls, die diese Sandbox benötigt. Playwright als Bibliothek startet bereits mit --no-sandbox, und dasselbe Opt-in setzt PLAYWRIGHT_MCP_SANDBOX=false für Playwright MCP, das es sonst wieder einschalten würde. Jeder andere Chromium-Launcher benötigt selbst --no-sandbox. cplt bleibt die durchsetzende Kernel-Grenze, aber ein kompromittierter Renderer erhält dann das vollständige cplt-Playwright-Profil statt Chromiums engerem Child-Profil. Siehe Cache exec und SECURITY.md.
Git commit funktioniert für jeden Agenten; ob git push über HTTPS funktioniert, hängt vom Agenten ab. Drei Voraussetzungen: HTTPS-Remotes statt SSH verwenden (git remote set-url origin https://github.com/org/repo.git, oder global umschreiben mit git config --global url."https://github.com/".insteadOf "[email protected]:"), einmal gh auth login außerhalb der Sandbox ausführen und gh auth setup-git ausführen, falls der Credential-Helper noch nicht konfiguriert ist. Push führt dann gh auth git-credential aus, was ein Token benötigt, das gh von innerhalb der Sandbox erreichen kann — das unterscheidet sich je nach Agent, siehe Git workflow. Pushes auf den Default-Branch und alle Force-Pushes werden standardmäßig vom git-Guard verweigert; pushe einen Feature-Branch. Der SSH-Agent-Socket ist blockiert, weil er jeden geladenen Key entsperrt und sich bei jedem Host authentifizieren kann, während der gh-Credential-Helper auf GitHub beschränkt ist.
Die JVM ist proxy-aware, daher muss ein internes Maven-Repository auf einer privaten IP jetzt erlaubt werden. cplt injiziert http(s).proxyHost/proxyPort in JAVA_TOOL_OPTIONS, sodass die Gradle- und Maven-Abhängigkeitsauflösung über den CONNECT-Proxy läuft und im Proxy-Log erscheint, statt ihn zu umgehen. Der SSRF-Guard des Proxys verweigert dann ein internes Nexus oder Artifactory, das in den privaten Adressraum auflöst, genau wie er es bereits für curl, npm und pip tut. Füge seinen DNS-Namen zu proxy.allow_private_domains hinzu. Eine Repository-URL, die als bloßes IP-Literal geschrieben ist (https://10.20.30.40/repository/maven-public/), kann durch keinen Key erlaubt werden — diese Prüfung läuft, bevor die Allow-List konsultiert wird —, daher benötigt ein solches Repository einen DNS-Namen. WorkerExecutor-Plugin-Forks und ein Gradle-Daemon, der außerhalb von cplt gestartet und innerhalb wiederverwendet wird, laufen nicht über den Proxy. Siehe Internal Maven/Gradle repositories on private IPs.
Gradle 9+ führt seine eigene verschachtelte Sandbox aus, und cplt schaltet sie ab. Seit Gradle 8.8 hüllt sich der Daemon in sandbox-exec (gesteuert durch GRADLE_MACOS_SANDBOX, früher die Property org.gradle.daemon.sandbox). macOS unterstützt keine verschachtelten sandbox-exec-Aufrufe, daher schlägt die innere Sandbox bei Socket-Operationen mit "Operation not permitted" fehl. cplt injiziert GRADLE_MACOS_SANDBOX=off, da es bereits Sandboxing auf Kernel-Ebene bereitstellt. Dies ist ein bekanntes Upstream-Problem, das jedes Tool trifft, das Gradle in eine äußere Sandbox einhüllt. Überschreibe mit --pass-env GRADLE_MACOS_SANDBOX, wenn du wirklich Gradles eigene Sandbox willst.
Copilot CLI 1.0.83 führt seine eigene verschachtelte Sandbox aus, und cplt schaltet sie ab. Unter Linux baut diese Sandbox einen Netzwerk-Namespace auf — slirp4netns, iptables, /dev/net/tun — und cplts seccomp-Filter verweigert das dafür benötigte unshare. cplt setzt außerdem HTTP_PROXY/HTTPS_PROXY, was in 1.0.83 eine Linux-Sandbox auf den Proxy-Egress-Pfad bringt, ob du es wolltest oder nicht, sodass die beiden bei jedem Start kollidieren. Symptom: [cplt] Starting Copilot in sandbox... und dann nichts. cplt injiziert Copilots eigenes Opt-out, COPILOT_CLI_SANDBOX_SUPPORT_OVERRIDE=unsupported; Copilot tritt für die Sitzung zurück, sagt es, und lässt dein gespeichertes sandbox.enabled unangetastet. cplt ist die Grenze, wie bei Gradle und Chromium. Überschreibe mit --pass-env COPILOT_CLI_SANDBOX_SUPPORT_OVERRIDE. Eine unternehmensverwaltete Richtlinie, die die Sandbox erfordert, überschreibt all dies — siehe Copilot CLI's own command sandbox.
Jede Auswirkung, mit den Pro-Tool-Tabellen, JVM- und Kotlin-Daemon-Hinweisen, GPG-Fehlerbehebung und den Plattformunterschieden bei privaten Registries: docs/known-impacts.md.
sandbox-exec ist veraltet. Apple hat es nicht entfernt, könnte es aber in einer zukünftigen macOS-Version tun.lsopen hat ebenfalls keinen Filter, daher ist --allow-browser alles von Launch Services oder nichts davon. Mit aktiviertem Flag kann der Agent jede Anwendung außerhalb der Sandbox starten, und kein Wrapper kann das einschränken — siehe docs/security.md..env-Lesen/Schreiben/Löschen innerhalb des Projektverzeichnisses nicht vom Kernel durchgesetzt. .git/hooks-Schreibvorgänge werden blockiert, wenn Bubblewrap aktiv ist.--deny-path erfordert Bubblewrap. Es wird über Mount-Masken durchgesetzt, wenn bwrap aktiv ist. Ohne es ist Landlock nur eine Allowlist und cplt warnt über das Deny, statt es anzuwenden.Mehr: docs/security.md
Beiträge sind willkommen.```bash git clone https://github.com/navikt/cplt.git && cd cplt git config core.hooksPath hack # enables pre-commit fmt + clippy checks mise run check # runs fmt, clippy, and tests
Öffne ein Issue, bevor du eine große Änderung beginnst. Jeder PR muss CI bestehen (fmt, clippy, Tests).
## Referenzen
- [SECURITY.md](https://github.com/navikt/cplt/blob/main/SECURITY.md), das vollständige Sicherheitsmodell, die Bedrohungsanalyse, die Teststrategie und frühere Arbeiten
- [Apple sandbox-exec(1)](https://keith.github.io/xcode-man-pages/sandbox-exec.1.html)
- [Chromium Seatbelt V2 Design](https://chromium.googlesource.com/chromium/src/sandbox/+show/refs/heads/main/mac/seatbelt_sandbox_design.md)
- [Landlock LSM documentation](https://docs.kernel.org/userspace-api/landlock.html)
- [seccomp-BPF documentation](https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html)
- [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)
- [michaelneale/agent-seatbelt-sandbox](https://github.com/michaelneale/agent-seatbelt-sandbox)
## Lizenz
[MIT](https://github.com/navikt/cplt/blob/main/LICENSE)
| Flag | Was es tut |
|---|
--preset strict | Vollständiger Netzwerk-Lockdown. Alle fünf Toggles aus, plus gh_guard, git_guard, proxy.forced (erzwungener Proxy-Egress) und proxy.default_allowlist (fail-closed Domain-Allowlist) an. Notausgang: --allow-all-domains deaktiviert nur die Allowlist |
--preset standard | Die aktuellen Defaults. Alle fünf aus, Scratch-Verzeichnis bleibt an. Gleichbedeutend mit keinem Preset |
--preset permissive | Aktiviert allow_localhost_any, allow_tmp_exec und allow_lifecycle_scripts |
--preset full-trust | ⚠️ Gefährlich. Aktiviert alle fünf, zusätzlich allow_env_files und allow_docker |
| Flag | Was es tut |
|---|
-d, --project-dir <DIR> | In welchem Verzeichnis Copilot arbeiten darf. Standardmäßig das Root des aktuellen Git-Repos |
--allow-read <PATH> | Copilot erlauben, Dateien außerhalb des Projekts zu lesen, nur lesend. Wiederholbar |
--allow-write <PATH> | Copilot erlauben, außerhalb des Projekts zu lesen und zu schreiben. Mit Vorsicht verwenden. Wiederholbar. Der Baum ist beschreibbar, aber nicht ausführbar — ein Baum, der beides ist, ist ein Binary-Drop-Pfad, also stoppt ein allow.write über ~/.cargo auch die Ausführung von ~/.cargo/bin. Verwende --allow-exec auf einem separaten, nicht überlappenden Baum, wenn du beides brauchst |
--allow-exec <PATH> | ⚠️ Gefährlich. Dem Agenten erlauben, Binaries aus einem Baum außerhalb der Standard-Tool-Verzeichnisse auszuführen — etwa ein verschobenes Homebrew- oder Toolchain-Prefix. Gewährt Lesen und Ausführen, niemals Schreiben. Wiederholbar. Wird für ein unsicheres Root (/, /tmp, $HOME und dessen Elternverzeichnisse, die Systemverzeichnisse der Plattform) und für jeden Baum abgelehnt, der einen beschreibbaren überlappt — das Projektverzeichnis, eine --allow-write-Gewährung, ein beschreibbares Tool-Verzeichnis wie ~/.cache, ein beschreibbares Agent-Datenverzeichnis (~/.claude, ~/.local/share/opencode, ~/.pi/agent und ähnliche), das echte .git eines Worktrees oder Bare-Repos, oder einen Baum, den die Backends ohne jegliche Gewährung beschreibbar machen (/tmp und /dev/shm unter Linux; /private/tmp und /private/var/folders unter macOS): beschreibbar plus ausführbar ist ein Binary-Drop-Pfad, und kein Backend kann die Schreibgewährung von der Exec-Gewährung abziehen |
--allow-socket <PATH> | ⚠️ Gefährlich. Einen Unix-Domain-Socket-Pfad erlauben, zum Beispiel einen benutzerdefinierten LSP-Daemon oder einen Datenbank-Socket. Wiederholbar. Was auch immer am anderen Ende sitzt, läuft außerhalb der Sandbox, also ist das Zeigen auf docker.sock oder einen Agent-Socket gleichbedeutend mit --allow-docker, und der einzige Schutz ist, dass --deny-path-Überlappungen abgelehnt werden. Unter Linux tut es nichts unter Kernel 7.1, da Unix-Socket-Verbindungen vor ABI v9 nicht von Landlock gegated werden (siehe Linux-Einschränkungen) |
--deny-path <PATH> | Einen Pfad blockieren, der sonst erlaubt wäre. Deny gewinnt immer. Wiederholbar |
--allow-port <PORT> | Ausgehenden Verkehr auf einem zusätzlichen Port erlauben. Standardmäßig nur 443. Wiederholbar. Unter macOS lautet die Regel (remote ip "*:PORT"), was familienagnostisch ist und daher UDP ebenso wie TCP umfasst; Landlock gated nur TCP-Connect. Unter proxy.forced öffnet der Port überhaupt keinen direkten Socket — er ist durch den Proxy erreichbar, sodass proxy-bewusste Tools weiter funktionieren |
--allow-localhost <PORT> | Ausgehend zu localhost auf einem Port erlauben. Localhost ist standardmäßig blockiert. Für MCP-Server oder Dev-Server verwenden. Wiederholbar |
--allow-localhost-any | Ausgehend zu localhost auf allen Ports erlauben. Wird von Build-Tools wie Turbopack (Next.js) und Vite benötigt, die zufällige ephemere Ports für IPC verwenden |
| Kategorie | Beispiele | Wie |
|---|
| Kernsystem | HOME, USER, PATH, SHELL, TMPDIR, LANG | Explizite Allowlist |
| Terminal | TERM, COLORTERM, TERM_PROGRAM | Explizite Allowlist |
| Editor | EDITOR, VISUAL, PAGER | Explizite Allowlist |
| Auth-Tokens | GH_TOKEN, GITHUB_TOKEN, COPILOT_GITHUB_TOKEN | Nur durchgelassen, wenn du sie bereits gesetzt hast. Der gh-Guard verwendet stattdessen eine Einmaldatei |
| Copilot-Config | COPILOT_DEBUG, COPILOT_* | Präfix-Allowlist |
| Sprach-Runtimes | NODE_*, GOPATH, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, PYTHONPATH | Explizite Allowlist |
| Tool-Manager | NVM_*, FNM_*, PYENV_*, MISE_*, SDKMAN_*, COREPACK_*, YARN_* | Präfix-Allowlist |
| OpenTelemetry | OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, OTEL_* | Präfix-Allowlist (OTEL_EXPORTER_OTLP_HEADERS kann Opt-in-Auth tragen) |
| XDG-Verzeichnisse | XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_STATE_HOME, XDG_CACHE_HOME | Explizite Allowlist |
NO_COLORFORCE_COLORSSH_AUTH_SOCKSSH_AGENT_PID| Flag | Was es tut |
|---|
--allow-lifecycle-scripts | npm/yarn/pnpm-Lifecycle-Skripte (Postinstall-Hooks) ausführen lassen. Standardmäßig blockiert. Verwenden, wenn npm install sie benötigt |
--allow-gpg-signing | GPG-Commit- und Tag-Signierung innerhalb der Sandbox erlauben. Gewährt Lesezugriff auf den öffentlichen Schlüsselbund und den GPG-Agent-Socket. Private Schlüssel bleiben verweigert. Siehe GPG-Signierung |
--allow-jvm-attach | JVM-Attach-API-Unix-Sockets in /tmp erlauben. Benötigt für MockK-Inline-Mocking, Mockito-Inline-Agents, ByteBuddy. Siehe JVM Attach API |
--allow-msbuild | MSBuild-Worker-Node-Unix-Sockets in /tmp erlauben. Benötigt für dotnet build. Aktiviert nicht den persistenten MSBuild-Server. Siehe MSBuild-Worker-Node-IPC |
--no-scratch-dir | Das sitzungsbezogene Scratch-Verzeichnis deaktivieren, das standardmäßig an ist. TMPDIR wird nicht umgeleitet |
--scratch-dir | Das sitzungsbezogene Scratch-Verzeichnis explizit aktivieren. Ist bereits der Standard, also dient dies zum Überschreiben von scratch_dir = false in der Config |
--brief | 🧪 Experimentell. Das agentengerichtete Sandbox-Brief in das Scratch-Verzeichnis schreiben (CPLT_BRIEF.md). Standardmäßig aus. Auch sandbox.brief = true in der Config. Instabil, kann sich also in einer zukünftigen Version ändern oder entfernt werden |
--no-brief | Das Sandbox-Brief für diesen Lauf ausschalten, überschreibt sandbox.brief = true in der Config. Unterdrückt außerdem den AGENTS.md-Block, der an das Brief gekoppelt ist |
--agents-md | 🧪 Experimentell. Mit --brief zusätzlich den verwalteten cplt-Block in die AGENTS.md des Projekts schreiben. Standardmäßig aus. Auch sandbox.agents_md = true in der Config. Ohne --brief wirkungslos. Instabil, kann sich also in einer zukünftigen Version ändern oder entfernt werden |
--no-agents-md | Den AGENTS.md-Block für diesen Lauf ausschalten, überschreibt sandbox.agents_md = true in der Config. Lässt das Scratch-Dir-Brief unberührt |
--allow-tmp-exec | ⚠️ Gefährlich. Exec aus System-Temp-Verzeichnissen erlauben (/private/tmp, /private/var/folders). Bevorzuge das Scratch-Verzeichnis |
--allow-cache-exec <SUBDIR> | Exec aus einem ~/Library/Caches/<SUBDIR> erlauben. Wiederholbar. Für Tools, die dort kompilierte Binaries cachen, wie Playwright und pnpm dlx |
--allow-cache-exec-any | ⚠️ Gefährlich. Exec aus dem gesamten ~/Library/Caches erlauben. Bevorzuge --allow-cache-exec <SUBDIR> |
--allow-browser | ⚠️ Gefährlich. Mit diesem Flag kann der Agent jede Anwendung auf deinem Rechner außerhalb der Sandbox starten. Die Gewährung ist Launch Services, kein Browser: launchd startet das Ziel außerhalb des Seatbelt-Profils, also läuft open -a Terminal /tmp/x.sh unsandboxed. Dies kann nicht auf URLs beschränkt werden — SBPLs lsopen nimmt keinen Filter, und die Gewährung ist über LSOpenCFURLRef() auch ohne das open-Binary erreichbar, sodass kein Wrapper sie einschränken kann (#251, und docs/security.md). Schalte es nur ein, während tatsächlich ein Anmelde-Prompt auf dem Bildschirm ist (MCP-Server-OAuth, Re-Auth), und schalte es dann wieder aus. Standardmäßig aus |
--deny-clipboard | Den Agenten daran hindern, die macOS-Zwischenablage zu lesen oder zu schreiben (pbpaste/pbcopy), indem der Mach-Service com.apple.pasteboard verweigert wird. Jeder andere Mach-Service (Keychain, DNS, Security-Framework) ist unberührt. Standardmäßig an — dieses Flag wiederholt den Standard |
--allow-clipboard | Dem Agenten die macOS-Zwischenablage zurückgeben, die cplt standardmäßig verweigert. Entspricht sandbox.deny_clipboard = false |
--use-bubblewrap | Nur Linux. Die bubblewrap-Namespace-Schicht (PID-, Mount-, IPC-, UTS-, cgroup-, User-Namespaces plus ein privates /tmp) zusätzlich zu Landlock und seccomp erfordern. Bricht mit Fehler ab, wenn bwrap fehlt. Wird automatisch erkannt, wenn keines der beiden Flags angegeben ist |
--no-bubblewrap | Nur Linux. Niemals bubblewrap verwenden, auch wenn installiert. Fällt auf Landlock und seccomp zurück. Verwende es, wenn bwrap ein bestimmtes Tool bricht |
| Runtime | Home-Verzeichnisse | Env-Variablen / Präfixe | Entdeckung |
|---|
| Node.js | .nvm, .local/share/fnm, .local/bin | NODE_*, NPM_*, NVM_*, FNM_* | node |
| Rust | .cargo, .rustup | CARGO_HOME, RUSTUP_HOME | cargo |
| Go | go/bin, go/pkg | GOPATH, GOROOT, GOCACHE, etc. | go |
| Java/Kotlin (JVM) | .sdkman, .jenv, .gradle, .m2 | JAVA_HOME, JAVA_TOOL_OPTIONS, GRADLE_*, MAVEN_*, SDKMAN_*, JENV_* | java, gradle |
| Kotlin Native | .konan | keine | keine |
| Python | .pyenv | VIRTUAL_ENV, PYTHONPATH, PYENV_ROOT, PYENV_* | python3 |
| Yarn Berry | .yarn | YARN_* (Härtung überschreibt YARN_ENABLE_SCRIPTS) | yarn |
| pnpm | Library/pnpm, .local/share/pnpm | PNPM_HOME | pnpm |
| Corepack | keine | COREPACK_* | keine |
| mise | .local/share/mise, .mise | MISE_* | mise |
| Flag | Was es tut |
|---|
--doctor | Veraltet. Verwende stattdessen das Subkommando cplt doctor |
--print-profile | Das generierte Sandbox-Profil (SBPL) ausgeben und beenden |
--show-denials | macOS-Sandbox-Denial-Logs in Echtzeit streamen |
--no-validate | Die Startprüfung überspringen, die verifiziert, dass die Sandbox-Beschränkungen aktiv sind |
-y, --yes | Den interaktiven Bestätigungs-Prompt überspringen. Die Konfigurationszusammenfassung wird weiterhin ausgegeben, zur Auditierbarkeit. Erforderlich, wenn stdin kein TTY ist, also benötigen CI und Skripte es |
-q, --quiet | Das Startbanner und nicht wesentliche Nachrichten unterdrücken. Fehler und Warnungen werden weiterhin ausgegeben. Auch sandbox.quiet = true in der Config |
--no-quiet | sandbox.quiet = true überschreiben und die Startzusammenfassung trotzdem anzeigen |
--no-audit | Den Post-Session-Änderungsbericht überspringen. cplt vergleicht normalerweise den Arbeitsbaum mit einem Baseline-Commit, der vor dem Lauf festgelegt wurde, und listet auf, was die Sitzung berührt hat, wobei sensible Pfade markiert werden. -q unterdrückt ihn ebenfalls |
--init-config | Eine Starter-Config-Datei unter ~/.config/cplt/config.toml erstellen und beenden |
| Flag | Was es tut |
|---|
--resume[=SESSION] | Eine vorherige Sitzung fortsetzen. Bloßes --resume wählt interaktiv, --resume=NAME wählt nach Name oder ID |
--continue | Die jüngste Sitzung im aktuellen Verzeichnis fortsetzen |
--remote | Fernsteuerung aktivieren, sodass du die Sitzung von GitHub.com oder mobil überwachen und steuern kannst |
--name SESSION | Die Sitzung benennen, damit --resume=NAME sie später finden kann |
| cplt-Flag | Copilot | OpenCode | Antigravity (agy) | Claude Code |
|---|
--continue | --continue | --continue | --continue | --continue |
--resume | --resume | --continue¹ | --continue¹ | --resume |
--resume=ID | --resume=ID | --session ID | --conversation ID | --resume ID |
--remote | --remote | ignoriert | ignoriert | ignoriert |
--name NAME | --name NAME | ignoriert | ignoriert | ignoriert |
GOOGLE_API_KEYGEMINI_API_KEY--pass-env--observe-domains-Aufzeichnung keinen eigenen Host, daher ist seine integrierte Allowlist nur die gemeinsame Paket-Registry-Basis. Fügen Sie die Domain Ihres Providers über allowed_domains hinzu, bevor Sie --default-allowlist aktivierenGOOSE_DISABLE_KEYRING=1 lässt goose stattdessen eine secrets.yaml in seinem Konfigurationsverzeichnis verwenden, und die Übergabe des Schlüssels mit --pass-env vermeidet gespeicherte Geheimnisse vollständig. Unter Linux verwendet goose den D-Bus Secret Service, den die Keychain-Gewährung nicht betrifft~/.config/goose/config.yaml deklariert extensions:-Einträge, deren cmd goose bei jedem Sitzungsstart startet, sodass ein beschreibbares Konfigurationsverzeichnis ein Host-Persistenz-Vektor ist. Normale Sitzungen schreiben es nicht; /mode-Änderungen und persistierte Tool-Berechtigungen überleben einen sandboxten Lauf nicht. Neukonfiguration mit goose configure außerhalb von cplt~/.local/share/goose/) und State-Verzeichnisse (~/.local/state/goose/) sind beschreibbar, mit verweigerter Ausführung. goose verwendet diese XDG-Pfade auch unter macOS und respektiert dort die XDG_*-Überschreibungen--continue und bloßes --resume werden auf goose session --resume abgebildet; --resume=ID auf goose session --resume --session-id ID; --name X auf goose session --name X. Dies sind Subcommand-Flags, daher injiziert cplt das session-Subcommand mit ihnen. --remote wird ignoriert (kein goose-Äquivalent)| Schicht | Durchsetzung | Umgehbar? | Was sie schützt |
|---|
| 1. Kernel-Sandbox | macOS Seatbelt / Linux Landlock+seccomp | ❌ Nein | Dateizugriff, exec, Netzwerkports |
| 2. Netzwerk-Proxy | CONNECT-Proxy, Domain-Filterung | ❌ Nein (innerhalb der Sandbox) | Ausgehende Verbindungen, Exfiltration |
| 3. Command-Guard | PATH-basierte Wrapper-Skripte | ⚠️ Weiche Barriere | Pushes, Merges, Releases, API-Schreibzugriffe |
gitbwrapsandbox-execmiseghPATHcplt doctor: Seine --version-Probes führen jedes Agent-Binary aus, das es auf deinem PATH findet, im Parent, sodass ein platziertes dort ausgeführt wird – dieselbe Exposition durch entdeckte Pfade wie beim Start oben, weshalb doctor ein Bericht und keine Grenze ist. Seine gh-Prüfung wird aus den vertrauenswürdigen Verzeichnissen aufgelöst, und sein Auslesen der Kernel-Version startet überhaupt nichts| Command | Action |
|---|
gh pr merge, gh repo delete, gh release create | 🔒 Blocked |
git push origin main, git push --force | 🔒 Blocked |
gh api (write to other repos) | 🔒 Scope-checked |
gh pr list, gh issue list, git commit | ✅ Allowed |
git push origin feature-branch | ✅ Allowed with protect_default_branch_only |
| Impact | Fix |
|---|
.env files blocked | cplt config set sandbox.allow_env_files true |
| npm postinstall hooks blocked | cplt config set sandbox.allow_lifecycle_scripts true |
go test / mise run blocked (temp exec) | The scratch dir is on by default. If you still need it, cplt config set sandbox.allow_tmp_exec true |
| Localhost connections blocked | cplt config set allow.localhost 3000, or cplt config set sandbox.allow_localhost_any true |
| Docker blocked | cplt config set sandbox.allow_docker true ⚠️ |
| SSH blocked | Use HTTPS remotes instead |
| GPG signing disabled | cplt config set sandbox.allow_gpg_signing true |
| JVM MockK/Mockito fails | cplt config set sandbox.allow_jvm_attach true |
dotnet build MSBuild worker nodes blocked | cplt config set sandbox.allow_msbuild true |
| Private registry creds blocked | cplt config set allow.read "~/.m2/settings.xml" |
| Internal Maven/Nexus repo unreachable (Gradle/Maven) | cplt config set proxy.allow_private_domains "intern.example.com". An IP-literal repository URL cannot be allowed — give the host a DNS name; see below |
| Playwright Chromium won't launch | Allow cache exec, then disable Chromium's nested sandbox; see below |