# 05 — Permissions, Process Spawning, Privilege Model & Sandboxing Audit

**Audit Date:** 2026-04-28  
**Codebase:** scorpiox/clang  
**Auditor:** Security Audit Agent  
**Scope:** OS-level permissions, system access, process spawning, privilege model, and sandboxing

---

## Executive Summary

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.

**Overall Risk Level: MEDIUM** — The container runtime has strong namespace isolation by default, but the broad use of `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.

---

## 1. Permissions Summary

| 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 |

---

## 2. Privilege Requirements

### 2.1 Components That Require Root

1. **`scorpiox-thunderbolt4`** — The only binary with an explicit root check:
   ```c
   // 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.

2. **`scorpiox-vm`** — Needs `/dev/kvm` (read-write) and `/dev/net/tun` for TAP networking. Typically requires `kvm` group membership rather than root.

3. **`scorpiox-dns`** — Binds to UDP/TCP port 53 by default. Port < 1024 requires root or `CAP_NET_BIND_SERVICE`.

### 2.2 Components That Benefit From Root

4. **`scorpiox-unshare`** — Designed as rootless, but detects root at runtime:
   ```c
   // 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.

5. **`scorpiox-wsl.c`** — Uses `chroot` within WSL overlayfs containers, which requires root inside the WSL distribution.

### 2.3 No Privilege Escalation Detected

- **No `setuid`/`setgid`/`seteuid` calls** anywhere in the first-party codebase.
- **No `prctl`/`seccomp`/`pledge`/`unveil` calls** — the codebase does not explicitly drop privileges or apply syscall filters.
- The only reference to setuid helpers is for `newuidmap`/`newgidmap` (standard Linux user namespace plumbing):
  ```c
  // scorpiox-unshare.c:774
  /* Non-root: must use newuidmap/newgidmap (setuid helpers) */
  ```

---

## 3. Process Spawning Inventory

**Total process spawning call sites (non-vendor): 656**

### 3.1 Spawning Mechanisms

| 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 |

### 3.2 Top Process Spawning Files

| 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 |

### 3.3 Security-Sensitive Spawning Patterns

**Concern 1: `system()` with constructed strings**
```c
// 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**
```c
// 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**
```c
// 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/<event>/` are executed with full user privileges. No sandboxing or validation of hook content.

### 3.4 Process Monitoring

The codebase includes a dedicated process monitoring subsystem (`sx_procmon.c`) using atomic counters to track:
- Fork/reap counts and peak active children
- popen/pclose lifecycle
- pipe open/close counts
- system() call counts

This is a positive security practice for leak detection and audit.

---

## 4. Filesystem Access Scope

### 4.1 Primary Access Paths

| 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-<pid>/` | 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 |

### 4.2 Filesystem Access Assessment

- **No global filesystem scanning** — access is scoped to known paths.
- The container runtime (`scorpiox-unshare`) properly isolates container filesystem via `pivot_root()`.
- User-specified bind mounts (`-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`.

---

## 5. System Call Analysis

### 5.1 Kernel Interactions

| 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 |

### 5.2 Notable Findings

- **No `PROT_EXEC` mmap usage** — no JIT or self-modifying code patterns detected.
- **No `mprotect()` calls** — no runtime memory permission changes.
- **No `ptrace()` calls** — no debugging/inspection of other processes.
- **No `seccomp()` or `prctl(PR_SET_SECCOMP)` calls** — containers do NOT have syscall filtering.
  - ⚠️ **Gap:** The container runtime does not apply seccomp profiles, unlike Docker/Podman which filter ~40+ dangerous syscalls by default.
- **KVM ioctl usage** (`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`

---

## 6. Sandboxing / Isolation Model

### 6.1 Container Runtime (`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 |

**Filesystem Isolation:**
- Uses `pivot_root()` to switch to container rootfs — proper isolation.
- Overlayfs when available (kernel ≥ 5.11), falls back to bind-mount.
- `/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.
- Supports read-only volume mounts via `MS_BIND | MS_REMOUNT | MS_RDONLY`.

**Network Isolation:**
- Default: `CLONE_NEWNET` + `slirp4netns` for userspace networking.
- DNS: Container resolv.conf points to slirp4netns gateway (10.0.2.3).
- Port forwarding via slirp4netns API socket (`add_hostfwd`).
- Opt-out: `--net host` shares host network namespace.

**Resource Limits:**
- Memory limits via cgroup v2: `--memory` flag writes to `/sys/fs/cgroup/sx-<pid>/memory.max`.
- No CPU limit support detected.

**GPU Passthrough:**
- NVIDIA + DRI device nodes are bind-mounted into container when `--gpu` is specified.
- This breaks device isolation but is expected for GPU workloads.

### 6.2 Container Security Gaps

| 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 |

### 6.3 KVM Virtual Machine (`scorpiox-vm`)

- Full hardware-level isolation via KVM.
- Guest memory is `mmap()`-ed with `PROT_READ | PROT_WRITE` (no PROT_EXEC on host side).
- TAP networking via `/dev/net/tun` provides network isolation.
- Uses same `.tar` rootfs images as the container runtime.
- This is the strongest isolation model in the codebase.

### 6.4 WSL Container (`scorpiox-wsl.c`)

- Uses overlayfs + chroot (not user namespaces) for WSL environments.
- Constructs large shell command strings passed to `system()` or `popen()`.
- ⚠️ Less isolated than `scorpiox-unshare` — chroot alone is not a security boundary.

---

## 7. IPC Mechanisms

### 7.1 Pipe Usage (797+ call sites)

| 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 |

### 7.2 Notable IPC Patterns

- **No shared memory** (`shm_open`, `shmget`, `sem_open`) — all IPC is stream/message-based.
- **sxmux** uses `AF_UNIX` + `SOCK_STREAM` with socket dir at `~/.scorpiox/sxmux/` (permissions `0700`).
- **slirp4netns** API socket at `/tmp/sx_slirp_<pid>.sock` — deleted after use.
- **scorpiox-server** uses fork-per-request model with SIGCHLD reaping for CGI scripts.

---

## 8. Environment Variable Handling

### 8.1 Security-Sensitive Environment Variables

| 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 |

### 8.2 Positive Findings

- **No `LD_PRELOAD` or `LD_LIBRARY_PATH` manipulation** anywhere in the codebase.
- **No `DYLD_*` variable manipulation** (macOS).
- Container runtime sets a clean `PATH` inside containers:
  ```c
  setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1);
  ```
- Environment pass-through into containers is explicit (user-specified `-e` flags or specific variables like `TERM`, `SCORPIOX_BRANCH`).

---

## 9. Principle of Least Privilege Assessment

### 9.1 Positive Practices

| 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 |

### 9.2 Violations / Gaps

| 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()` |

---

## 10. Risk Assessment

### 10.1 Critical Risks

| # | 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 |

### 10.2 High Risks

| # | 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 |

### 10.3 Medium Risks

| # | 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 |

### 10.4 Recommendations Summary

1. **Eliminate `system()` calls** where possible — the codebase already has `sx_exec()` with `fork()+exec()` and timeout support. Route all command execution through it.
2. **Add seccomp-bpf** to the container runtime — even a basic profile blocking `kexec_load`, `reboot`, `mount` (after setup), `ptrace` would significantly improve security.
3. **Remove the hardcoded password** from `scorpiox-sshpass` or gate it behind a build flag.
4. **Add `CLONE_NEWIPC | CLONE_NEWCGROUP`** to the default clone flags in `scorpiox-unshare`.
5. **Drop privileges** in long-running servers (`scorpiox-server`, `scorpiox-dns`) after binding ports.
6. **Sandbox hook execution** — run hooks in a child with reduced capabilities or inside a container.

---

## Appendix A: File-by-File Process Spawning Count

| 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 |

## Appendix B: Namespace Isolation Matrix

```
                    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
```

## Appendix C: Methodology

1. Recursive grep of all non-vendor `.c` and `.h` files for process spawning functions (`fork`, `exec*`, `system`, `popen`, `posix_spawn`, `clone`, `vfork`, `CreateProcess`, `ShellExecute`)
2. Recursive grep for privilege-related syscalls (`setuid`, `setgid`, `chroot`, `capset`, `prctl`, `seccomp`, `pledge`, `unveil`)
3. Recursive grep for kernel interactions (`ioctl`, `ptrace`, `mmap`, `mprotect`, signal handling)
4. Recursive grep for namespace/container calls (`unshare`, `setns`, `clone`, `CLONE_NEW*`, `pivot_root`, `cgroup`)
5. Recursive grep for environment variable manipulation (`setenv`, `putenv`, `getenv` of security-sensitive variables)
6. Recursive grep for IPC mechanisms (pipes, sockets, shared memory, signals)
7. Recursive grep for filesystem access (`fopen`, `open`, `mkdir`, `unlink`, etc.)
8. Manual code review of critical files: `scorpiox-unshare.c`, `scorpiox-vm.c`, `sx_exec.c`, `scorpiox-bash.c`, `scorpiox-server.c`, `scorpiox-sshpass.c`
