* docs: v0.8.46 CHANGELOG — platform archives, palette, sub-agents, sandbox, web install, search fixes Closes #2188 * feat(v0.8.46): quick fixes — palette, model picker Esc, sub-agent sidebar, shell chip, model name casing, CVE bump (#2212) * fix: bump qs to >=6.15.2 for CVE-2026-8723 Add qs override in feishu-bridge package.json to force transitive dependency resolution to >=6.15.2, addressing CVE-2026-8723. Refs: #2198 * fix: Esc in model picker applies last-highlighted choice Previously Esc reverted to the initial model when the user hadn't moved the selection. Now Esc always applies the currently highlighted model and thinking-effort tier, making Esc consistent with Enter. Also updates the picker footer hint from 'Esc cancel' to 'Esc apply'. Refs: #2196 * feat: show '⏳ shell running' chip in TUI footer Adds a footer_shell_chip function that displays a '⏳ shell running' status chip in the footer's right cluster whenever a foreground shell command is active via exec_shell. The chip is always visible regardless of user-configured status items. Refs: #2194 * feat: auto-collapse finished sub-agents in sidebar When a sub-agent completes (status = 'done'), its detail lines (id, steps, duration, progress) are now hidden in the sidebar agents panel. Only the summary label line is shown, keeping the sidebar compact. Running agents still show full detail. Refs: #2195 * feat: refresh Whale dark palette for better contrast Improve contrast and layer separation in the Whale dark theme: - Deepen base background for more depth (10,17,32) - Lighten panel (22,34,56) for clearer distinction from bg - Lighten elevated surface (36,52,78) for better elevation - Lighten selection (48,68,100) for clearer selected state - Boost text hint (138,150,174) and dim (118,130,156) readability - Brighter border (52,88,145) for better edge definition - Update tool surface colors for consistency Refs: #2197 * fix: preserve model name casing in normalize_model_name_for_provider When the user enters a model name like 'DeepSeek-V4-Flash', the normalizer was lowercasing it to 'deepseek-v4-flash' via the canonical_official_deepseek_model_id function. Now the normalizer preserves the caller's casing when the input already matches a known model id case-insensitively. Compact aliases like 'deepseek-v4pro' are still rewritten to 'deepseek-v4-pro'. Refs: #2109 * feat(web): install download tile with arch detection, SHA256, China mirrors + companion binary fix (#2213) * fix(web): download both codewhale and codewhale-tui binaries in install snippets The SNIPPETS map only fetched one binary per platform, causing the dispatcher to fail with MISSING_COMPANION_BINARY. Every arch now downloads both codewhale AND codewhale-tui side-by-side. - macOS/Linux: added second curl + combined chmod/xattr/mv for tui - Windows: added second Invoke-WebRequest for codewhale-tui.exe - VERIFY: PowerShell now hashes both binaries; Unix --ignore-missing covers all present binaries in a single sha256sum pass * feat(web): add install download tile with arch detection, SHA256, and China mirrors (#2192) * feat(sandbox/linux): process hardening — PR_SET_DUMPABLE, NO_NEW_PRIVS, RLIMIT_CORE (#2214) * feat(sandbox/linux): add process hardening module — PR_SET_DUMPABLE, NO_NEW_PRIVS, RLIMIT_CORE (#2183) * feat(sandbox/linux): seccomp filter + bwrap passthrough - seccomp: BPF filter whitelisting safe syscalls, denying ptrace/mount/kexec and other dangerous syscalls. Uses raw BPF instructions via libc prctl to avoid external dependencies (#2182). - bwrap: optional bubblewrap passthrough when /usr/bin/bwrap is present and [sandbox] prefer_bwrap=true in config. Creates read-only rootfs with write access limited to the working directory (#2184). - landlock detect_denial extended to recognize seccomp SIGSYS/"Bad system call" patterns alongside existing Landlock EACCES/EPERM detection. - SandboxManager gains prefer_bwrap field; set_prefer_bwrap on ShellManager. - EngineConfig gains prefer_bwrap field, wired through main/ui/runtime_threads. - Diagnostics now reports bwrap_available and cgroup_version. - config.example.toml documents the prefer_bwrap key. Pre-existing clippy fixes picked up in the same build: - collapsible_if in ui.rs version-check - cmp_owned in goal.rs test - consecutive str::replace in normalize_auth_mode Closes #2182, closes #2184 * docs: add cross-links to issue and PR templates in CONTRIBUTING.md (#2215) - Link .github/ISSUE_TEMPLATE/bug_report.md and feature_request.md from the Reporting Issues section - Link .github/PULL_REQUEST_TEMPLATE.md from the Pull Request Guidelines section * feat(release): bundle platform archives with install scripts (#2216) - Add bundle job to release workflow that creates per-platform archives (tar.gz for Linux/macOS, .zip for Windows) containing both codewhale and codewhale-tui binaries plus install scripts - Create install.bat (Windows) — copies binaries to %USERPROFILE%\bin - Create install.sh (Unix) — copies binaries to ~/.local/bin - Windows gets a portable .zip variant without install script - Release notes updated to promote archives as primary download method - Individual binaries retained for npm wrapper and scripting Closes #2193 * fix(web_search): fall back to DuckDuckGo when Bing returns zero results (#2130) When the configured search provider is Bing and the query returns zero results (common for technical/compound queries), fall through to the DuckDuckGo path instead of reporting empty. A provenance message is surfaced: "Bing returned no results; used DuckDuckGo fallback". Also adds Security and Code of Conduct cross-links to CONTRIBUTING.md per the sub-agent renovation (#2203). * docs: SANDBOX.md threat model + RFCs for persistence and MCP + SandboxExecutor trait - docs/SANDBOX.md: complete threat model describing each platform's sandbox (Seatbelt, Landlock, seccomp, process hardening, bwrap, Windows v1). Covers defense-in-depth layering, config keys, denial detection, limitations. - docs/rfcs/2189-persistence-sqlite.md: RFC for SQLite migration (drafted by sub-agent) - docs/rfcs/2190-mcp-modularization.md: RFC for MCP crate split into protocol/client/server with OAuth support - crates/tui/src/sandbox/policy.rs: SandboxExecutor trait definition and SafetyLevel→SandboxPolicyBehavior mapping function with tests Closes #2180, closes #2186, closes #2189, closes #2190 * feat: sandbox parity tests + remove sub-agent 100-turn cap - Add sandbox parity tests covering platform detection, denial patterns, bwrap preference, and policy consistency across modes (#2187) - Remove arbitrary 100-turn sub-agent cap: DEFAULT_MAX_STEPS changed from 100 to u32::MAX. Sub-agents now run until they produce a final text response, are cancelled by the parent, or hit a configured explicit budget (#2034) Closes #2187, closes #2034
10 KiB
Sandbox threat model
CodeWhale executes shell commands spawned by AI reasoning. The sandbox module restricts what those commands can do to the host system. This document describes what each platform's sandbox actually enforces, what is best-effort, and what is explicitly out of scope.
Platform overview
| Mechanism | Platform | Type | Status |
|---|---|---|---|
| Seatbelt | macOS | Mandatory access control | Enforced |
| Landlock | Linux | Filesystem access control | Enforced |
| seccomp BPF | Linux | Syscall filter | Enforced |
| Process hardening | Linux | Kernel prctl / rlimit | Enforced |
| Bubblewrap (bwrap) | Linux | Namespace isolation | Optional |
| Windows Job Object | Windows | Process-tree containment | v1 (PR #2220) |
Threat model: what each layer addresses
1. Process hardening (Linux only)
When it runs: Before any threads are spawned, before Tokio boots, before any data is loaded into memory.
What it does:
PR_SET_DUMPABLE=0— prevents ptrace, makes/proc/<pid>/root-ownedPR_SET_NO_NEW_PRIVS=1— irreversible; no child can ever gain privilegesRLIMIT_CORE=0— no core dumps, so sensitive data never hits disk
What it protects against:
- Process inspection via ptrace/strace/gdb
- Privilege escalation via setuid/setgid/fscaps
- Core dumps leaking API keys, tokens, prompt content
What it does NOT protect against:
- A compromised child reading its parent's
/proc/<pid>/mem(already blocked byPR_SET_DUMPABLE=0making/proc/<pid>/root-owned) - Kernel exploits that bypass prctl
2. Landlock (Linux, kernel 5.13+)
When it runs: Applied to each child process at spawn time via a
helper script or landlock_restrict_self. Only restrictable by the
process itself — parent cannot force Landlock on a child.
What it does:
- Restricts filesystem access to a whitelist of paths
- Handles:
EXECUTE,READ_FILE,READ_DIR,WRITE_FILE,REMOVE_DIR,REMOVE_FILE,MAKE_DIR,MAKE_REG,MAKE_SYM,TRUNCATE
What it protects against:
- Reading files outside the workspace (e.g.,
/etc/passwd,~/.ssh) - Writing to system directories (
/usr,/bin,/lib) - Creating or deleting files in protected locations
What it does NOT protect against:
- Network access (Landlock is filesystem-only)
- Process inspection (use seccomp for this)
- Reading files that are already mapped (Landlock applies at
open()time)
Detection: detect_denial() checks stderr for Permission denied,
Operation not permitted, EACCES, EPERM.
3. seccomp BPF (Linux only)
When it runs: Installed via prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)
on the child process.
What it does:
- Whitelist of ~100 safe syscalls (file I/O, memory, process, IPC, synchronization, signals, time)
- Explicitly denied:
ptrace,mount,umount2,kexec_load,kexec_file_load,init_module,finit_module,delete_module,bpf,reboot,swapon,swapoff,pivot_root,setuid/setgid/setreuid/setregid/setresuid/setresgid,personality - Any syscall not on the whitelist →
SECCOMP_RET_KILL_PROCESS(SIGSYS)
What it protects against:
- Process hijacking via ptrace
- Mounting filesystems (bypassing Landlock read-only restrictions)
- Loading kernel modules
- Loading BPF programs (would bypass seccomp itself!)
- Rebooting the system
- Privilege changes via setuid/setgid
What it does NOT protect against:
- Legitimate use of allowed syscalls for malicious purposes
- Side-channel attacks via allowed syscalls (e.g., timing)
Detection: detect_denial() checks exit code 31 (SIGSYS) or stderr
for Bad system call, bad system call, SIGSYS, seccomp.
4. Bubblewrap / bwrap (Linux, optional)
When it runs: If /usr/bin/bwrap is present AND the config key
[sandbox] prefer_bwrap = true is set. Runs as an outer wrapper around
the child command.
What it does:
- Creates a new mount namespace with
--unshare-all - Read-only bind-mounts the entire root filesystem
- Bind-mounts the workspace directory with read-write access
- Changes into the workspace with
--chdir
What it protects against:
- Any filesystem write outside the workspace (stronger than Landlock alone because it's enforced at the namespace level, not just filesystem access)
- Accidental modification of system files
What it does NOT protect against:
- Network access (bwrap does not create a network namespace by default with
--unshare-all; the child still has full network access) - Process inspection
- Memory attacks
Installation: User must install bubblewrap themselves:
- Ubuntu/Debian:
apt install bubblewrap - Fedora:
dnf install bubblewrap - Arch:
pacman -S bubblewrap
CodeWhale does NOT vendor bwrap.
Fallback: If bwrap is not installed, the sandbox falls back to Landlock only.
5. Seatbelt (macOS)
When it runs: Applied via the sandbox-exec wrapper command. The
seatbelt profile is generated dynamically based on the SandboxPolicy.
What it does:
- Restricts filesystem access based on the policy profile
- Can restrict network access (when
network_access: false)
What it protects against:
- Reading/writing files outside allowed paths
- Network connections (when configured)
What it does NOT protect against:
- Process inspection (Seatbelt does not block ptrace)
- Syscall-level attacks
Detection: Checks stderr for file-write and network denial patterns.
6. Windows Job Object (v1, PR #2220)
When it runs: Applied at process spawn time via
PROC_THREAD_ATTRIBUTE_JOB_LIST and restricted token assignment.
What it does (v1):
- Job Object with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE— all child processes terminate when the parent exits - Memory cap: 1 GB per process, 2 GB per job
- Active process limit: 64
- UI restrictions: no desktop handle access
- Restricted token: drops Administrators group SID, sets medium-low integrity level
What is deferred (v2):
- WFP (Windows Filtering Platform) firewall rules — network is open in v1
- Filesystem ACL integration at spawn time (stub exists)
- AppContainer isolation
- Registry key isolation
Detection: Checks stderr for Access is denied, STATUS_ACCESS_DENIED,
ERROR_ACCESS_DENIED, ERROR_PRIVILEGE_NOT_HELD,
ERROR_ACCESS_DISABLED_BY_POLICY, and integrity/AppContainer patterns.
Defense in depth
The Linux sandbox applies layers in order:
Process hardening (prctl) ← before threads
↓
Landlock (filesystem) ← at child spawn
↓
seccomp BPF (syscalls) ← at child spawn
↓
bwrap (namespace isolation) ← optional outer wrapper
Each layer addresses a different threat surface. seccomp cannot protect the filesystem (that's Landlock's job). Landlock cannot stop ptrace (that's seccomp + PR_SET_DUMPABLE). bwrap adds namespace-level isolation that neither Landlock nor seccomp can provide.
Configuration
Relevant config keys in ~/.codewhale/config.toml:
# Sandbox policy mode
sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access | external-sandbox
# Linux bubblewrap passthrough
prefer_bwrap = false # requires `bubblewrap` package installed
# External sandbox backend
sandbox_backend = "none" # "none" or "opensandbox"
sandbox_url = "http://localhost:8080"
sandbox_api_key = "YOUR_API_KEY"
Environment variable overrides:
DEEPSEEK_SANDBOX_MODE→sandbox_modeDEEPSEEK_PREFER_BWRAP=true→prefer_bwrapDEEPSEEK_SANDBOX_BACKEND→sandbox_backendDEEPSEEK_SANDBOX_URL→sandbox_urlDEEPSEEK_SANDBOX_API_KEY→sandbox_api_key
Detecting sandbox denials
When a command fails, the sandbox manager checks for denial patterns:
| Platform | Denial mechanism | Exit code | Stderr patterns |
|---|---|---|---|
| macOS Seatbelt | sandbox-exec violation | non-zero | file-write, network |
| Linux Landlock | EACCES / EPERM | non-zero | Permission denied, Operation not permitted |
| Linux seccomp | SIGSYS (31) | 31 or 159 | Bad system call, SIGSYS |
| Linux bwrap | Mount/namespace failure | non-zero | varies |
| Windows | Access denied / privilege | non-zero | Access is denied, ERROR_PRIVILEGE_NOT_HELD |
The was_denied() method on SandboxManager aggregates all platform-specific
checks. The denial_message() method returns a human-readable explanation.
Limitations
What the sandbox does NOT protect against
- Network attacks — only macOS Seatbelt can block network; Linux and Windows v1 leave network open
- Memory attacks — no platform prevents a child process from reading its own memory or exploiting memory corruption bugs
- Timing side channels — allowed syscalls on Linux can be used for timing-based information leaks
- Resource exhaustion — the Linux job object limits memory and process count, but does not limit CPU, file descriptors, or disk I/O
- Kernel vulnerabilities — if the kernel itself has a vulnerability, the sandbox cannot prevent exploitation (this applies to all platforms)
- Supply chain — if the child process downloads and executes untrusted code, the sandbox limits what that code can do, but does not prevent the download
Platform-specific gaps
- Linux: Landlock only protects filesystem access. seccomp adds syscall filtering but uses a whitelist that may need updates for new syscalls.
- macOS: Seatbelt profiles are generated at runtime. A misconfigured profile could be too permissive.
- Windows v1: No filesystem ACL enforcement at spawn time. Network is fully open. Job Object is process-tree only.
Related
crates/tui/src/sandbox/— implementationcrates/config/src/lib.rs— config keyscrates/tui/src/tools/diagnostics.rs—diagnosticstool reportssandbox_available,sandbox_type,bwrap_available,cgroup_versionconfig.example.toml— annotated config reference- Issue #2180 — this document
- Issue #2182 — seccomp filter implementation
- Issue #2183 — process hardening
- Issue #2184 — bwrap passthrough
- Issue #2185 — Windows Job Object v1
- Issue #2186 — SandboxExecutor trait unification
- Issue #2187 — sandbox parity tests