📄 04-vulnerabilities.md
⬇ Download Raw

ScorpioX C Codebase — Security Vulnerability Audit Report

Date: 2026-04-28 Auditor: Security Audit Agent Scope: All project C source files (excluding vendor: mbedtls, yyjson) Codebase: 150 source files, ~143,515 lines of C/H code

Executive Summary

The ScorpioX C codebase is a substantial systems project comprising networking libraries (libsxnet), utility libraries (libsxutil), UI libraries (libsxui), a WebSocket bridge, an HTTP server, mail server components, container management, and various CLI tools. The audit identified 23 distinct vulnerability findings spanning command injection, buffer overflow, memory safety, and race condition categories. The most critical finding is a command injection vulnerability in the HTTP server (scorpiox-server.c) that could allow remote code execution.

Overall, the codebase demonstrates generally sound practices — extensive use of snprintf (2,228 calls) over unsafe sprintf (0 calls), and bounded buffer operations in most code paths. However, the heavy reliance on system()/popen() (164 total calls) with constructed command strings presents a systemic risk surface.


Vulnerability Summary

#SeverityCVSSFileLine(s)CategoryDescription
1CRITICAL9.8scorpiox-server.c2480-2486Command InjectionOTP endpoint injects unsanitized query params into shell command
2HIGH8.1scorpiox-unshare.c309-312Command InjectionURL/path injected into curl/wget system() via single-quote wrapping only
3HIGH7.5sxmail_maildir.c704-711Command InjectionUser-derived path injected into rm -rf '%s' / rmdir /s /q system() call
4HIGH7.5scorpiox-server.c1192-1222Path Traversalurl_decode() applied before path validation; no .. check post-decode
5HIGH7.2sx.c655-659Command Injection$EDITOR env var injected into system() without sanitization
6MEDIUM6.5sx_term.c785-786Memory LeakAllocation null-check logic error — new_front leaked if new_back is NULL
7MEDIUM6.2Multiple filesVariousMissing NULL Checks50+ malloc() calls without subsequent NULL validation
8MEDIUM5.9scorpiox-server.c2059, 2222Return Type Mismatchreturn -1 in void function — undefined behavior, skips cleanup
9MEDIUM5.9scorpiox-mcp.c1240Type ConfusionReturns int (-1) from char function — pointer/integer confusion
10MEDIUM5.5scorpiox-vi.c512Integer Overflowsizeof(Line) E.num_lines — no overflow check before malloc
11MEDIUM5.5sxmux_vt.c910,918Integer Overflow(size_t)cols (size_t)rows — multiplication may overflow for large dimensions
12MEDIUM5.3sx_http.c46-53Integer Overflowsize nmemb in write_cb — classic curl callback overflow pattern
13MEDIUM5.0Multiple (28 locs)VariousUnsafe strcpystrcpy() used 28 times — most with constant strings but some with computed offsets
14MEDIUM5.0ws2tcp.c506-510Weak Path Traversalstrstr(url_path, "..") — bypassable with encoded sequences on some platforms
15MEDIUM4.715+ filesVariousStack Overflow RiskStack buffers ≥64KB (max 65536 bytes) — 5 locations with char[65536] on stack
16MEDIUM4.5sx.c, sx_dll.c3772-3776Diagnostic Alloc5 malloc calls without NULL checks in heap diagnostic — dereferenced if NULL
17LOW3.7sxmux_vt.c919Memory LeakScrollback alloc failure returns NULL without freeing vt->cells
18LOW3.5sx_dll.cVariousRace ConditionWorker thread accesses g_dll.busy volatile flag without mutex in some paths
19LOW3.3sxmux_session.c1841Logic ErrorComparison always false due to limited data type range
20LOW3.1sx_systemprompt.c262Format StringNon-literal format string in strftime — low risk (format from internal config only)
21LOW2.5Multiple filesVariousSignal Handler Safety59 signal() registrations — some handlers call non-async-signal-safe functions
22LOW2.3scorpiox-unshare.cVariousTOCTOUaccess() then open()/exec() patterns — race window exists
23INFO1.5scorpiox-config.c1429,1444,1479Unsafe strcpystrcpy with "1"/"0" into value buffers — safe but should use snprintf

Detailed Analysis

1. Command Injection Analysis

CRITICAL: OTP Endpoint Command Injection (scorpiox-server.c:2480)

// Query parameters 'account' and 'secret' come directly from HTTP request

char cmd[2048];

if (secret && *secret) {

snprintf(cmd, sizeof(cmd), "scorpiox-otp -s \"%s\" -a \"%s\" -j", secret, account);

} else {

snprintf(cmd, sizeof(cmd), "scorpiox-otp -a \"%s\" -j", account);

}

char *otp_out = run_command(cmd, 4096, &otp_exit, NULL); // popen()

Attack Vector: A remote attacker sends GET /api/otp?a=x";$(id);"&s=y — the double-quote escaping is trivially bypassed since url_decode() applies URL decoding but does no shell metacharacter sanitization. CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 Remediation:

HIGH: Download URL Injection (scorpiox-unshare.c:309)

static int download_image(const char url, const char dest) {

char cmd[2048];

snprintf(cmd, sizeof(cmd),

"curl -fSL --progress-bar -o '%s' '%s' 2>&1", dest, url);

if (system(cmd) == 0) return 0;

Single-quote wrapping is insufficient — a URL containing ' breaks out. The url parameter originates from user-specified container image names.

Remediation: Use fork()/execvp() with proper argument passing, or implement HTTP download in-process using the existing libcurl integration.

HIGH: Mail Folder Deletion Injection (sxmail_maildir.c:704)

snprintf(cmd, sizeof(cmd), "rm -rf '%s'", path);

if (system(cmd) != 0) { ... }

The path is constructed from base_path, user, and folder_sub — if any contain single quotes, command injection occurs. The user field may come from IMAP authentication, and folder from IMAP CREATE/DELETE commands.

Remediation: Use nftw() with FTW_DEPTH for recursive deletion, or fork()/execvp("rm", ...) with proper argument separation.

HIGH: Editor Command Injection (sx.c:655)

const char *editor = getenv("EDITOR");

char editor_cmd[512];

snprintf(editor_cmd, sizeof(editor_cmd), "%s %s", editor, tmpfile);

int ret = system(editor_cmd);

The $EDITOR environment variable is injected directly into a shell command. A malicious EDITOR value like vim; malicious_command # achieves code execution.

Risk: Lower in practice since $EDITOR is controlled by the local user, but violates defense-in-depth. Container/sandbox environments may inherit untrusted env.

Systemic Risk: system()/popen() Usage

The codebase uses 115 system() and 49 popen() calls. Files with highest density:

Recommendation: Create a helper function sx_exec_argv(const char prog, char const argv[]) using fork()/execvp() and migrate all system() calls that include user-derived data.

2. Buffer Overflow Analysis

Stack-Allocated Buffer Risks

The codebase contains 2,365 stack-allocated char buffers. The largest:

SizeFileVariable
65,536scorpiox-wsl.c:158char buffer[65536]
65,536scorpiox-multiplexer.c:1484char buf[65536]
65,536sx_http_wasm.c:39static char sx_xhr_response_buf[65536]
65,535scorpiox-multiplexer.c:1212char payload[65535]
32,768sxmail_dkim.c:414char sign_data[32768]
16,384scorpiox-wsl.c:674char cmd[16384]
16,384scorpiox-tmux.c:2247char full_cmd[16384]
16,384scorpiox-sdk.c:1353char wsl_cmdline[16384]
Risk: 64KB stack allocations may cause stack overflow on systems with limited stack size (e.g., threads with 64KB stacks, embedded/WASM environments). Recursive functions calling these could crash. Remediation: Allocate buffers ≥8KB on the heap with proper cleanup, or use alloca() guards.

strcpy Usage (28 locations)

Most strcpy uses copy string literals of known length into adequately-sized buffers (low risk). Notable exceptions:

// scorpiox-unshare.c:140 — SAFE: bounds-checked 2 lines above

if (dlen + 1 + strlen(prog) + 1 > sizeof(buf)) { ... continue; }

strcpy(buf + dlen + 1, prog);

// scorpiox-agent.c:1452 — SAFE: replaces ".sln" with ".dll" (same length)

strcpy(out + len - 4, ".dll");

// scorpiox-sdk.c:364 — MODERATE RISK: assumes buffer has room for "scorpiox-host.exe"

strcpy(slash + 1, "scorpiox-host.exe"); // 17 chars + NUL

// Buffer allocated with readlink(..., buf, size - 20) — safe due to -20 margin

Good practice: The codebase uses snprintf 2,228 times and sprintf 0 times — excellent.

Integer Overflow in Allocation Sizes

// sx_http.c:46 - Classic curl callback overflow

size_t real_size = size * nmemb; // Can overflow if both are large

// sxmux_vt.c:910

vt->cells = calloc((size_t)cols * (size_t)rows, sizeof(SxMuxCell));

// cols*rows can overflow for attacker-controlled terminal dimensions

// scorpiox-vi.c:512

E.undo.lines = malloc(sizeof(Line) * E.num_lines);

// num_lines is int — if large file, sizeof(Line)*num_lines overflows

Remediation: Use overflow-safe multiplication:
if (cols > SIZE_MAX / rows) return NULL;  // overflow check

3. Memory Safety Analysis

Missing NULL Checks After malloc (50+ locations)

Critical instances where NULL dereference could crash the application:

FileLineAllocation
sxtmux_backend_sxmux.c64malloc(len + 2) — used immediately
sxmail_smtp.c245malloc(dkim_cap) — DKIM buffer
sxmail_maildir.c478, 909malloc(fsize + 1) — file read
sxmail_imap.c738malloc(literal_sz + 1) — IMAP message
sx_term.c610, 746, 816, 1170, 1536, 1838, 1845Terminal buffers
sx_dll.c328, 358, 860, 1104DLL interop
sx.c305, 677, 2471, 2562, 3729, 4403Main application
scorpiox-vi.c408, 459, 512, 548, 1167Editor buffers
scorpiox-unshare.c346, 473, 1795Container management
scorpiox-wsl.c885malloc(1024 * 1024) — 1MB allocation
Risk: NULL pointer dereference causes segfault. In server contexts (scorpiox-server, sxmail), this is a denial-of-service vector.

Memory Leak: sx_term.c:785

SxCell *new_front = calloc(cells, sizeof(SxCell));

SxCell *new_back = calloc(cells, sizeof(SxCell));

if (!new_back) return; // BUG: leaks new_front

if (!new_front || !new_back) { free(new_front); free(new_back); return; }

// Line 786 is dead code — new_back was already checked

The first check on line 785 returns without freeing new_front if new_back is NULL but new_front succeeded. The second check on line 786 is unreachable for the !new_back case.

Fix:
if (!new_front || !new_back) { free(new_front); free(new_back); return; }

Memory Leak: sxmux_vt.c:919

vt->scrollback = calloc((size_t)vt->scrollback_cap * (size_t)cols, sizeof(SxMuxCell));

if (!vt->scrollback) return NULL; // Leaks vt->cells (allocated at line 910)

Type Confusion: scorpiox-mcp.c:1240

if (!out) return -1;  // Function returns char — returning (char)-1 (0xFFFFFFFFFFFFFFFF)

// ...

return out;

Callers may check if (!result) which would pass for -1 cast to pointer. This is undefined behavior.


4. Path Traversal Analysis

scorpiox-server.c: Post-Decode Path Traversal

// parse_request() at line 1220:

strncpy(req->path, uri, sizeof(req->path) - 1);

url_decode(req->path); // Decodes %2e%2e to .. AFTER copying

The server calls url_decode() on the path but never checks for .. components after decoding. While extract_website_name() restricts characters to [alnum-_.], the try_fallback path for static file serving (if configured) may be vulnerable.

ws2tcp.c: Weak Traversal Check

if (strstr(url_path, "..")) {  // Blocks literal ".." but not encoded forms

The url_path passed to serve_static() comes from parse_http_request() which does not URL-decode the path. This means %2e%2e in the URL bypasses the check on this server. However, the actual fopen() of the file path would fail for %2e%2e since the filesystem doesn't interpret URL encoding — so the risk is theoretical unless a front-end proxy decodes before forwarding.


5. Race Condition Analysis

Signal Handler Safety

The codebase registers 59 signal handlers. Several perform non-async-signal-safe operations:

// scorpiox-unshare.c - cleanup_handler likely calls free()/close()

signal(SIGINT, cleanup_handler);

signal(SIGTERM, cleanup_handler);

signal(SIGHUP, cleanup_handler);

// sx_term.c:677 - SIGWINCH handler

signal(SIGWINCH, term_sigwinch);

// term_sigwinch likely accesses global terminal state

Risk: If a signal arrives during malloc()/free(), the signal handler calling malloc()/free() causes heap corruption. Remediation: Use sigaction() with SA_RESTART, set a volatile flag in the handler, and check it in the main loop.

TOCTOU in scorpiox-unshare.c

// Line 124: check-then-use

return access(prog, X_OK) == 0; // File could change between access() and exec()

Multiple access() calls followed by exec()/open() create time-of-check-time-of-use windows. In container management code (scorpiox-unshare.c), this is a privilege escalation risk if an attacker can race symlink creation.

Threading: sx_dll.c

The DLL worker thread properly uses mutexes for queue operations. However, the volatile bool busy flag at line 275 is accessed without mutex protection in some paths, which may cause torn reads on non-x86 architectures.


6. Compiler Warning Analysis

Compilation with -Wall -Wextra -Wformat-security produced:

WarningFileSeverityDescription
-Wreturn-typescorpiox-server.c:2059,2222Mediumreturn -1 in void function — undefined behavior
-Wreturn-typescorpiox-openai.c:594MediumSame — return with value in void function
-Wint-conversionscorpiox-mcp.c:1240MediumReturns int from char* function
-Wint-conversionscorpiox-claudecode-fetchtoken.c:12-13LowImplicit int-to-pointer conversion
-Wswitchsx.c:927LowUnhandled enum value SX_CMDSRC_BUILTIN_CMD
-Wformat-nonliteralsx_systemprompt.c:262LowNon-literal format in strftime
-Wtype-limitssxmux_session.c:1841LowComparison always false
-Wunused-variablesxmux_session.c:1832,1776InfoUnused variables

The void-function return errors in scorpiox-server.c are particularly concerning — they silently skip cleanup code (buffer deallocation, socket close) on error paths.


7. Format String Analysis

No format string vulnerabilities found. The codebase consistently uses literal format strings with printf/fprintf/snprintf. All 2,228 snprintf calls use string literal format arguments. The one non-literal format (sx_systemprompt.c:262) is strftime() with an internally-sourced format string.

Risk Scoring Summary

Risk LevelCountAction Required
CRITICAL1Immediate fix — remote code execution
HIGH4Fix within 1 week — command injection / path traversal
MEDIUM12Fix within 1 month — memory safety, integer overflow
LOW5Fix within 1 quarter — race conditions, logic errors
INFO1Best practice improvement

Remediation Recommendations

Priority 1 — Immediate (Critical/High)

  • Replace system()/popen() with fork()/execvp() for all paths that include user-derived data. Create a shared utility:
  •    int sx_safe_exec(const char prog, char const argv[], char **output);
       
  • Sanitize OTP query parameters in scorpiox-server.c — restrict to [a-zA-Z0-9@._-] or use argument arrays.
  • Add post-decode path traversal checks in scorpiox-server.c:
  •    if (strstr(req->path, "..") || req->path[0] != '/') return false;
    

    char *real = realpath(filepath, NULL);

    if (!real || strncmp(real, webroot, strlen(webroot)) != 0) { / reject / }

  • Replace rm -rf '%s' in sxmail_maildir.c with nftw() recursive deletion or fork()/exec("rm","-rf","--",path).
  • Priority 2 — Short Term (Medium)

  • Add NULL checks after all malloc/calloc/realloc calls — particularly in server-facing code (sx_http.c, sxmail_*.c, scorpiox-server.c).
  • Add integer overflow guards before multiplication in allocation sizes:
  •    #define SX_SAFE_MUL(a, b) (((b) != 0 && (a) > SIZE_MAX / (b)) ? 0 : (a) * (b))
       
  • Fix sx_term.c:785 memory leak — remove the redundant first null check.
  • Fix scorpiox-server.c void function returns — change return -1 to proper error handling with cleanup.
  • Replace large stack allocations (≥8KB) with heap allocations — especially char[65536] buffers.
  • Priority 3 — Long Term (Low)

  • Migrate from signal() to sigaction() with async-signal-safe handlers.
  • Eliminate remaining strcpy() calls — replace with snprintf() or bounded copy macros.
  • Add -Wformat-security -Werror=return-type to CI build flags to catch future regressions.
  • Implement TOCTOU-safe patterns — use openat()/fstatat() with directory file descriptors.

  • Methodology


    Report generated 2026-04-28 by Security Audit Agent