Landlock no longer handles TCP, the Seatbelt profile drops (deny network*), and the seccomp filter stops refusing IPv4/IPv6 sockets. Sandboxed commands run without asking in auto mode on every backend (Low-IL no longer asks for network commands), and connection errors are not treated as sandbox blocks.
9.5 KiB
Platform support: Linux, WSL, macOS
CST-Agent targets POSIX systems: Linux (glibc and musl), WSL 2 and macOS.
Native Windows builds are covered separately in README-windows.md.
Build
| Platform | Dependencies | Build |
|---|---|---|
| Debian / Ubuntu / WSL | build-essential pkg-config libcurl4-openssl-dev libncurses-dev |
make |
| Fedora | gcc make pkgconf libcurl-devel ncurses-devel |
make |
| Arch | base-devel curl ncurses |
make |
| Alpine (musl) | build-base pkgconf curl-dev ncurses-dev |
make |
| NixOS | nix-shell (uses shell.nix) |
make |
| macOS | xcode-select --install, brew install ncurses curl pkg-config |
make |
make install PREFIX=~/.local installs the binary; CMake works too
(cmake -B build && cmake --build build && ctest --test-dir build).
The Makefile picks ncursesw, then ncurses, through pkg-config. On macOS it
adds Homebrew's keg-only ncurses/curl to PKG_CONFIG_PATH; without
Homebrew it falls back to the system -lncurses (wide-character capable).
Verified so far: NixOS x86_64 with gcc and clang (-Werror, make check,
make asan, ctest), and every source compiled -fsyntax-only with
x86_64-unknown-linux-musl-gcc without warnings. Full musl, macOS and WSL
builds run in CI (.gitea/workflows/ci.yml; the macOS job needs a runner and
the HAVE_MACOS_RUNNER variable) or by hand with the checklist below.
Files
| What | Where |
|---|---|
API keys (one per provider, entered in the TUI or with --ask-key) |
$XDG_CONFIG_HOME/cst-agent/credentials (default ~/.config/cst-agent/), mode 0600 |
| Last provider, models, effort and approval mode | $XDG_CONFIG_HOME/cst-agent/settings.json |
| Sessions, undo snapshots, exports | <workspace>/.agent/ (git-ignored automatically) |
| Saved permission rules | <workspace>/.agent/permissions.json |
| Project notes injected into the prompt | <workspace>/AGENTS.md (or CST.md) |
Shell sandbox
In the default auto approval mode, run_shell runs inside an OS sandbox
when one is available. Commands may read and execute anything but may only
write beneath the workspace, the temp directory, /dev/null and /dev/tty.
The network is not restricted.
| Platform | Sandbox | Notes |
|---|---|---|
| Linux 5.13+ | Landlock (raw syscalls, no extra library) | |
| Linux 5.5+ without Landlock (Android / Termux, WSL, older distros), x86_64 / aarch64 | seccomp user-notify | see below |
| macOS | sandbox_init profile |
|
| Windows 10 1903+ | Low integrity restricted token + Job Object | writes need a Low label (given to the workspace once); see README-windows.md |
| Older kernels | none | falls back to the read-only rules |
The status bar and sidebar show auto · landlock, auto · seccomp,
auto · seatbelt, auto · lowil or auto · rules. CST_SANDBOX=off
disables the sandbox; CST_SANDBOX=landlock, seccomp or lowil allows
only that kind. When a sandboxed command looks blocked, the TUI asks whether to rerun
it outside the sandbox. The policy is built in the parent; the forked child
only makes a few async-signal-safe syscalls before exec, so the agent
itself is never confined.
seccomp. Used when Landlock is missing, e.g. Android GKI kernels. The
child installs a seccomp filter with a user-notify listener and passes the
listener to a supervisor thread in the agent. Opens with write flags and
every create / remove / rename / link / chmod / chown / utimes / xattr call
stop until the supervisor has read the path from the child's memory,
resolved it (cwd, dirfd, .., symlinks, /proc/self, /dev/stdout) and
answered: continue when it lands in the allowed places, EACCES otherwise.
Reads, exec and writes to already-open files are not checked. The probe also
checks that the parent can peek a direct child's memory (process_vm_readv
or /proc/<pid>/mem); with Yama ptrace_scope=2 and no CAP_SYS_PTRACE
that fails, so seccomp is not selected (forced CST_SANDBOX=seccomp then
falls back to unavailable). Background jobs that outlive their shell are no
longer the agent's descendants: with Yama ptrace_scope ≥ 1 (common on
desktop distros, absent on Android) the supervisor cannot read their memory
and their checked writes fail with EACCES; once the run has finished they
get ENOSYS. This keeps mistakes out of the rest of the disk but is not a
security boundary: the kernel reads the path again after the check, so a
hostile multi-threaded program can swap it in between. The availability
check runs in a throwaway child, so a platform seccomp policy that kills the
caller cannot take the agent down.
Terminal notes
- UTF-8 locale gives the full UI. Under a non-UTF-8 locale, or with
CST_ASCII=1, box and icon glyphs fall back to ASCII. NO_COLOR=1turns colours off./copyand mouse selection use OSC 52. iTerm2 needs "Applications in terminal may access clipboard"; tmux needsset -g set-clipboard on; Windows Terminal (WSL), kitty, WezTerm, foot and WHC Shell support it out of the box.- Mouse selection: the TUI turns on button-motion reporting (
?1002h) on top of what ncurses enables, so dragging over the transcript selects text (double-click a word, triple-click a line) and releasing copies it. Terminals without 1002 still get clicks and the wheel. Shift+drag bypasses the TUI and uses the terminal's own selection. - Bracketed paste needs ncurses (all platforms above).
- macOS Terminal.app sends Option+Enter as Esc+Enter only with "Use Option as Meta key"; Ctrl+N always inserts a newline.
- Kitty keyboard protocol: at startup the TUI sends
CSI ? u; if the terminal answers (kitty, WezTerm, foot, Ghostty, WHC Shell, ...) it pushes the "disambiguate" flag (CSI > 1 u) on the alternate screen and pops it on exit. That makes Shift+Enter / Ctrl+Enter (newline), Ctrl+Backspace (delete word) and an instant Esc work. Other terminals (and tmux) don't answer and keep the legacy keys. If the TUI is killed, the main screen's keyboard mode is untouched because the flags live on the alternate screen's stack.
Acceptance checklist
Run on each platform before a release. Use a scratch git repository as the workspace and a real API key (entered when the TUI asks for it).
Build and tests:
makehas no warnings;make checkpassesmake asanpasses (Linux; macOS:make CFLAGS="-fsanitize=address,undefined -g" check)cmake -B build && cmake --build build && ctest --test-dir buildpassesmake install PREFIX=/tmp/cst && /tmp/cst/bin/cst-agent --help
CLI:
--list-sessions,--new,--resume <prefix>,--continuebehave as documented--headless --prompt "list the files"prints a transcript and exits 0- First start asks for a provider and key; the credentials file is mode 0600
- Second start reuses provider, model and effort without asking
TUI:
- Typing Chinese/emoji keeps the cursor in the right column; ←→ Home End work
- Pasting a multi-line block does not send it; Enter sends
/shows the command popup, Tab completes,@completes file paths- Esc cancels a running turn; Esc on an empty prompt does nothing; Ctrl+C twice quits
- A long
run_shell(e.g.sleep 30) stops promptly on Esc - Write permission shows the diff;
asaves a rule;psaves a shell prefix rule ask_usermodal accepts a number, an arrow selection and typed text- Todo panel appears while tasks are open; Ctrl+T hides it
/undorestores files changed by the last turn;/retryre-asks/diff,/export,/status,/tools off grep,/yolowork- Mouse: click a tool card to expand it, click in the input to move the
cursor, click a picker row;
/mouseturns it off - Drag over a wrapped reply: it highlights, the status row says "Copied", and pasting gives the paragraph without extra line breaks; double-click copies a word
- Tab toggles Build/Plan, Shift+Tab cycles ask/auto/yolo, Ctrl+P opens the palette
/modellists the provider's/models(marked(api)); a wrong key is re-askedautomode:echo x > ~/outsidein run_shell asks "Rerun outside the sandbox?" (Linux/Termux/macOS/Windows); withCST_SANDBOX=off,lsruns andtouch xasks- Resize the terminal during streaming: layout recovers
NO_COLOR=1andCST_ASCII=1render readable screens
WSL specifics:
- Workspace under
/mnt/c/...works (slower IO is expected);~/projectis recommended - Windows Terminal: mouse wheel scroll and OSC 52
/copy
Windows specifics (scripts/win_test.sh runs the tests on English and
Chinese VMs as Administrator, a standard user and a Chinese user name):
install_windows.ps1 -Zip dist\cst-agent-*.zipinstalls, adds the user PATH;cst-agent-update -Zip ...works whilecst-agent.exeruns- conhost with a raster font: Chinese renders (font switched), and the old font is back after exit; Windows Terminal renders the same
- Chinese input via IME, paste, mouse click / drag-copy / wheel
- Workspace under
C:\Users\<中文名>\项目: read/write/edit/apply_patch keep CRLF and BOM;run_shelloutput in Chinese is readable on a 936 system auto · lowil: first command reports the label;echo x > C:\outsideasks to rerun;curl ...runs without asking;/sandbox reset-labelsrestores the label- Esc during
sleep 30 & sleep 30returns at once and leaves nobusybox.exebehind
macOS specifics:
- Builds with Homebrew ncurses and with the system ncurses (
brew unlink ncurses) - Terminal.app and iTerm2: arrows, Home/End, PgUp/PgDn, bracketed paste