Changelog¶
Changelog¶
All notable changes to this project will be documented in this file.
Unreleased¶
v0.17.0 - 2026-04-30¶
Added¶
- Audit-grade structured logging. Per-session fields (
session_id,sandbox_name,sandbox_path,project_dir,isolator,pid,devsandbox_version) on every dispatched entry, plus synthesizedsession.start/session.endlifecycle events and security events (proxy.filter.decision,proxy.redaction.applied,proxy.credential.injected,proxy.mitm.bypass,mount.decision,notice.overflow). See Audit Logging. - OTLP
header_sources. Resolve receiver headers fromvalue/env/fileat runtime so secrets stay on the host. See Authenticating to an Auth-Enforced Endpoint. NODE_USE_ENV_PROXY=1is now set automatically in proxy mode so Node.js ≥24's built-infetch(undici) honorsHTTP(S)_PROXY- fixesENETUNREACHfrom npx-based tools likemcp-remote.
v0.16.0 - 2026-04-29¶
Added¶
- Proxy
log_skiprules. Drop matching requests from the proxy log (local + remote dispatchers); the request itself still passes through. See Skipping Log Entries.
v0.15.0 - 2026-04-29¶
Added¶
devsandbox sandboxes prune --orphanedflag to restrict pruning to orphaned sandboxes (those whose original project directory no longer exists). The flag intersects with other selectors:--orphaned --older-than 30dremoves orphans last used over 30 days ago;--orphaned --keep Nprunes orphans outside the N most-recently-used set;--orphaned --all(or--orphanedalone) removes every orphan. Without the flag, the existing default (orphans-only when no other selector is set) is unchanged.- Generic credential injector for proxy. Define credential injection by
host+header+value_format+[...source]+overwritein TOML - no Go code change required to add a new service. Built-ingithubpreset preserves existing config compatibility ([proxy.credentials.github] enabled = trueworks unchanged, includingGITHUB_TOKEN→GH_TOKENfallback). Specificity-based ordering when multiple injectors could match the same request (exact host > longer literal > shorter glob, tie-break by name).BuildCredentialInjectorsnow returns an error for invalid configs (unknown preset, missinghost/header, invalid glob).
v0.14.1 - 2026-04-28¶
Changed¶
zellijtool is now disabled by default. Unlikekitty, the zellij socket has no capability filtering - exposing it lets sandboxed code drive the host multiplexer (run commands in any pane, read pane contents, etc.). Auto-detection of an activeZELLIJsession no longer mounts the socket or forwardsZELLIJ*env vars on its own. Set[tools.zellij] enabled = trueto opt back in.devsandbox tools check zellijreports the opt-in requirement.
v0.14.0 - 2026-04-27¶
Added¶
codex,opencode, andpitools now honor their respective custom config-location env vars on the host and forward them into the sandbox so the CLIs resolve the same paths inside:codex:CODEX_HOMEoverrides~/.codex. When set, the host value is passed through and the directory is mounted at the same path.opencode:OPENCODE_CONFIG_DIRis mounted in addition to (not in place of)~/.config/opencode, matching opencode's load semantics; the env var is forwarded.pi:PI_CODING_AGENT_DIRoverrides~/.pi/agent. The agent dir is still tmpoverlayed (settings/credentials are write-discarded) and thesessions/subdirectory is still persisted; the env var is forwarded.
v0.13.3 - 2026-04-20¶
Fixed¶
kittyproxy revdiff launch pattern now accepts the unquoted/usr/bin/envprefix the launcher actually emits (onlyENV_PREFIXassignments and the inner argv are single-quoted). The literal absolute path is required - bareenv(PATH-relative) still rejects, so$PATHshadowing can't be used to bypass the inner-program check.
v0.13.2 - 2026-04-20¶
Fixed¶
kittyproxy revdiff launch pattern: addedMatchShellExecEnvSentinel, acceptingsh -c "'/usr/bin/env' 'KEY=VAL' ... '<prog>' '<arg>'...; touch '<sentinel>'". The revdiff launcher injects anenvwrapper so the kitty-spawned overlay inheritsEDITOR/VISUALfrom the caller's login shell; the previous pattern matched only the no-env form. Env-var names are restricted to^[A-Z_][A-Z0-9_]*$, the inner argv is still validated against the existing revdiff pattern, and the sentinel-tail rules (no shell metacharacters, canonical path) are unchanged.
v0.13.1 - 2026-04-18¶
Fixed¶
revdifftool no longer wipes its shared IPC directory onStart/Stop. Because the dir is exported as$TMPDIRfor every sandboxed process, long-lived tenants (Claude Code's per-session task cache under$TMPDIR/claude-<uid>/…/tasks/, Node's compile cache, Go's build cache) populate subtrees that must survive sandbox restarts for the same project - and parallel sandboxes on the same project share the directory, so wiping it from one tore state out from under the others. The oldRemoveAllonStartcould yank state out from under a running caller; Node's non-recursivefs.mkdirSyncthen failed withENOENT, breaking every subsequent Claude Code Bash tool call.Startnow only ensures the dir exists (0700);Stopis a no-op. Stale revdiff sentinels are harmless - the launcher usesmktempwith fresh names.
v0.13.0 - 2026-04-17¶
Added¶
[sandbox.environment.<NAME>]config block: declare sandbox environment variables using the same source model as proxy credentials (value/env/file, priorityvalue > env > file).env = "X"withXunset on the host silently skips the variable; an unreadablefile = "..."is a startup error. Declaring the same variable in bothenv_passthroughandenvironmentfails at startup with a message naming the variable - each variable belongs in exactly one place.pitool: integrates Pi Coding Agent.~/.pi/agentis mounted with credential protection;~/.pi/agent/sessionspersists across runs.[proxy.credentials.github] overwrite = true: force-replace any existingAuthorizationheader on outgoingapi.github.comrequests. Intended for the pattern where a sandboxed CLI (e.g.gh) refuses to start without a token in its environment - pass a placeholder throughenv_passthrough/sandbox.environmentwhile the real token stays on the host and is swapped in by the proxy. Default remainsfalse(existing tool-set headers are preserved).revdifftool now provides a shared IPC directory (~/.cache/devsandbox/revdiff-ipc/<session>/) bind-mounted at the same path on both sides and exported asTMPDIR. The kitty-spawned overlay shell runs on the host and receives sentinel/output paths as literal strings, so host and sandbox must agree on the string - argv-shipped paths needSource == Destequality, not just a shared inode.
Changed¶
- Kitty tool now runs a capability-filtering proxy instead of bind-mounting the host socket. The host kitty remote-control socket is no longer exposed inside the sandbox; a local proxy at
$HOME/.kitty.sockis exposed instead, andKITTY_LISTEN_ONis rewritten to point at it. Sandboxed processes can only issue kitty commands declared as capabilities by an enabled tool (launch_overlay,launch_window,launch_tab,launch_os_window,close_owned,wait_owned,focus_owned,send_text_owned,get_text_owned,set_title_owned,list_owned), and*_ownedcommands are scoped by ownership tracking to windows the sandbox itself opened. Shell metacharacters insh -cpayloads forlaunch_*are rejected outright.remote_control_passwordis unsupported - useallow_remote_control = socket-only. New[tools.kitty]fields:mode(autodefault /disabled/enforce) andextra_capabilities(additive;launch_*entries rejected). Underauto, the proxy only starts when at least one enabled tool declares a capability - zero attack surface when no tool needs kitty.revdiffis the built-in consumer.
Fixed¶
- macOS: shortened test directory names to stay under the platform's unix socket path length limit (affected
kittyproxyandkittytool tests).
v0.12.0 - 2026-04-16¶
Changed¶
- Wrapper diagnostic output (port-forward notices, session warnings, proxy setup info, container progress) no longer writes directly to stderr while a child process owns the terminal. Messages are written to
$XDG_STATE_HOME/devsandbox/wrapper.log(or~/.local/state/devsandbox/wrapper.log) and a one-line banner is shown on exit if anything was suppressed. This prevents wrapper output from corrupting TUI applications (Claude Code, aider, etc.) running inside the sandbox. Pass--verboseor setDEVSANDBOX_DEBUG=1to restore the old behavior of writing every message to stderr.
Added¶
--worktreeand--worktree-baseflags: opt-in git-worktree mode. Bare--worktreeauto-generatesdevsandbox/<session-or-timestamp>off HEAD;--worktree=<branch>reuses or creates a named branch. The sandbox CWD is the worktree; the main checkout is untouched. With--git-mode=readwrite, commits land on the worktree branch only.--rmremoves the worktree on exit viagit worktree remove --force+prune.--worktree+--git-mode=disabledis rejected at flag-parse time. Worktrees live at~/.local/share/devsandbox/<project-slug>/worktrees/<branch>/and the slug is derived from the main repo root so sibling worktrees share sandbox state.devsandbox sandboxes pruneand thedoctorcommand are worktree-aware.
Fixed¶
zellijandkittytool socket bindings are now explicit bind mounts (Type: MountBind) instead of inheriting the default tmpoverlay fromCategoryRuntime. Overlayfs cannot expose a unix socket from its lower layer, so under the previous policy the host socket was invisible inside the sandbox andzellij list-sessions/kitten @silently failed.- Auto port-forwarding no longer tries (and fails) to forward when the sandbox shares the host network namespace. Without proxy mode the sandbox uses bwrap's
--share-net, so a tool listener inside the sandbox is the same kernel socket as the "host" bind the forwarder would attempt - producing a spuriousbind: address already in useerror for every detected port. Auto-detect now inspects the sandbox netns inode and skips forwarding (with a one-line explanatory message) when it matches the host; the sandbox ports are already directly reachable on127.0.0.1. For the rare case where auto-forward runs in a properly isolated netns but the host happens to already have that port in use, the forwarder falls back to an ephemeral host port and logs the mapping instead of silently dropping the service.
v0.11.0 - 2026-04-14¶
Added¶
zellijtool forwards an active Zellij session into the sandbox by mounting the session socket directory and thezellijbinary. Auto-detected whenZELLIJis set and the binary is onPATH, sozellijcommands run inside the sandbox attach to the host multiplexer.zellijtool now also mounts$XDG_RUNTIME_DIR/zellij/, which is where zellij 0.41+ stores its IPC socket (the legacy/tmp/zellij-$UID/holds only cache/log files on modern releases). The override env var isZELLIJ_SOCKET_DIR(previously the tool checked the incorrectZELLIJ_SOCK_DIR).
v0.10.0 - 2026-04-10¶
Added¶
kittytool forwards the Kitty remote-control socket into the sandbox sokitten @commands inside the sandbox can drive the host terminal.
v0.9.3 - 2026-04-08¶
~/.local/binand~/.local/share/claudeare now read-only bind mounts instead of persistent writable overlays. Under the split-mode default introduced in v0.8.0 these host-managed tool-install directories were being treated asCategoryData, which let in-sandbox tool self-updaters (e.g. Claude Code's own updater) write partial/empty files into the per-project overlay upper-dir. Those writes shadowed the real host binaries in every subsequent session, causing failures likefish: '/home/$USER/.local/bin/claude' exists but is not an executable file(exit 126).
v0.9.2 - 2026-04-08¶
Fixed¶
- HTTP proxy no longer intercepts the body of HEAD requests. The previous behavior broke
Content-Lengthhandling and caused errors for some clients (e.g. Helm pulling OCI charts).
v0.9.1 - 2026-04-07¶
Fixed¶
- Sandbox removal now
chmods files recursively before deletion. Go populates its build cache with0500files, which previously caused sandbox cleanup to fail.
v0.9.0 - 2026-04-07¶
Added¶
devsandbox scratchpad [name] [command...]subcommand for running sandboxes in managed, clean working directories under~/.local/share/devsandbox-scratchpads/. State persists between runs. Name defaults todefault.devsandbox scratchpad listanddevsandbox scratchpad list --jsonlist scratchpads with size and state info.devsandbox scratchpad rm <name>(with--all,--keep-state,--force) removes scratchpads and their sandbox state.
Fixed¶
- Git tool now strips sensitive fields from
.git/configin place instead of replacing the file wholesale. The previous full replacement caused the git CLI to refuse to operate even for read-only commands inside the sandbox.
v0.8.2 - 2026-04-06¶
Fixed¶
- Claude tool stores project knowledge under the
datasection so chat history persists between sandbox runs.
v0.8.1 - 2026-04-06¶
Added¶
- macOS support for the devsandbox shim via a platform-specific copy-on-start overlay implementation, split from the Linux path.
jqis now included in the default Docker image.
Changed¶
- Debian base image bumped in the Docker image.
- mise-managed tool dependencies bumped.
- Docker and lint CI workflows limit concurrency to avoid redundant runs.
Fixed¶
- Restored shim source files that were missing from the v0.8.0 release and added CI coverage so the shim is built and verified on every run.
v0.8.0 - 2026-04-05¶
Breaking Changes¶
[overlay] enabledremoved - replaced by[overlay] defaultwhich accepts:split(default),overlay,tmpoverlay,readonly,readwrite.[tools.mise] writableandpersistentremoved - use[tools.mise] mount_modeinstead. Mise no longer has tool-specific overlay configuration; use the unifiedmount_modesystem.- Default mount behavior changed - tool mounts now default to
splitoverlay policy (configs → tmpoverlay, caches/data/state → persistent overlay) instead of read-only bind mounts. This prevents supply chain attacks from poisoning host tool configurations through sandboxed package managers.
Migration Guide¶
| Before | After |
|---|---|
[overlay] enabled = true |
[overlay] default = "split" (or omit - it's the default) |
[overlay] enabled = false |
[overlay] default = "readonly" |
[tools.mise] writable = true, persistent = true |
[tools.mise] mount_mode = "overlay" |
[tools.mise] writable = true, persistent = false |
[tools.mise] mount_mode = "tmpoverlay" |
| (no equivalent) | [tools.git] mount_mode = "readwrite" |
Added¶
- Binding categories - tools now classify each mount as
config,cache,data,state, orruntime, enabling differentiated overlay policies. [overlay] default- global mount mode for all tool bindings with five modes:split,overlay,tmpoverlay,readonly,readwrite.- Per-tool
mount_mode- override the global default for specific tools (e.g.,[tools.git] mount_mode = "readwrite"). Acceptsdisabledto prevent a tool's config from being mounted entirely.
Changed¶
- Tool bindings no longer hardcode
ReadOnlyorType- the builder resolves these based on the mount mode policy chain (per-tool > global > split). - Claude, Copilot, Codex, and OpenCode tools previously mounted configs read-write; they now follow the global mount mode (default: tmpoverlay for configs).