Repository navigation
Description
Problem
Compose runs python:3.12-slim / node:22-bookworm as root, bind-mounts the entire repo .:/app, installs uv/npm at container start, and publishes 8000 and 3000. Railway startCommand is uv sync && … python -m mutiny_api --host 0.0.0.0. There is no Dockerfile; images are generic language bases.
Why it matters
Runtime supply-chain (whatever PyPI/npm serve at boot), root + bind-mount (container escape = host write), and an exposed operator UI (see MUT-001). This is a dev-shaped deploy artifact, not a confirmed RCE by itself.
Evidence
- Audit ID: MUT-012 (MEDIUM, confirmed as written in manifests).
docker-compose.yml— nouser:,volumes: [.:/app],pip install uv && uv sync/npm installincommand, ports 8000 and 3000.railway.toml—uv sync && mkdir -p data && uv run python scripts/ensure_sample_mutiny.py && uv run python -m mutiny_api --host 0.0.0.0.
Attack surface / Preconditions / Impact / Current mitigation / Recommended fix direction
- Attack surface: Compose/Railway Hosted processes, host bind-mount, boot-time package install.
- Preconditions: Operator uses default compose or Railway start as committed; typically combined with MUT-001’s published UI port.
- Impact: Compromised PyPI/npm at start, or container breakout writing the host tree; not a Mutiny application RCE proof.
- Current mitigation:
MUTINY_API_TOKENrequired for non-loopback API bind; Hosted does not exec customer adapters. - Recommended fix direction: Image-build deps, non-root user, mount only
data/, publish UI only on loopback (with MUT-001).
Reproduction
Manifests as written. No live Docker escape was attempted.
Expected behavior
Default compose is safe for local demo: non-root if feasible, deps baked or locked at build, host mount limited to data, UI not published to all interfaces by default.
Suggested implementation direction
Add a real Dockerfile (or nixpacks build phase) that uv sync/npm ci at build. Run as non-root. Volume-mount ./data only. Align Railway start to use the built image without uv sync at every boot if possible.
Acceptance criteria
- Default compose does not bind-mount the full git worktree into a root container as the documented happy path (data-only mount or documented “dev bind-mount” override).
- Runtime
pip install/npm install/uv syncis not required on every container start for the documented path (build-time install). - Process is not root or SECURITY.md explicitly says compose is local-dev-only and unsafe for exposed hosts.
-
python -m mutiny_apifail-closed bind/token behavior unchanged. - Document the threat model change in SECURITY.md / COLD_START.
Scope
docker-compose.yml, railway.toml, optional Dockerfile, SECURITY.md. Out of scope: MUT-001 auth BFF (related but separate issue), MUT-066 Railway sample ensure (deferred).
Related audit findings
MUT-012. Related: MUT-001 (UI bind), MUT-057 (raw uvicorn — documented, skip), MUT-066 (deferred).
Contributor notes
Generated from the Mutiny deep audit.
help wanted. Not a good-first-issue (deploy architecture).
Activity
BasilZafar11 commentedon Oct 24, 2026
Opened PR #135 to fix #63. Dependencies now install during image builds, both services run as non-root, Compose uses data-only storage, and ports bind to loopback. Railway startup uses the built environment. All seven CI checks pass.
#135
docs: index #60, #62, #63 as shipped; changelog for #132-#139
fix(deploy): build non-root Hosted images with data-only storage (MUT…