Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Deliberate defaults — do not reverse

These defaults were chosen on purpose. Reversing one needs a new reason, not a rediscovery of the old trade-off.

Native mirroring is the default

writableCwd = true (live host edits), the share.* allowlist on (settings / claudeMd / commands / agents / skills reuse host ~/.claude), and share.gitConfig = true (commit as you, with your aliases/ignores) make ccvm behave like native claude. Isolation (read-only project, no config) is the opt-in.

Do not re-propose “secure by default” — that was the original spec and was deliberately reversed. The out-of-the-box win is containment plus the host login never crossing, not project-exfiltration resistance, a deliberate DevEx choice.

RAM-only is the default; the disk pool and in-VM nix are opt-in

vmDiskSize = 0 (no disk, pure RAM) and nix.enable = false (read-only /nix/store, no in-VM nix, lean closure) are the defaults.

The user-facing option is programs.ccvm.nix.enable; the internal config + guest module use the same nested nix.enable name end to end. It is build-time — it flips nix.enable and rebuilds the store as a writable overlay in the initrd. Its overlay upper is tmpfs by default; combine with vmDiskSize > 0 and the initrd LUKS oneshot relocates that upper onto the encrypted disk (fail-open to tmpfs), so a large nix develop doesn’t OOM guest RAM — one shared pool also backs /scratch.

The guest always boots off the self-contained squashfs store; the host store is never the guest’s boot store.

To give in-VM nix extra pre-built paths, point it at a binary cache via nix.substituters + nix.trustedPublicKeys — pure guest-closure config, baked into the guest’s nix.conf (appended to cache.nixos.org and nixpkgs’ keys), HTTP substitution at line rate, no host credentials. require-sigs stays ON. A public-read signed cache works with zero secrets; a cache behind a token/netrc is not yet supported.

Two predecessors were deliberately removed — do not re-add: mountHostNixStore (host store as the guest’s boot store) and nix.useHostStoreAsCache (host /nix/store + DB over ro 9p as a local substituter): 9p copy ran slower than downloading (<1 MiB/s vs. network), and it exposed the entire host store to the agent. See Design decisions → in-VM nix.

agentSudo is auto, not a fixed default

The agent has passwordless root in the guest EXCEPT when egressAllowlist is set, where it auto-drops so the in-guest egress firewall can’t be flushed (the firewall is installed by a root systemd unit, not the agent).

Coupled to it (guest/default.nix): Nix trusted-users is gated on the SAME flag — [ "root" ] ++ lib.optional cfg.agentSudo "ccvm" — so when sudo is off the agent is also non-trusted. This is load-bearing under nix.enable: a Nix trusted-user is root-equivalent (a post-build-hook runs as root), so leaving the agent trusted would hand straight back the root the sudo-drop removes, letting it nft flush the firewall (audit S-1). A non-trusted agent can still nix build / nix develop; builds run as the nixbld users.

null = auto; true / false force it — but forcing true alongside an egressAllowlist re-opens the nft flush bypass (and re-grants trusted-user), so it’s only sensible behind host-side egress control. Build-time (rebuilds the guest closure).

Guest kernel/userspace hardening is deliberate, cheap, and boot-safe

See Security invariants → guest hardening for the full list: security.protectKernelImage, the sysctl set, sudo-rs, the explicit root password lock, and the pinned allowed-users. Notably not done: lockKernelModules (incompatible with the seed service’s runtime modprobe) and disabling unprivileged user namespaces (the nix build sandbox needs them; namespaced-root can’t reach the init-netns firewall — audit-verified, not a containment hole).

extraClaudeMd is default-on context, not a flag

A built-in blurb is staged as the guest’s ~/.claude/CLAUDE.md (via the seed, appended to any host-shared one — never clobbering it) so the agent knows it’s in ccvm. It must stay seed-delivered, never become --append-system-prompt (which would break transparent passthrough). The wrapper prepends a runtime mode line (rw=live / overlay=discarded) that the build-time file can’t know.

Transparent passthrough

The wrapper injects no flags. Everything after ccvm is forwarded to claude verbatim, including --dangerously-skip-permissions. The only args the wrapper consumes are its own: --shell, --ccvm-debug, --writable-cwd, --read-only-cwd, --ccvm-help, --ccvm-version — all deliberately ccvm-specific names (none is a claude flag), so bare --help / --version still reach claude. Preserve that interception boundary.

Image paste — image-only reverse clipboard bridge (clipboard.images, default-on)

The full design and threat analysis live in Security → Image-paste bridge. In short: the bridge restores Ctrl+V image paste over the existing SSH connection, is image-only (host clipboard text never crosses), and opens no new network hole.