# Security Vulnerability Audit Report — ScorpioX Clang Codebase

**Audit Date:** 2026-04-28  
**Auditor:** Security Audit Agent (automated)  
**Codebase:** scorpiox/clang — 148 C source files, 74 headers, ~141,971 lines of code (excluding vendor)  
**Scope:** C-specific security vulnerabilities: buffer overflows, memory safety, format strings, integer overflows, use-after-free, command injection, race conditions

---

## Executive Summary

The ScorpioX codebase is a large, multi-component C project comprising a terminal multiplexer, email server (SMTP/IMAP), HTTP server, AI provider clients, a KVM-based VM hypervisor, file transfer utilities, and more. The project demonstrates good security awareness in many areas — using `snprintf` over `sprintf` in 2,172 out of 2,192 format calls, performing bounds-checked header parsing in the HTTP server, and sanitizing filenames in network receive paths. However, **critical and high-severity vulnerabilities** exist, primarily in **shell command injection** (232 `system()`/`popen()` calls, many with insufficiently sanitized input), **missing null checks after allocation** (166 instances), and **20 unbounded `sprintf` calls** that can cause heap/stack buffer overflows. The email server and HTTP server — both network-facing — have the highest exposure.

---

## Vulnerability Summary

| # | Severity | CVSS | Location | Category | Description |
|---|----------|------|----------|----------|-------------|
| V-01 | **CRITICAL** | 9.8 | `sxmail_maildir.c:704` | Command Injection | `system("rm -rf '<path>'")` — single-quote in folder name = arbitrary shell command execution |
| V-02 | **CRITICAL** | 9.8 | `scorpiox-vm.c:488-493` | Command Injection | `system(cmd)` with URL/path from user config passed through `curl`/`wget` — single-quote injection |
| V-03 | **CRITICAL** | 9.1 | `sx.c:1318,1575,2109,2262,2458` | Command Injection | Multiple `system()` calls building commands from session data, config values, file paths |
| V-04 | **CRITICAL** | 9.1 | `scorpiox-agent.c` (12 calls) | Command Injection | `system()` and `popen()` with agent-provided paths, `find -exec` with unsanitized directory names |
| V-05 | **HIGH** | 8.1 | `libsxnet/sx_http_wasm.c:388-460` | Buffer Overflow | `sprintf` chain into `static char json[32768]` — escape expansion (`\uXXXX`) can exceed `needed` estimate |
| V-06 | **HIGH** | 8.1 | `libsxnet/sx_api.c:367-404` | Buffer Overflow | `sprintf` chain into heap buffer — size estimate uses `+16` per field but `sprintf` format overhead can exceed this |
| V-07 | **HIGH** | 7.8 | `scorpiox-wsl.c:1278-1279` | Buffer Overflow | `strcat(command_buf, argv[i])` in loop into `char command_buf[4096]` — no bounds checking |
| V-08 | **HIGH** | 7.8 | `scorpiox-traffic.c:926-927` | Buffer Overflow | `strcat(cmdline, argv[i])` in loop into `char cmdline[4096]` — no bounds checking |
| V-09 | **HIGH** | 7.5 | 166 locations (see §5) | Null Deref | `malloc`/`calloc`/`realloc` return values used without null checks — crash on OOM |
| V-10 | **HIGH** | 7.5 | `libsxnet/sx_mcp.c:701` | Null Deref + Overflow | `realloc(buf, cap)` in loop with `cap *= 2` — no null check, potential integer overflow on `cap` |
| V-11 | **HIGH** | 7.5 | `libsxnet/sx_http.c:44` | Integer Overflow | `size * nmemb` multiplication in curl write callback — can overflow `size_t` on 32-bit platforms |
| V-12 | **MEDIUM** | 6.5 | `libsxnet/sx_http_wasm.c:39,303,388,639` | Thread Safety | Multiple `static char` buffers (up to 65536 bytes) — data races if called from multiple threads |
| V-13 | **MEDIUM** | 6.5 | `sx_dll.c:288` | Fixed Buffer | `char enqueue_buf[8192]` — shared across agent messages, no overflow check on concatenation |
| V-14 | **MEDIUM** | 6.1 | `sx_term.c:1840` | Buffer Overflow | `sprintf(osc, ...)` with `b64` string — safe (allocated as `b64_size + 20`) but relies on exact math |
| V-15 | **MEDIUM** | 5.9 | `scorpiox-server.c:1233` | Input Validation | `Content-Length` parsed via `strtoll` without upper-bound validation — can cause huge allocations |
| V-16 | **MEDIUM** | 5.5 | 20 `sprintf` calls | Buffer Overflow | Unbounded `sprintf` calls (see §2 for full list) — heap or stack corruption risk |
| V-17 | **MEDIUM** | 5.3 | `sxmail_smtp.c:365` | Missing Null Check | `realloc` for SMTP DATA accumulation — null return causes use-after-free of old buffer |
| V-18 | **MEDIUM** | 5.3 | `scorpiox-unshare.c:140` | Buffer Overflow | `strcpy(buf + dlen + 1, prog)` — no bounds check on `prog` length vs remaining buffer space |
| V-19 | **LOW** | 4.0 | `sx_term.c:515-518` | Signal Safety | `SIGWINCH` handler sets `g_term.resized = true` — safe (signal-flag pattern), but `signal()` used instead of `sigaction()` |
| V-20 | **LOW** | 3.7 | 73 compiler warnings | Ignored Returns | `system()`, `read()`, `write()`, `chdir()`, `symlink()` return values ignored — silent failures |
| V-21 | **LOW** | 3.3 | `scorpiox-unshare.c:124-141` | TOCTOU | `access()` check then `execve()` — time-of-check-time-of-use race on file existence |
| V-22 | **LOW** | 3.0 | 28 `strcpy` calls | Bounded Copy | Most `strcpy` calls copy string literals or bounded values — low risk but should use `snprintf` |
| V-23 | **INFO** | 2.0 | `scorpiox-whatsapp.c:156` | Input Parsing | `sscanf` for version parsing — bounded by format but lacks input length validation |

---

## 1. Buffer Overflow Analysis

### 1.1 Unbounded `sprintf` Calls (20 instances)

The codebase contains **20 `sprintf` calls** without bounds checking. While the project predominantly uses `snprintf` (2,172 calls), the remaining `sprintf` calls represent real overflow risks:

**Critical `sprintf` chains:**

| File | Line | Pattern | Risk |
|------|------|---------|------|
| `sx_http_wasm.c` | 394-451 | `p += sprintf(p, ...)` chain building JSON into `static char[32768]` | The `needed` calculation uses `*2` for escaping but `\uXXXX` expands 1→6 chars (6x). If body contains many control characters, actual expansion exceeds estimate. The `if (needed > sizeof(json))` check uses the underestimate. |
| `sx_api.c` | 367-404 | `offset += sprintf(buf + offset, ...)` chain | Buffer `total` calculation uses `+16` per field for format overhead, but `sprintf("%zu:", len)` for large `len` values can produce 20+ digit strings. Multiple fields compound the undercount. |
| `sx_slashcmd.c` | 1118 | `sprintf(p, "- %s: ", name)` | No remaining-space tracking in loop building description string |
| `sx_exec_wasm_builtins.c` | 1778-1781 | `sprintf(new_json, "[%s]", entry)` / `sprintf(new_json + len - 1, ",%s]", entry)` | `entry` could be arbitrarily large |
| `scorpiox-mcp.c` | 1192 | `sprintf(out, "Error: %s", estr)` | If `estr` exceeds `out` buffer |

**Recommendation:** Replace all `sprintf` calls with `snprintf` and check return values.

### 1.2 Unbounded `strcpy` and `strcat` (37 instances)

**High-risk `strcat` in loops:**
```c
// scorpiox-wsl.c:1278-1279 — argv concatenation into fixed buffer
char command_buf[4096] = {0};
for (int i = arg_start + 1; i < argc; i++) {
    if (command_buf[0]) strcat(command_buf, " ");
    strcat(command_buf, argv[i]);  // NO BOUNDS CHECK
}

// scorpiox-traffic.c:926-927 — same pattern
char cmdline[4096] = {0};
for (int i = 0; i < argc; i++) {
    if (i > 0) strcat(cmdline, " ");
    strcat(cmdline, argv[i]);  // NO BOUNDS CHECK
}
```

Long argument lists will overflow these 4096-byte stack buffers, corrupting the return address.

**High-risk `strcpy`:**
```c
// scorpiox-unshare.c:140
strcpy(buf + dlen + 1, prog);  // prog length unchecked

// scorpiox-agent.c:1452
strcpy(out + len - 4, ".dll");  // assumes len >= 4
```

### 1.3 Stack Buffer Analysis

The codebase uses **2,311 stack-allocated char buffers**. Notable large allocations:
- `sxmail_dkim.c:414` — `char sign_data[32768]` (32KB on stack)
- `sx_dll.c:288` — `char enqueue_buf[8192]` (in global struct)
- `sxmail_imap.c:927` — `char resp[8192]`
- `sxmail_queue.c:896` — `char bounce[8192]`

Stack exhaustion risk on deeply nested calls with 8KB+ buffers per frame.

---

## 2. Format String Analysis

### 2.1 Direct Format String Vulnerabilities

No classic format string vulnerabilities were found (i.e., `printf(user_input)` without format string). All `printf`/`fprintf` calls use string literals as format specifiers. **This is well-implemented.**

### 2.2 Indirect Format Risks

The `snprintf` calls that build command strings for `system()` can indirectly introduce format-related issues if the resulting command string is interpreted by the shell. This is primarily a command injection risk (see §6).

---

## 3. Integer Overflow Analysis

### 3.1 Multiplication Before Allocation

```c
// sx_http.c:44 — curl write callback
size_t real_size = size * nmemb;  // Can overflow on 32-bit
```
While curl guarantees `size == 1`, the contract allows arbitrary values. On 32-bit platforms, `size * nmemb` can wrap to a small value, causing subsequent `memcpy` to write beyond the buffer.

```c
// sx_http.c:48 — doubling strategy
size_t newcap = buf->cap * 2;  // Can overflow to 0
```
If `cap` is near `SIZE_MAX/2`, doubling wraps to 0 or a small value. The follow-up `realloc(buf->data, newcap)` with `newcap=0` frees the buffer, and subsequent `memcpy` writes to freed memory.

### 3.2 Integer Truncation

```c
// sxtmux_backend_sxmux.c:62
int len = (int)strlen(keys);  // Truncates on strings > 2GB (theoretical)

// sxmux_session.c:28
int len = (int)strlen(tmp);
```
19 instances of casting `strlen()` (returns `size_t`) to `int`. On 64-bit systems this truncates for strings > 2GB. While unlikely in practice, this violates C safety invariants.

### 3.3 `realloc` Doubling Overflow

```c
// sx_mcp.c:701
while (len + need >= cap) { cap *= 2; buf = realloc(buf, cap); }
```
- `cap *= 2` can overflow to 0 on repeated doublings
- No null check on `realloc` return — old `buf` is leaked and `buf` becomes NULL
- Subsequent `snprintf(buf + len, ...)` dereferences NULL

Same pattern in `sx_provider_gemini_wasm.c:56`, `sx_provider_openai.c:323`.

---

## 4. Memory Safety Analysis

### 4.1 Missing Null Checks After Allocation (166 instances)

**166 `malloc`/`calloc`/`realloc` calls** lack null-pointer checks. These will crash on out-of-memory conditions. Key high-risk locations:

| File | Line | Call | Impact |
|------|------|------|--------|
| `sx_http.c` | 156 | `malloc(8192)` — HTTP response buffer | Crash during active HTTP request |
| `sx_frp.c` | 493 | `malloc(st->read_buf_size)` — FRP read buffer | Crash in network relay |
| `sx_provider.c` | 151 | `calloc(1, sizeof(SxProvider))` | Crash on provider init |
| `sx_api.c` | 25, 61 | `calloc(SX_INITIAL_HISTORY, ...)` | Crash on API init |
| `sx_bgtask.c` | 210 | `malloc(SX_BGTASK_OUTPUT_MAX)` — background task output | Crash during task creation |
| `sx_mcp.c` | 701 | `realloc(buf, cap)` — MCP tool list | Null deref + use-after-free |

### 4.2 Potential Use-After-Free

The `realloc` pattern without null check is the primary use-after-free risk:

```c
// When realloc fails, it returns NULL but does NOT free the old pointer.
// However, assigning the result to the same variable:
buf = realloc(buf, cap);  // If this returns NULL, old buf is leaked
                           // and buf is now NULL — subsequent use = crash
```

This pattern appears in:
- `sx_mcp.c:701`
- `sx_provider_gemini_wasm.c:56`
- `sx_provider_openai.c:323`
- `sx_provider_claude_code.c:580`

### 4.3 Memory Leaks

Return paths that skip `free()`:
- `sx_api.c:367`: `sprintf` chain into `buf = malloc(total)` — if the function returns early after partial construction, `buf` is leaked (though no early returns exist currently)
- General: many functions allocate multiple resources without cleanup-on-error patterns (no `goto cleanup` idiom)

---

## 5. Command Injection Analysis

### 5.1 Overview

The codebase contains **232 `system()`/`popen()`/`exec*()` calls**. This is the most significant attack surface.

### 5.2 Critical: Shell Metacharacter Injection via Single Quotes

Multiple locations use single-quote wrapping to "protect" user input in shell commands:

```c
// sxmail_maildir.c:704 — CRITICAL: email folder name from IMAP client
snprintf(cmd, sizeof(cmd), "rm -rf '%s'", path);
system(cmd);  // If path contains: foo'; rm -rf /; echo '

// scorpiox-vm.c:488 — CRITICAL: URL from config
snprintf(cmd, sizeof(cmd), "curl -fSL --progress-bar -o '%s' '%s'", dest, url);
system(cmd);  // If url contains single quotes

// scorpiox-unshare.c:427
snprintf(cmd, sizeof(cmd), "tar xf '%s' -C '%s'", tar_path, rootfs_dir);

// scorpiox-server.c:688
snprintf(cmd, sizeof(cmd), "git -C '%s' rev-parse HEAD 2>&1", repo_dir);
```

Single-quote wrapping is **not sufficient** — an attacker providing a value like `'; malicious_command; echo '` breaks out of the quotes.

### 5.3 Critical: Argument Concatenation

```c
// scorpiox-agent.c:1313 — find -exec with agent directory
snprintf(cmd, sizeof(cmd),
    "find '%s' -name '*.csproj' -exec grep -li 'scorpiox\\.agent\\.dotnet' {} \\;",
    agent_dir);
FILE *fp = popen(cmd, "r");
```

### 5.4 High-Risk `system()` Calls by Component

| Component | Count | User Input Path | Risk |
|-----------|-------|-----------------|------|
| `scorpiox-wsl.c` | 12 | Distro names, bind paths, WSL args | High — user-provided distro names flow to `system()` |
| `scorpiox-unshare.c` | 15 | Rootfs paths, tar paths, package lists | High — paths from config |
| `scorpiox-agent.c` | 8 | Agent directory, hook scripts | Critical — agent-provided paths |
| `sxmail_maildir.c` | 2 | Folder name from IMAP `DELETE` | Critical — network input |
| `scorpiox-vm.c` | 4 | Image URLs, file paths | High — URLs from user |
| `sx.c` | 8 | Session IDs, file paths, voice commands | Medium — local user input |
| `scorpiox-server.c` | 2 | Git repo paths (CGI) | High — web-accessible |

**Recommendation:** Replace all `system()`/`popen()` calls with `fork()`+`execve()` with explicit argv arrays, or implement proper shell-escaping functions.

---

## 6. Race Condition Analysis

### 6.1 Threading Model

The codebase uses POSIX threads in two primary areas:
- **`sx_dll.c`**: Worker thread with mutex-protected event queue and input queue — properly synchronized
- **`sx_cache_keepalive.c`**: Keepalive thread with mutex protection — properly synchronized

Both use `pthread_mutex_lock/unlock` consistently. **No obvious data races** in the mutex-protected sections.

### 6.2 Thread-Unsafe Static Buffers

`sx_http_wasm.c` uses multiple `static` buffers:
```c
static char sx_xhr_response_buf[65536];  // Line 39
static char json[4096];                   // Line 303
static char json[32768];                  // Line 388
static char body_copy[65536];            // Line 639
```
These are in WASM context (single-threaded), so this is low-risk in practice but violates safe coding principles.

### 6.3 Signal Handler Safety

```c
// sx_term.c:515
static void term_sigwinch(int sig) {
    (void)sig;
    g_term.resized = true;  // Safe: volatile-style flag pattern
}
```
The handler only sets a boolean flag — this is the correct async-signal-safe pattern. However, `signal()` is used instead of `sigaction()`, which has undefined behavior for re-entrant signals on some platforms.

### 6.4 TOCTOU (Time-of-Check-Time-of-Use)

```c
// scorpiox-unshare.c:124,141
return access(prog, X_OK) == 0;
// ... later ...
execve(buf, ...);  // File could have changed between access() and exec()
```

30+ instances of `stat()`/`access()` followed by `open()`/`exec()`. Low risk in most contexts (local programs, not setuid) but technically exploitable.

### 6.5 PID File Races

```c
// sxmux_session.c:1558
if (fscanf(pf, "%d", &existing_pid) == 1 && existing_pid > 0) {
    // Check if pid is alive, then decide to reuse
    // Race: pid could die and be recycled between check and use
}
```

---

## 7. Compiler Warning Analysis

Compilation with `-Wall -Wextra -Wformat-security -Wstack-protector` produced **95 unique warnings**:

| Warning Type | Count | Security Impact |
|-------------|-------|-----------------|
| `-Wunused-result` | 73 | **Medium**: Ignored `system()`, `read()`, `write()`, `chdir()`, `symlink()`, `fread()` return values can mask failures silently |
| `-Wunused-function` | 13 | **Low**: Dead code |
| `-Wunused-variable` | 3 | **Info**: Minor |
| `-Wunused-const-variable` | 2 | **Info**: Minor |
| `-Wswitch` | 1 | **Low**: Unhandled enum case in `sx.c:927` (`SX_CMDSRC_BUILTIN_CMD`) |
| `-Wtype-limits` | 1 | **Low**: Comparison always true/false |

**Notable:** The project already suppresses `-Wformat-truncation` and `-Wno-stringop-truncation` in CMakeLists.txt — these may mask legitimate truncation bugs.

**Key ignored return values:**
- `system()` return in `scorpiox-unshare.c` (8 instances) — cannot detect failed commands
- `read()` / `write()` in `scorpiox-unshare.c`, `sx_term.c` — potential partial I/O
- `chdir()` in `scorpiox-unshare.c` — process may operate in wrong directory

---

## 8. Positive Security Findings

The codebase demonstrates several good security practices:

1. **`snprintf` dominance**: 2,172 of 2,192 format calls use `snprintf` with `sizeof()` bounds (99.1%)
2. **HTTP header parsing**: `scorpiox-server.c` properly bounds header extraction with `sizeof()` checks
3. **Filename sanitization**: `scorpiox-beam.c:755` uses `sx_basename()` to strip path components from received filenames
4. **SMTP size limits**: `sxmail_smtp.c` enforces `SXMAIL_SMTP_MAX_MSG_SIZE` on accumulated data
5. **Network buffer management**: `sx_http.c` `write_cb` uses doubling-growth `realloc` with null checks on realloc (partial — the check exists but the overflow on doubling does not)
6. **Content-Type bounds**: HTTP server checks `ct_len >= sizeof(req->content_type)` before copy
7. **No format string vulns**: All `printf`-family calls use literal format strings

---

## 9. CVSS Risk Scoring Summary

| Risk Level | Count | Findings |
|------------|-------|----------|
| **CRITICAL** (9.0+) | 4 | V-01, V-02, V-03, V-04 — Shell command injection |
| **HIGH** (7.0-8.9) | 7 | V-05 through V-11 — Buffer overflows, null derefs, integer overflows |
| **MEDIUM** (4.0-6.9) | 8 | V-12 through V-19 — Thread safety, input validation, bounded copies |
| **LOW** (1.0-3.9) | 4 | V-20 through V-23 — Ignored returns, TOCTOU, safe strcpy |
| **Total** | **23** | |

---

## 10. Remediation Recommendations

### Priority 1 — Critical (Immediate)

1. **Eliminate `system()`/`popen()` with unsanitized input**: Replace all `system(snprintf(..., "'%s'", user_input))` patterns with `fork()`+`execve()` using explicit `argv[]` arrays. This eliminates shell interpretation entirely.

2. **Implement shell-escape function**: If `system()` must be used, create `sx_shell_escape(const char *input, char *output, size_t output_size)` that escapes all shell metacharacters (`'`, `"`, `` ` ``, `$`, `\`, `!`, `(`, `)`, `{`, `}`, `|`, `&`, `;`, `<`, `>`, `\n`).

3. **Audit `sxmail_maildir.c:704`**: This is network-reachable via IMAP DELETE — an attacker with mailbox access can execute arbitrary commands by creating a folder with shell metacharacters in the name.

### Priority 2 — High (This Sprint)

4. **Replace all 20 `sprintf` calls with `snprintf`**: Especially the chain patterns in `sx_http_wasm.c`, `sx_api.c`, and `sx_exec_wasm_builtins.c`.

5. **Add null checks after all 166 allocation calls**: Use a wrapper macro:
   ```c
   #define SX_ALLOC(ptr, size) do { \
       (ptr) = malloc(size); \
       if (!(ptr)) { SX_ERROR("OOM at %s:%d", __FILE__, __LINE__); abort(); } \
   } while(0)
   ```

6. **Fix `realloc` patterns**: Never assign `realloc()` result to the same pointer:
   ```c
   char *tmp = realloc(buf, cap);
   if (!tmp) { free(buf); return -1; }
   buf = tmp;
   ```

7. **Fix `strcat` loops** in `scorpiox-wsl.c` and `scorpiox-traffic.c`: Use `snprintf` with remaining-space tracking.

### Priority 3 — Medium (Next Sprint)

8. **Replace `signal()` with `sigaction()`** for portable, reliable signal handling.
9. **Add integer overflow checks before multiplication**: `if (nmemb && size > SIZE_MAX / nmemb) return 0;`
10. **Validate `Content-Length`** against a maximum (e.g., 100MB) in HTTP server before allocating.
11. **Replace remaining `strcpy` calls** with `snprintf(dst, sizeof(dst), "%s", src)`.

### Priority 4 — Low (Backlog)

12. **Fix all 73 ignored-return-value warnings**: Check `system()`, `read()`, `write()` returns.
13. **Replace `access()`+`exec()` TOCTOU patterns** with `fexecve()` or `openat()`.
14. **Remove `-Wno-format-truncation`** from CMakeLists.txt and fix any resulting warnings.
15. **Add `-Wformat-security`** to default build flags permanently.
16. **Consider AddressSanitizer** (`-fsanitize=address`) in CI/debug builds for runtime detection.

---

## Appendix A: Unsafe Function Usage Counts

| Function | Occurrences | Safe Alternative |
|----------|------------|-----------------|
| `sprintf` | 20 | `snprintf` |
| `strcpy` | 28 | `snprintf` / `strlcpy` |
| `strcat` | 9 (functional) | `snprintf` / `strlcat` |
| `sscanf` | 15 | Field-width limits |
| `fscanf` | 15 | `fgets` + `sscanf` |
| `fgets` | 122 | (safe when used correctly ✓) |
| `system` | ~120 | `fork`+`execve` |
| `popen` | ~50 | `fork`+`execve`+`pipe` |
| `signal` | 4 | `sigaction` |
| `access` | 15 | `faccessat` / `open` |
| `atoi` | 3 | `strtol` with validation |

## Appendix B: Files with Highest Risk Concentration

| File | Lines | Risk Factors |
|------|-------|-------------|
| `scorpiox-unshare.c` | ~2400 | 15 `system()` calls, TOCTOU, ignored returns, `strcpy` |
| `scorpiox-wsl.c` | ~1300 | 12 `system()` calls, `strcat` overflow, `strcpy` |
| `scorpiox-vm.c` | ~3149 | `system()` with URLs, `popen()`, `ioctl()` without full error checking |
| `scorpiox-agent.c` | ~2200 | 8 `system()`/`popen()` with agent paths, `find -exec` injection |
| `sxmail_maildir.c` | ~900 | `system("rm -rf")` with IMAP folder name — network reachable |
| `scorpiox-server.c` | ~2200 | HTTP request parsing, CGI environment, `popen()` |
| `sx_dll.c` | ~1100 | Threading, fixed `enqueue_buf`, `sprintf` |
| `sx_http_wasm.c` | ~1200 | Static buffers, `sprintf` chains, thread-unsafe globals |
| `sx_api.c` | ~420 | `sprintf` chain, `realloc` without null check |
| `sxmail_smtp.c` | ~800 | Network input parsing, `realloc` without null check |

---

*End of Security Audit Report*
