The scorpiox codebase is a large, multi-binary C platform encompassing an AI agent shell (sx), a rootless container runtime (scorpiox-unshare), a KVM virtual machine runner (scorpiox-vm), a web server (scorpiox-server), a DNS server (scorpiox-dns), an email stack (IMAP/SMTP), a terminal multiplexer (sxmux), and numerous tool binaries. The project makes extensive use of process spawning (656+ call sites), interacts deeply with Linux namespaces and cgroups, and in most cases operates without elevated privileges. However, several components require or benefit from root access, and certain design choices warrant security attention.
system()/popen() for shell command execution, a hardcoded SSH password, and the number of binaries that can spawn arbitrary processes create a meaningful attack surface.
| Component | Root Required? | Privilege Level | Notes |
|---|---|---|---|
sx (main agent shell) | No | User | Spawns editor, reads config |
scorpiox-unshare | No | User (rootless containers) | Can run as root for NFS/CIFS mounts |
scorpiox-vm | Effectively yes | Needs /dev/kvm + /dev/net/tun access | KVM group membership required |
scorpiox-thunderbolt4 | Yes | Root (BPF) | Explicit geteuid() != 0 check |
scorpiox-dns | Depends | Port 53 requires root or CAP_NET_BIND_SERVICE | Default: 0.0.0.0:53 |
scorpiox-server | No | User | Binds to configurable port (default 7432) |
scorpiox-bash | No | User | Executes shell commands as current user |
scorpiox-host | No | User | HTTP session gateway, daemonizes via fork |
scorpiox-sshpass | No | User | Hardcoded default password: "xboxone" |
scorpiox-init | No | User | Tool loader; exec's into child processes |
scorpiox-agent | No | User | Orchestrates agent sessions |
scorpiox-sdk | No | User | Heavy process spawning via forkpty |
scorpiox-thunderbolt4 — The only binary with an explicit root check: // scorpiox-thunderbolt4.c:1406
if (geteuid() != 0) { fprintf(stderr, "error: need root (sudo)\n"); return 1; }
Required for BPF access on macOS to do raw Ethernet frame I/O over Thunderbolt.
scorpiox-vm — Needs /dev/kvm (read-write) and /dev/net/tun for TAP networking. Typically requires kvm group membership rather than root.scorpiox-dns — Binds to UDP/TCP port 53 by default. Port < 1024 requires root or CAP_NET_BIND_SERVICE.scorpiox-unshare — Designed as rootless, but detects root at runtime: // scorpiox-unshare.c:1821
int skip_userns = (getuid() == 0);
When running as root, it skips user namespace creation (matches Docker/Podman behavior) to enable NFS/CIFS bind-mounts inside containers.
scorpiox-wsl.c — Uses chroot within WSL overlayfs containers, which requires root inside the WSL distribution.setuid/setgid/seteuid calls anywhere in the first-party codebase.prctl/seccomp/pledge/unveil calls — the codebase does not explicitly drop privileges or apply syscall filters.newuidmap/newgidmap (standard Linux user namespace plumbing): // scorpiox-unshare.c:774
/ Non-root: must use newuidmap/newgidmap (setuid helpers) /
| Mechanism | Count | Files | Risk Level |
|---|---|---|---|
system() | ~80+ | 25+ files | HIGH — Passes strings to shell |
popen() | ~40+ | 15+ files | HIGH — Shell-interpreted strings |
fork() + exec*() | ~100+ | 20+ files | MEDIUM — Direct exec, no shell |
forkpty() | ~20+ | 6 files | MEDIUM — PTY-based spawning |
clone() | 1 | scorpiox-unshare.c | LOW — Container runtime (by design) |
CreateProcess (Windows) | 2 | sxmux_session.c | LOW — Platform-specific |
| File | Call Sites | Purpose |
|---|---|---|
scorpiox-unshare.c | 34 | Container runtime — fork/clone/exec are core functionality |
scorpiox-sdk.c | 34 | Session orchestration — forkpty→containers |
sx_procmon.c | 32 | Process monitoring library |
scorpiox-agent.c | 17 | Agent orchestration — system()/popen()/execlp() |
sx_exec.c | 17 | Core command execution library |
scorpiox-bash.c | 11 | Shell command execution tool |
scorpiox-wsl.c | 11 | WSL container management |
sx_provider_claude_code.c | 12 | Claude Code provider — spawns Claude CLI |
sx_provider_codex.c | 11 | Codex provider — spawns OpenAI CLI |
scorpiox-server.c | 10 | CGI script execution via fork+exec |
system() with constructed strings
// scorpiox-agent.c:382 — arbitrary git commands
int ret = system(cmd);
// sx.c:659 — editor invocation
int ret = system(editor_cmd);
// sxmail_maildir.c:704 — maildir operations
if (system(cmd) != 0) {
Concern 2: popen() with constructed commands
// sxtmux_backend_tmux.c:42
FILE *fp = popen("tmux list-sessions ...", "r");
// scorpiox-agent.c:407
FILE *fp = popen(cmd, "r");
Concern 3: Hook script execution
// scorpiox-hook.c:579 — executes user-supplied hook scripts
FILE *pipe = popen(full_cmd, "r");
// scorpiox-hook.c:640
system(full_cmd);
Hook scripts in .scorpiox/hooks/ are executed with full user privileges. No sandboxing or validation of hook content.
The codebase includes a dedicated process monitoring subsystem (sx_procmon.c) using atomic counters to track:
This is a positive security practice for leak detection and audit.
| Path Pattern | Access Type | Components |
|---|---|---|
~/.scorpiox/ (SCORPIOX_HOME) | R/W | All components — config, sessions, data |
/tmp/ | R/W | Multiple — temp files, IPC sockets, slirp API |
/proc/, /sys/ | R | Container setup, terminal size, procfs |
/dev/kvm | R/W | scorpiox-vm only |
/dev/net/tun | R/W | scorpiox-vm (TAP networking) |
/dev/bpf* | R/W | scorpiox-thunderbolt4 only (macOS) |
/dev/pts/, /dev/shm/ | R/W | Container runtime (mounted inside containers) |
/sys/fs/cgroup/sx- | R/W | scorpiox-unshare (cgroup v2 memory limits) |
/etc/resolv.conf | R | Container DNS setup |
/etc/subuid, /etc/subgid | R | User namespace mapping |
/mnt/tools/, /mnt/apps/ | R | scorpiox-init (tool loading) |
| Working directory (CWD) | R/W | Various — config, sessions, git operations |
scorpiox-unshare) properly isolates container filesystem via pivot_root().-v host:container[:ro]) support read-only mode./tmp is used for IPC sockets (sxmux, slirp4netns API) — standard POSIX practice but vulnerable to symlink races without O_NOFOLLOW.| Syscall Category | Count | Files | Notes | |
|---|---|---|---|---|
ioctl() | 50+ | scorpiox-vm.c, sx_term.c, mail | KVM control, terminal size, socket config | |
mmap() | 5 | scorpiox-vm.c | Guest memory + VCPU run struct. PROT_READ\ | PROT_WRITE only (no PROT_EXEC) |
clone() | 1 | scorpiox-unshare.c | Namespace creation with CLONE_NEW* flags | |
pivot_root() | 1 | scorpiox-unshare.c | Container rootfs switch | |
mount() | 30+ | scorpiox-unshare.c | Container filesystem setup | |
signal()/sigaction() | 40+ | Various | Signal handling for cleanup, SIGPIPE ignore |
PROT_EXEC mmap usage — no JIT or self-modifying code patterns detected.mprotect() calls — no runtime memory permission changes.ptrace() calls — no debugging/inspection of other processes.seccomp() or prctl(PR_SET_SECCOMP) calls — containers do NOT have syscall filtering.scorpiox-vm.c) is extensive and appropriate for a VM monitor:KVM_CREATE_VM, KVM_CREATE_VCPU, KVM_SET_USER_MEMORY_REGION, KVM_RUN
- KVM_SET_SREGS, KVM_SET_REGS, KVM_SET_CPUID2
- KVM_CREATE_IRQCHIP, KVM_CREATE_PIT2, KVM_IRQ_LINE
scorpiox-unshare)The container runtime implements a rootless Linux container model similar to Podman:
Namespace Isolation:| Namespace | Flag | Status | Notes |
|---|---|---|---|
| Mount (mnt) | CLONE_NEWNS | ✅ Always | Filesystem isolation |
| PID | CLONE_NEWPID | ✅ Always | Process isolation |
| UTS | CLONE_NEWUTS | ✅ Always | Hostname isolation |
| User | CLONE_NEWUSER | ✅ Default | Skipped when running as root |
| Network | CLONE_NEWNET | ✅ Default | Opt-out via --net host |
| IPC | — | ❌ Missing | No CLONE_NEWIPC flag |
| Cgroup | — | ❌ Missing | No CLONE_NEWCGROUP flag |
pivot_root() to switch to container rootfs — proper isolation./dev is mounted as tmpfs with only essential devices bind-mounted from host./proc mounted with MS_NOSUID | MS_NODEV | MS_NOEXEC./sys mounted read-only./tmp and /run are independent tmpfs mounts.MS_BIND | MS_REMOUNT | MS_RDONLY.CLONE_NEWNET + slirp4netns for userspace networking.add_hostfwd).--net host shares host network namespace.--memory flag writes to /sys/fs/cgroup/sx-/memory.max .--gpu is specified.| Gap | Severity | Description |
|---|---|---|
| No seccomp filter | MEDIUM | Containers can invoke any syscall the user namespace allows |
No CLONE_NEWIPC | LOW | Shared IPC namespace allows SysV IPC cross-container communication |
No CLONE_NEWCGROUP | LOW | Container can see host cgroup hierarchy |
--privileged flag | MEDIUM | Skips user namespace entirely, runs as real root inside container |
| No capability dropping | LOW | All user-namespace capabilities available inside container |
scorpiox-vm)mmap()-ed with PROT_READ | PROT_WRITE (no PROT_EXEC on host side)./dev/net/tun provides network isolation..tar rootfs images as the container runtime.scorpiox-wsl.c)system() or popen().scorpiox-unshare — chroot alone is not a security boundary.| IPC Type | Usage | Files |
|---|---|---|
Unix pipes (pipe()) | Parent-child communication, process I/O | sx_exec.c, scorpiox-bash.c, scorpiox-server.c |
| Named pipes (Windows) | Multiplexer session protocol | sxmux_session.c |
Unix domain sockets (AF_UNIX) | Multiplexer daemon, slirp4netns API | sxmux_session.c, scorpiox-unshare.c |
PTY (forkpty()) | Terminal emulation for child processes | sx_pty.c, sx_bgtask.c, scorpiox-sdk.c |
| TCP sockets | HTTP server, DNS, email, host gateway | scorpiox-server.c, scorpiox-dns.c, scorpiox-host.c |
| Signals (SIGTERM, SIGINT, SIGCHLD) | Process lifecycle management | All daemon-like components |
shm_open, shmget, sem_open) — all IPC is stream/message-based.AF_UNIX + SOCK_STREAM with socket dir at ~/.scorpiox/sxmux/ (permissions 0700)./tmp/sx_slirp_.sock — deleted after use.| Variable | Usage | Risk |
|---|---|---|
SCORPIOX_HOME | Base directory for all data/config | LOW — data directory path |
PATH | Set inside containers to standard dirs | LOW — hardcoded safe value |
EDITOR, VISUAL | Used to launch editor via system() | MEDIUM — user-controlled path executed |
SHELL | Default shell in multiplexer panes | LOW |
CONTAINER_PACKAGES | Packages to install in containers | MEDIUM — passed to package manager |
IMAGE_BASE_URL | Container image download URL | MEDIUM — could redirect image downloads |
LD_PRELOAD or LD_LIBRARY_PATH manipulation anywhere in the codebase.DYLD_* variable manipulation (macOS).PATH inside containers: setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1);
-e flags or specific variables like TERM, SCORPIOX_BRANCH).| Practice | Status | Notes |
|---|---|---|
| Rootless by default | ✅ | Container runtime uses user namespaces |
| No unnecessary setuid | ✅ | No binaries require setuid bit |
| Process monitoring | ✅ | sx_procmon.c tracks all fork/pipe lifecycle |
| Read-only mounts | ✅ | /sys read-only, volume :ro support |
| Tmpfs for sensitive dirs | ✅ | /dev, /tmp, /run are independent tmpfs |
| Namespace isolation | ✅ | 5 of 7 namespaces used by default |
| UID mapping | ✅ | Full subuid/subgid range mapping with newuidmap |
| Violation | Severity | Description |
|---|---|---|
| Hardcoded SSH password | HIGH | scorpiox-sshpass has DEFAULT_PASSWORD "xboxone" — documented as "home lab only" but ships in production binary |
| No seccomp profiles | MEDIUM | Container runtime does not filter syscalls |
system() for shell commands | MEDIUM | ~80+ call sites pass constructed strings to shell — injection risk if inputs are not sanitized |
| No IPC namespace isolation | LOW | CLONE_NEWIPC not used in container runtime |
| No cgroup namespace | LOW | CLONE_NEWCGROUP not used |
| No privilege dropping in servers | MEDIUM | scorpiox-server binds port then continues as same user — no setuid(nobody) pattern |
| CGI execution model | MEDIUM | scorpiox-server fork+exec's scripts with CGI env vars — relies on script security |
| Hook script execution | MEDIUM | .scorpiox/hooks/ scripts are executed without sandboxing |
--privileged flag exists | MEDIUM | Disables user namespace, grants real root in container |
Editor launch via system() | LOW | EDITOR env var value passed to system() |
| # | Risk | Component | Mitigation |
|---|---|---|---|
| 1 | Hardcoded password in production binary | scorpiox-sshpass | Remove default, require explicit -p flag |
| 2 | Shell injection via system()/popen() | Multiple (25+ files) | Replace with fork()+execv() pattern; use sx_exec.c library which already has safe exec |
| # | Risk | Component | Mitigation |
|---|---|---|---|
| 3 | No syscall filtering in containers | scorpiox-unshare | Add seccomp-bpf profile matching Docker defaults |
| 4 | Unsandboxed hook execution | scorpiox-hook | Execute hooks in container/namespace or with reduced privileges |
| 5 | CGI script execution | scorpiox-server | Apply seccomp or namespace isolation to CGI children |
| # | Risk | Component | Mitigation | |
|---|---|---|---|---|
| 6 | Missing IPC and cgroup namespaces | scorpiox-unshare | Add CLONE_NEWIPC \ | CLONE_NEWCGROUP |
| 7 | IMAGE_BASE_URL from environment | scorpiox-unshare/scorpiox-vm | Validate URL against allowlist or pin to HTTPS | |
| 8 | No privilege dropping after port bind | scorpiox-server, scorpiox-dns | Drop to unprivileged user after bind() | |
| 9 | /tmp IPC socket race conditions | sxmux, scorpiox-unshare | Use $XDG_RUNTIME_DIR with restricted permissions |
system() calls where possible — the codebase already has sx_exec() with fork()+exec() and timeout support. Route all command execution through it.kexec_load, reboot, mount (after setup), ptrace would significantly improve security.scorpiox-sshpass or gate it behind a build flag.CLONE_NEWIPC | CLONE_NEWCGROUP to the default clone flags in scorpiox-unshare.scorpiox-server, scorpiox-dns) after binding ports.| File | Spawning Calls |
|---|---|
scorpiox-unshare.c | 34 |
scorpiox-sdk.c | 34 |
sx_procmon.c | 32 |
scorpiox-agent.c | 17 |
sx_exec.c | 17 |
sx.c | 13 |
sx_provider_claude_code.c | 12 |
scorpiox-bash.c | 11 |
scorpiox-wsl.c | 11 |
sx_provider_codex.c | 11 |
scorpiox-whatsapp.c | 10 |
scorpiox-server.c | 10 |
scorpiox-docs.c | 9 |
sx_systemprompt.c | 9 |
sx_pty.c | 9 |
sx_bridge.c | 8 |
scorpiox-mirror-git.c | 7 |
sx_bgtask.c | 7 |
sx_provider_openai.c | 7 |
sx_provider_gemini.c | 7 |
scorpiox-unshare scorpiox-vm scorpiox-wsl
Mount NS ✅ CLONE_NEWNS N/A (KVM) ❌ chroot only
PID NS ✅ CLONE_NEWPID N/A (KVM) ❌ No
User NS ✅ CLONE_NEWUSER* N/A (KVM) ❌ No
Network NS ✅ CLONE_NEWNET† ✅ TAP/bridge ❌ Host network
UTS NS ✅ CLONE_NEWUTS N/A (KVM) ❌ No
IPC NS ❌ Missing N/A (KVM) ❌ No
Cgroup NS ❌ Missing N/A (KVM) ❌ No
Seccomp ❌ None N/A (KVM) ❌ None
Capability drop ❌ None N/A (KVM) ❌ None
* Skipped when running as root
† Opt-out via --net host
.c and .h files for process spawning functions (fork, exec*, system, popen, posix_spawn, clone, vfork, CreateProcess, ShellExecute)setuid, setgid, chroot, capset, prctl, seccomp, pledge, unveil)ioctl, ptrace, mmap, mprotect, signal handling)unshare, setns, clone, CLONE_NEW*, pivot_root, cgroup)setenv, putenv, getenv of security-sensitive variables)fopen, open, mkdir, unlink, etc.)scorpiox-unshare.c, scorpiox-vm.c, sx_exec.c, scorpiox-bash.c, scorpiox-server.c, scorpiox-sshpass.c