Skip to content
nav-pilotCopilot at Nav
  • Get started
  • nav-pilot
  • Customisation
  • Practice and rules
  • Insights
Sign in

Built with GitHub Copilot

Glossary (Norwegian)NorskPrivacy (Norwegian)Accessibility (Norwegian)GitHub

cplt keeps your AI agent sandboxed.

Kernel-level isolation for AI coding agents. Your secrets stay secret. Enforced by the OS, not by trust.

Read the argument: a sandbox confines the process, not the token

navikt/cplt★ 128
cplt: sandboxed Copilot session
The agent tries to read ~/.ssh and to post data to an external endpoint. The sandbox denies both.
brew install navikt/tap/cplt

macOS (Apple Seatbelt) · Linux (Landlock + seccomp-BPF) · Windows: WSL2 only

Security boundary

What your agent can and cannot access, enforced at the kernel level.

ResourceWithout cpltWith cplt
Project directory (read/write)AllowedAllowed
Secrets (.env*, .pem, .key§, SSH keys)⚠ExposedProtected
Cloud credentials (~/.aws, ~/.azure)⚠ExposedProtected
Build tool homes (~/.m2, ~/.gradle, ~/.cargo)AllowedAllowed
Tool credential files (~/.m2/settings.xml, ~/.gradle/gradle.properties)⚠ExposedProtected
Git hooks‡, /tmp execution, SSH agent⚠ExposedProtected
Outbound network (HTTPS)⚠ExposedFiltered*
Private IPs, and localhost on macOS†⚠ExposedProtected
Destructive git/gh commands (push to default branch, force push, merge, delete)⚠ExposedProtected
Copilot auth and tool caches (read-only)AllowedAllowed

*Routed through CONNECT proxy. Telemetry and non-allowlisted domains are blocked.
†On Linux, localhost is not blocked and UDP is unrestricted unless proxy.forced is on.
‡Git hooks are write-protected on macOS. On Linux .git/hooks stays writable unless bubblewrap is installed.
§Blocked in the project on macOS. On Linux, Landlock cannot deny single files inside the project. Key files are matched by exact name (.pem), not by extension (server.pem), unless sandbox.deny_key_files_by_extension is on.

The filesystem and syscall layers are kernel-enforced: Apple Seatbelt on macOS, Landlock + seccomp-BPF on Linux, plus bubblewrap namespace isolation when bwrap is installed. The git and gh guards are a different mechanism: PATH shims and a best-effort command filter. Bubblewrap is not a network boundary, because the host network is shared by design so the proxy keeps working, and the root filesystem is bind-mounted read-only with Landlock as the access-control layer. Known limits are written down in SECURITY.md.

gh guard & git guard

Block destructive GitHub and git operations. The agent can commit and branch, but not push to main or merge PRs.

gh guard with a three-tier policy

Default-deny engine over 133 classified gh commands: 52 allowed, 64 blocked, 17 scope-checked.

Read

gh issue list, gh pr view

Allowed

Write

gh pr create, gh issue edit

Own repo only

Destructive

gh repo delete, gh pr merge

Always blocked

gh api calls restricted to /repos/{current-repo}/...

git guard for push protection

Blocks pushes to the default branch, and force pushes everywhere. A push to a feature branch goes through, so the review gate is the pull request rather than the push. Commit, branch, rebase. All fine.

Refuse every push instead? cplt config set git_guard.protect_default_branch_only false

Opt out for a single run
$ cplt --no-gh-guard --no-git-guard
What the agent sees
⚠️ BLOCKED by sandbox: 'gh repo delete' is not allowed
in this environment.
Reason: deletes entire repository
This operation is restricted by the cplt sandbox
to prevent unintended changes.
Please make a note of this for the human operator
and continue with your remaining work.

Both guards are on by default. Opt out for a single run with --no-gh-guard or --no-git-guard, or set mode: audit to observe before enforcing.

Sandbox config read from git HEAD, not the working tree

Commit .cplt.toml to your repo and every developer gets the same sandbox, without the agent being able to loosen it.

.cplt.toml
# Tightens the sandbox, applied without approval
[deny]
env = ["VAULT_TOKEN", "NPM_TOKEN"]

# Loosens the sandbox, inert until `cplt trust accept`
[propose]
allow_localhost_any = true

[propose.allow]
localhost = [3000, 5432]

[deny] is applied automatically

Can only tighten the sandbox. Block env vars and deny file paths. No approval needed.

[propose] requires approval

Request additional permissions. Each developer approves with cplt trust accept --all. Content-pinned, so any change invalidates the approval.

The agent cannot edit its own sandbox

.cplt.toml is read from git HEAD, so an edit in the working tree changes nothing, and the file is write-denied inside the sandbox anyway. A committed [deny] block therefore applies unconditionally: it can only tighten the sandbox, so there is nothing to approve. A [propose] block is content-pinned and stays inert until the developer runs cplt trust accept, and any change to the block invalidates that approval.

How it works

Three steps from zero to sandboxed agent.

STEP 1

Install

brew install navikt/tap/cplt

Homebrew on macOS, the apt archive on Debian and Ubuntu (WSL2 included), the install script anywhere else.

STEP 2

Configure

cplt init --write

Detect your project's tooling and generate sandbox config.

STEP 3

Run your agent

cplt -- -p "fix the tests"

Your agent works normally, but your secrets are unreadable.

Copilot CLI is the default. --agent takes copilot, opencode, gemini, antigravity, pi, claude, goose, dsh and shell, the last being a sandboxed shell with no AI.

Make it the default

Run cplt --shell-install so copilot always runs sandboxed.

$ cplt --shell-install
[cplt] Installed 'copilot' alias in ~/.zshrc
[cplt] Restart your shell or run: source ~/.zshrc
View on GitHub →

Network proxy

A local CONNECT proxy filters and logs the agent's HTTPS traffic. By default the kernel still permits direct outbound :443, so the proxy only sees what is routed to it. Turn on proxy.forced to make it mandatory.

cplt sandboxAI Agentcurl, fetch, gitCONNECT Proxylocalhost:ephemeralBlocklist / AllowlistPrivate IP filterDNS rebinding protectionAudit log ✓✓ AllowedInternetgithub.com, npm, PyPI, api.openai.comAllowlisted or not in blocklist✗ BlockedDroppedwebhook.site, ngrok.io, pastebin.com169.254.x.x, 10.x.x.x, tunneling servicesproxy.blocked_domains+ 47 built-in · reloads every 5sproxy.allowed_domainsFail-closed; agent hosts kept

How the traffic flows

Agent traffic (curl, fetch, git) goes to the CONNECT proxy on localhost:ephemeral inside the sandbox. The proxy applies its blocklist and allowlist, a private IP filter and DNS rebinding protection, and writes an audit log. Allowed traffic reaches the internet: github.com, npm, PyPI, api.openai.com, anything allowlisted or not in the blocklist. Blocked traffic is dropped: webhook.site, ngrok.io, pastebin.com, 169.254.x.x, 10.x.x.x, tunneling services. proxy.blocked_domains adds to the 47 built-in domains and reloads every 5s; proxy.allowed_domains turns on fail-closed allowlist mode, which still lets the agent reach its own hosts.

Opt-in proxy-forced mode

By default the kernel still allows direct outbound :443, so a raw socket or an unset HTTPS_PROXY can skip the proxy. proxy.forced closes that bypass: the proxy becomes mandatory and kernel-level egress is restricted to the proxy port only. Fails closed. If the proxy cannot start, the agent does not launch. macOS pins connections to localhost:<proxy_port>, though name lookups through the system resolver still work; Linux drops the direct :443 allow, but Landlock filtering is port-based, so a narrow port-based residual remains. That is a deliberate limitation, tracked upstream.

cplt config set proxy.forced true

Corporate / upstream proxy chaining

Behind a corporate proxy? proxy.upstream forwards CONNECT tunnels through it instead of forcing you to disable the cplt proxy. cplt applies its own domain filtering, logging, and port checks before forwarding the tunnel upstream, so a blocked target never reaches the corporate proxy. Optional basic-auth userinfo is supported; http scheme only.

cplt config set proxy.upstream "http://proxy.example.com:8080"

Auto-detect your project

cplt init scans your project for build files, frameworks, and patterns, then generates the right .cplt.toml automatically.

$ cplt init
Detected:
  Spring Boot  application.yml + spring-boot-starter
  Flyway       db/migration/ directory
  Docker       Dockerfile + compose.yml
  Gradle       build.gradle.kts
  .env         .env.example found

Generated .cplt.toml:

# Deny access to sensitive env vars
[deny]
env = ["API_KEY", "DB_PASSWORD"]

[propose]
# ⚠️ grants access to the Docker socket, effectively root on the host
allow_docker = true
allow_jvm_attach = true
allow_localhost_any = true

[propose.allow]
localhost = [5432, 8080]

Run cplt init --write to save

17 ecosystem detectors

Each detector knows which sandbox permissions the ecosystem needs. Dangerous permissions get risk warnings.

  • JVMGradle / Maven
  • Node.jsnpm / pnpm
  • DockerCompose
  • Pythonpip / uv
  • Spring Boot8080 + PG
  • Ktor8080
  • Next.js3000
  • FlywayPG 5432
  • Playwrightbrowsers
  • Rust / Godefaults

Personal config with --global

Detects Gradle wrapper, Playwright browsers, GPG signing, and alternative agents on your machine. Writes to ~/.config/cplt/config.toml.

Configuration

Every option explained. Search by name or description.

74 options

proxy.enabledbooldefault: true

Enable the CONNECT proxy for outbound HTTPS traffic logging and domain filtering.

cplt config set proxy.enabled false --force
proxy.forcedbooldefault: false

Force all egress through the proxy: make the proxy mandatory and restrict kernel-level egress to the proxy port only (no direct *:443). Fails closed if the proxy cannot start.

cplt config set proxy.forced true
proxy.portintegerdefault: 0

Local port for the CONNECT proxy listener.

cplt config set proxy.port 8080
proxy.blocked_domainsstringdefault: ""

Path to a file listing domains to block through the proxy (one per line).

cplt config set proxy.blocked_domains "~/.config/cplt/blocked-domains.txt"
proxy.allowed_domainsstringdefault: ""

Path to a file listing the only domains allowed through the proxy (allowlist mode).

cplt config set proxy.allowed_domains "~/.config/cplt/allowed-domains.txt"
proxy.default_allowlistbooldefault: false

Fail-closed networking (opt-in): restrict egress to the agent's built-in default allowlist (e.g. GitHub Copilot infrastructure + package registries) merged with allowed_domains; block all other domains. Override for one run with --allow-all-domains.

cplt config set proxy.default_allowlist true
proxy.log_filestringdefault: ""

Path to write proxy connection logs (CONNECT requests and outcomes).

cplt config set proxy.log_file "~/.cache/cplt/proxy.log"
proxy.log_levelstringdefault: none

Stderr verbosity for proxy events: "none" (silent), "error" (DNS/connect failures), "blocked" (errors + blocked connections), "all" (everything including CONNECTED). With an allowlist, an unset level shows blocked connections; an explicit "none" stays silent. The separate log_file records everything.

cplt config set proxy.log_level blocked
proxy.timeoutintegerdefault: 60

Timeout in seconds for proxy request/header reads. Established tunnels may idle up to 1h.

cplt config set proxy.timeout 120
proxy.upstreamstringdefault: ""

Upstream (corporate) proxy URL to forward CONNECT tunnels through, e.g. "http://corporate-proxy.example.com:8080". cplt still enforces all domain filtering, logging, and port checks before forwarding. Optional basic-auth userinfo is supported; only the http scheme is supported.

cplt config set proxy.upstream "http://proxy.example.com:8080"
proxy.upstream_no_proxystring[]default: []

Hosts that BYPASS the upstream proxy and are connected to directly (like NO_PROXY). Only meaningful with proxy.upstream. Suffix matching: "example.com" covers all subdomains; CIDR/IP ranges are NOT honored. Merged additively with the ambient NO_PROXY/no_proxy environment. All of cplt's domain/port/SSRF filtering still applies, so an internal host that resolves to a private IP is BLOCKED (403) unless you ALSO add it to proxy.allow_private_domains.

cplt config set proxy.upstream_no_proxy intern.example.com
proxy.allow_private_domainsstring[]default: []

Domains allowed to resolve to private/internal IPs (opt-in DNS-rebinding bypass). Use for corporate intranet services. Suffix matching: "intern.nav.no" covers all subdomains.

cplt config set proxy.allow_private_domains intern.example.com
allow.readstring[]default: []

Extra directories to allow read access (e.g., shared libraries outside the project).

cplt config set allow.read "~/shared-libs"
allow.writestring[]default: []

Extra directories to allow write access (use sparingly, the project dir is already writable).

cplt config set allow.write "~/shared-libs"
allow.execstring[]⚠ dangerousdefault: []

\u{26a0}\u{fe0f} DANGEROUS: Trees the agent may execute binaries from (e.g. a relocated Homebrew prefix). Read + execute, never write. Refused for an unsafe root (/, /tmp, $HOME and its parents, the platform system dirs) and for any tree that overlaps a writable one \u{2014} writable + executable is a binary-drop path.

cplt config set allow.exec "/opt/toolchain"
allow.socketstring[]default: []

Unix socket paths to allow access to.

cplt config set allow.socket "~/.colima/default/docker.sock"
allow.domainsstring[]default: []

Domains to add to the proxy allowlist. Adds to an allowlist already in force; does not turn one on.

cplt config set allow.domains registry.example.com
allow.portsinteger[]default: []

Additional outbound ports to allow (443 is always allowed).

cplt config set allow.ports 3000
allow.localhostinteger[]default: []

Specific localhost ports to allow outbound connections to (e.g., local dev servers).

cplt config set allow.localhost 3000
deny.pathsstring[]default: []

Extra paths to deny access to (overrides project-dir allows for sensitive subdirs). Each entry is one literal path: globs such as `**/*.pem` are not expanded.

cplt config set deny.paths "~/secrets"
deny.envstring[]default: []

Environment variables to strip from the sandbox (repo-local only: tightens env filtering).

cplt config set --repo deny.env VAULT_TOKEN
sandbox.agentstringdefault: ""

Preferred AI coding agent (copilot, opencode, gemini, antigravity, pi, claude, goose, dsh, shell). Auto-detected from PATH if not set.

cplt config set sandbox.agent opencode
sandbox.presetstringdefault: standard

Security posture baseline: "standard" (the default: both guards on in block mode, the git guard scoped to the default branch), "strict" (all toggles off, every push blocked, proxy.forced and proxy.default_allowlist on), "permissive" or "full-trust" (both guards off, sandbox toggles loosened). Individual keys/flags override it.

cplt config set sandbox.preset strict
sandbox.validatebooldefault: true

Validate the sandbox profile with sandbox-exec before launching Copilot.

cplt config set sandbox.validate false --force
sandbox.briefbooldefault: false

EXPERIMENTAL: Write the per-session agent-facing sandbox brief (CPLT_BRIEF.md) to the scratch dir. Unstable — may change or be removed in a future release.

cplt config set sandbox.brief true
sandbox.agents_mdbooldefault: false

EXPERIMENTAL: Also inject the managed cplt sandbox block into the project's AGENTS.md on launch (writes into the repo). Requires sandbox.brief. Unstable — may change or be removed in a future release.

cplt config set sandbox.agents_md true
sandbox.allow_env_filesbooldefault: false

Allow Copilot to read .env, .pem, .key files in the project directory.

cplt config set sandbox.allow_env_files true
sandbox.allow_localhost_anybooldefault: false

Allow outbound connections to any localhost port (for local dev servers).

cplt config set sandbox.allow_localhost_any true
sandbox.pass_envstring[]default: []

Extra environment variables to pass through to the sandbox (exact names).

cplt config set sandbox.pass_env MY_VAR
sandbox.repo_dirsstring[]default: []

Additional repositories this project spans (local config only: cplt config set --local). Relative paths are resolved when set; stored absolute and re-validated on every launch.

cplt config set --local sandbox.repo_dirs ../other-repo
sandbox.inherit_envbool⚠ dangerousdefault: false

⚠️ DANGEROUS: Pass ALL environment variables instead of the safe allowlist. May leak secrets.

cplt config set sandbox.inherit_env true --force
sandbox.allow_lifecycle_scriptsbool⚠ dangerousdefault: false

Allow npm/yarn/pnpm lifecycle scripts (postinstall, prepare, etc.) to run.

cplt config set sandbox.allow_lifecycle_scripts true --force
sandbox.allow_gpg_signingbool⚠ dangerousdefault: false

⚠️ DANGEROUS: Allow GPG commit/tag signing. Exposes the GPG agent socket for signature requests. Private keys stay protected.

cplt config set sandbox.allow_gpg_signing true --force
sandbox.allow_tmp_execbool⚠ dangerousdefault: false

⚠️ DANGEROUS: Allow executing binaries from /tmp and /var/folders. Weakens code-exec isolation.

cplt config set sandbox.allow_tmp_exec true --force
sandbox.scratch_dirbooldefault: true

Create a per-session scratch directory and redirect TMPDIR into it.

cplt config set sandbox.scratch_dir false
sandbox.auditbooldefault: true

Print the post-session project-change and network audit reports (net file changes vs a pinned baseline commit). Suppressed by quiet.

cplt config set sandbox.audit false --force
sandbox.use_bubblewrapbooldefault: auto-detect

Linux only: wrap the sandbox in Bubblewrap namespaces (PID/IPC/UTS/cgroup/user + private /tmp) for defense-in-depth. Unset = auto-detect with fallback to Landlock-only.

cplt config set sandbox.use_bubblewrap true
sandbox.quietbooldefault: false

Hide the startup configuration summary (sandbox rules, network, env info).

cplt config set sandbox.quiet true
sandbox.yesbooldefault: false

Skip the confirmation prompt at startup (equivalent to --yes).

cplt config set sandbox.yes true
sandbox.deny_clipboardbooldefault: true

Deny the macOS clipboard (com.apple.pasteboard) to the agent, so `pbpaste` cannot read whatever was last copied. Override for one run with --allow-clipboard. No effect on Linux.

cplt config set sandbox.deny_clipboard false
sandbox.allow_jvm_attachbooldefault: false

Allow JVM Attach API unix sockets for ByteBuddy/MockK/Mockito inline mocking.

cplt config set sandbox.allow_jvm_attach true
sandbox.allow_msbuildbooldefault: false

Allow MSBuild worker-node unix sockets for `dotnet build` (not the persistent MSBuild Server).

cplt config set sandbox.allow_msbuild true
sandbox.gradle_initbooldefault: false

Install a cplt-managed Gradle init script in the Gradle user home ($GRADLE_USER_HOME/init.d/ or ~/.gradle/init.d/cplt-sandbox.gradle) that applies the preferIPv4Stack workaround inside the sandbox. Inert outside sandbox builds.

cplt config set sandbox.gradle_init true
sandbox.deny_nested_gitbooldefault: false

macOS only: refuse to create a .git file, directory or symlink anywhere below a writable root, so a session cannot plant a repository whose config runs on the host (#576). Breaks `git worktree add` and fixtures that create a .git inside the project. No effect on Linux, where the session-end check is the only cover.

cplt config set sandbox.deny_nested_git true
sandbox.refuse_cache_exec_linksbooldefault: false

Linux only: refuse to launch when an allow_cache_exec entry reaches its directory through a symlink inside ~/.cache, instead of granting the link's target with a warning. The agent can write ~/.cache, so it can plant or re-point such a link for the next launch. A symlinked ~/.cache itself is not affected.

cplt config set sandbox.refuse_cache_exec_links true
sandbox.deny_key_files_by_extensionbooldefault: false

macOS only: inside the project and every granted tree (--repo-dir, allow.write, allow.read), deny .pem, .key, .p12, .pfx and .jks files by extension (server.pem, tls.key), not only files named exactly .pem. Breaks anything there that reads a key or certificate: a local HTTPS dev server, key fixtures in tests, a project virtualenv's certifi/cacert.pem (pip and requests fail TLS), and system CA bundles if a grant covers /etc or /opt/homebrew. No effect on Linux.

cplt config set sandbox.deny_key_files_by_extension true
sandbox.protect_pnpm_configbooldefault: false

Keep pnpm's global config dir (~/.config/pnpm, or $XDG_CONFIG_HOME/pnpm) read-only and deny its token files, auth.ini (pnpm 11+) and rc (pnpm 10 and older). Breaks pnpm installs from a private registry whose token is only in those files, and `pnpm config set --global` / `pnpm login` inside the sandbox. config.yaml stays readable. Re-allow a token file with allow.read.

cplt config set sandbox.protect_pnpm_config true
sandbox.refuse_invalid_repo_configbooldefault: false

Refuse to launch when a repository's .cplt.toml cannot be loaded or has a [deny] key this cplt does not recognize, naming the problem, instead of warning and launching without those [deny] rules. Covers the launch repository and every named repository. Catches broken files, not an adversarial session.

cplt config set sandbox.refuse_invalid_repo_config true
sandbox.deny_copilot_dir_execbooldefault: false

Linux only: stop granting execute on ~/.copilot, so the writable directory Copilot keeps its config in is not also a place to run a dropped binary from (#324). Copilot runs its bundled tools from ~/.cache/copilot/pkg. Breaks a plugin, MCP server, LSP server or hook launched as a program stored under ~/.copilot. No effect on macOS, where the launch warns instead.

cplt config set sandbox.deny_copilot_dir_exec true
sandbox.allow_dockerbool⚠ dangerousdefault: false

⚠️ DANGEROUS: Allow Docker/Colima/OrbStack access. Exposes daemon socket and ~/.docker config. Container mounts bypass sandbox.

cplt config set sandbox.allow_docker true --force
sandbox.allow_cache_execstring[]default: []

~/Library/Caches subdirs to allow exec from (e.g. ["ms-playwright"] for Playwright browsers).

cplt config set sandbox.allow_cache_exec ms-playwright
sandbox.allow_cache_exec_anybool⚠ dangerousdefault: false

⚠️ DANGEROUS: Allow exec from ALL ~/Library/Caches subdirs. Prefer allow_cache_exec with specific subdirs.

cplt config set sandbox.allow_cache_exec_any true --force
sandbox.allow_browserbool⚠ dangerousdefault: false

⚠️ DANGEROUS: Launch Services grant for OAuth code flows. Lets the agent launch ANY application outside the sandbox, via launchd. Cannot be scoped.

cplt config set sandbox.allow_browser true --force
sandbox.keychain_substitutebooldefault: true for Copilot and Claude, false for other agents

Drop the macOS Keychain grant when the agent has a credential it can reach without it. Copilot uses `gh auth token`, Claude uses CLAUDE_CODE_OAUTH_TOKEN; both keep the grant without one.

cplt config set sandbox.keychain_substitute true
sandbox.allow_build_credentialsbool⚠ dangerousdefault: false

\u{26a0}\u{fe0f} DANGEROUS: Let the agent read ~/.npmrc, ~/.gradle/gradle.properties and ~/.m2/settings.xml (the files only; read-only on macOS, while Linux leaves the Maven and Gradle files read/write either way). Exposes every registry token in them, not only the one the project uses.

cplt config set sandbox.allow_build_credentials true --force
sandbox.allow_git_worktreesbooldefault: false

Grant read, write and execute on a cplt-owned directory for this repository's sub-agent worktrees (~/.cplt-worktrees/<id>, exported as CPLT_WORKTREE_ROOT). Worktrees and branches there persist after the session. macOS only. User config only; .cplt.toml cannot propose it.

cplt config set sandbox.allow_git_worktrees true
sandbox.worktree_walk_max_dirsintegerdefault: 100000

With allow_git_worktrees on: how many directories the check of the worktree root visits at launch and session end. Past it the launch refuses to start, since a .git beyond it would go unseen. Raise it for large dependency trees.

cplt config set sandbox.worktree_walk_max_dirs 200000
sandbox.gh_proxybooldefault: false

DEPRECATED: use [gh_guard] section instead. Enables gh CLI proxy.

cplt config set sandbox.gh_proxy true
sandbox.git_push_preventionbooldefault: false

DEPRECATED: use [git_guard] section instead. Enables git push prevention.

cplt config set sandbox.git_push_prevention true
gh_guard.enabledbooldefault: true

Enable gh CLI proxy that blocks destructive GitHub operations (delete repo, merge PR, etc.).

cplt config set gh_guard.enabled false --force
gh_guard.modestringdefault: block

Enforcement mode: "block" (deny and exit), "warn" (print warning, allow), or "audit" (silent log).

cplt config set gh_guard.mode audit --force
gh_guard.scope_checkbooldefault: true

Enforce same-repo check. Blocks operations targeting other repositories via the -R flag.

cplt config set gh_guard.scope_check false --force
gh_guard.block_auth_tokenbooldefault: true

Block 'gh auth token' command to prevent credential exfiltration.

cplt config set gh_guard.block_auth_token false --force
gh_guard.inject_tokenbool⚠ dangerousdefault: false

DEPRECATED on macOS: gh gets its token from the gh guard, Copilot from sandbox.keychain_substitute. Still needed on Linux when Copilot's login sits in the Secret Service. Pre-extracts GH_TOKEN into the agent env (Copilot only).

cplt config set gh_guard.inject_token true --force
gh_guard.unknown_commandstringdefault: block

Policy for commands not in the classification table: "block" (default-deny) or "allow" (permissive).

cplt config set gh_guard.unknown_command allow --force
gh_guard.allow_api_writebool⚠ dangerousdefault: false

Allow 'gh api' write operations (POST/PATCH/PUT and input flags). Writes are scope-checked to the current repo. GraphQL is always blocked.

cplt config set gh_guard.allow_api_write true --force
gh_guard.allow_pr_mergebool⚠ dangerousdefault: false

Allow 'gh pr merge' of the account's own PRs, in scope, into a branch where an active ruleset the account cannot bypass requires an approving review that a new push dismisses. '--admin' is always refused.

cplt config set gh_guard.allow_pr_merge true --force
git_guard.enabledbooldefault: true

Intercept git push, request-pull and send-pack. What happens to an intercepted command is `mode`: block (default) refuses it, warn prints and runs it.

cplt config set git_guard.enabled false --force
git_guard.modestringdefault: block

Enforcement mode: "block" (default: deny and exit), "warn" (print warning, allow), or "audit" (silent log).

cplt config set git_guard.mode audit --force
git_guard.prevent_pushbooldefault: true

Treat git push, request-pull and send-pack as violations. `mode` decides what a violation costs.

cplt config set git_guard.prevent_push false --force
git_guard.prevent_force_pushbooldefault: true

Block force push (only meaningful when prevent_push is false).

cplt config set git_guard.prevent_force_push false --force
git_guard.protect_default_branch_onlybooldefault: true

Only block pushes to the default branch (main/master), the default outside --preset strict. Set false to block every push.

cplt config set git_guard.protect_default_branch_only false
git_guard.allow_pushtable[]⚠ dangerousdefault: []

Structured push exceptions. Each entry specifies remote/branches/force conditions under which push is allowed.

cplt config explain git_guard.allow_push
shell.skipstring[]default: []

Agents the PATH shim sync leaves out (e.g. ["goose"] when your goose is pressly/goose, the migration tool). Only matters after `cplt --shell-install --shims`.

cplt config set shell.skip goose

For Nav employees: isolation is required for all AI agent work on Nav equipment, personal work included. cplt is the recommended way. Any other route has to give equivalent isolation, and that is your responsibility to set up.

Sandboxing er påkrevd på Nav-utstyr (Norwegian)

Trust the kernel, not the agent.

Open source, MIT licensed, built at Nav.

GitHubSecurity PolicyMIT License