#1017·llamafile

Sandbox combined mode (TUI + server) instead of skipping entirely

Author: aittalamCreated Jul 14, 2026Updated Jul 14, 2026
Labelsenhancement

Prerequisites

  • I am running the latest code.
  • I searched using keywords relevant to my issue to make sure that I am creating a new issue that is not already open (or closed).

Feature Description

Follow-up to #930. Combined mode (the default llamafile -m model.gguf, TUI chat + HTTP server in one process) currently skips pledge()/SECCOMP sandboxing entirely — see sandbox: disabled in combined mode; use --server for a sandboxed server in tools/server/server.cpp (patched via llama.cpp.patches/patches/tools_server_server.cpp.patch). The stated reason is that the in-process TUI acts as an HTTP client and needs connect(), which the server's default accept-only (anet) promise forbids.

That reasoning only explains why anet alone doesn't work — it doesn't require skipping the sandbox altogether. The same codebase already has a mechanism for this: needs_outbound relaxes anetinet for other connect()-needing features (--rpc, server-side tools, the MCP proxy), keeping writes/exec blocked. Combined mode looks like it fits the same pattern.

Motivation

Investigation (see thread with @claude, and design notes in docs/security.md / the sandboxing project memory) suggests combined mode is sandboxable:

  • The TUI thread is spawned via pthread_create inside the on_ready callback in llamafile/main.cpp (combined_main), which only fires after ctx_http.start() — i.e., after the point where pledge() would already be installed on the main thread. Cosmo's per-thread filter inheritance rule means the TUI thread would automatically inherit whatever promise set is installed pre-start(), the same way server worker threads do today.
  • Tracing the combined-mode chat commands (llamafile/chatbot_comm.cpp, chatbot_backend.h, chatbot_file.cpp):
    • /dump is already disabled in API/combined mode (supports_dump() defaults false, only true for the standalone --chat direct-model backend in chatbot_direct.cpp), so the wpath/cpath requirement standalone --chat has doesn't apply here.
    • /upload only stat()s + reads a file (pure rpath, no writes).
    • /push, /pop, /clear, /undo, /forget, /stats, /context are in-memory only, no filesystem I/O.
  • So the promise set combined mode would need is approximately stdio inet rpath tty (plus wpath cpath only if the server side already needs them for slot-save/prompt-cache) — no more permissive than what standalone --chat already runs with today, unsandboxed in that one dimension.

Possible Implementation

  • Extend needs_outbound (llamafile/sandbox.c) to also trigger when combined_mode is true, so anet relaxes to inet.
  • Fold tty into the promise string built for the combined-mode case (mirroring chatbot_main.cpp's own "stdio rpath wpath cpath tty" for standalone --chat).
  • Wire this through the tools_server_server.cpp.patch call site, which currently just logs a skip warning when combined_mode is true, instead of building a real spec.
  • Note: GPU mode still forces a total skip independently (sandbox_skip_status()), so this only changes behavior for combined mode run with --gpu disable.

This is currently tracked as a known deferred gap (not a router-mode-style permanent no-op) in the sandboxing design notes from #930's implementation.