- Child installs a seccomp filter with a listener; a supervisor thread resolves the path of write-type syscalls (cwd, dirfd, .., symlinks, /proc/self, /dev/stdout, both ends of rename/link) and answers CONTINUE or EACCES; IPv4/IPv6 sockets are refused - Probe order Landlock -> seccomp -> none, checked in a throwaway child; CST_SANDBOX=landlock|seccomp|off - curl connect failures count as sandbox refusals - test_sandbox runs the detected sandbox and forced seccomp, with escape, symlink, rename, link, chmod, stdout, ENOENT, background and network cases - Docs: platform matrix, limits (not a security boundary, Yama and background jobs)
7.1 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.
| Platform | Sandbox | Notes |
|---|---|---|
| Linux 5.13+ | Landlock (raw syscalls, no extra library) | ABI ≥ 4 (6.7+) also blocks TCP connect/bind |
| Linux 5.5+ without Landlock (Android / Termux, WSL, older distros), x86_64 / aarch64 | seccomp user-notify | refuses IPv4/IPv6 sockets; see below |
| macOS | sandbox_init profile |
also denies network |
| Older kernels, Windows | none | falls back to the read-only rules |
The status bar and sidebar show auto · landlock, auto · seccomp,
auto · seatbelt or auto · rules. CST_SANDBOX=off disables the
sandbox; CST_SANDBOX=landlock or CST_SANDBOX=seccomp 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. 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./copyuses OSC 52. iTerm2 needs "Applications in terminal may access clipboard"; tmux needsset -g set-clipboard on; Windows Terminal (WSL) supports it out of the box.- 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.
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 - 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); 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
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