Vulnerability GHSA-9vhv-g5hf-m7m7
Summary
fast-agent: `fast-agent serve` binds HTTP to 0.0.0.0 with no authentication by default; with --shell this is unauthenticated remote command execution
Details
Summary
fast-agent serve exposes the agent over MCP. By default it uses HTTP transport bound to 0.0.0.0 (all interfaces) on port 8000 with no authentication (auth=None) and prints no warning. Any host that can reach the port can call the exposed agent tool. When the server is started with --shell (which is -x, the flag used in the README quickstart uvx fast-agent mcp@latest -x), the agent has a local execute shell tool, so an unauthenticated remote client can drive the agent into running arbitrary OS commands — unauthenticated RCE.
Affected code
cli/commands/serve.py— transport defaults to HTTP;--hostdefaults to0.0.0.0; port 8000.mcp/server/agent_server.py—auth_provider = NoneunlessFAST_AGENT_SERVE_OAUTH=huggingface;run_async(transport="http", host=host, ...)withhostdefaulting to0.0.0.0.tools/local_shell_executor.py— the shell tool runs commands viacreate_subprocess_shell.
Verified (live, faithful — published 0.7.20 in Docker python:3.13-slim, stock unauthenticated client)
- Unauthenticated exposure (core defect): a client with no Authorization header completed
initializeandtools/list(->['agent']) againsthttp://HOST:8000/mcp. Model-independent. - Unauthenticated RCE with
--shell, demonstrated two ways:- Real production model (
gpt-4o-mini): a single plain-language message asking the agent to run a command via its shell tool -> the model autonomously calledexecuteand the command output (process identity showing it ran as root, the hostname, and the first lines of a sensitive system file) was returned to the unauthenticated caller. - Key-free deterministic (
passthroughmodel): the tool-call directive invoked the shell tool directly, same result. The server-side permission prompt did not block this in non-interactive serve mode.
- Real production model (
Proof of concept
Environment: Docker python:3.13-slim, pip install fast-agent-mcp -> 0.7.20.
# server (real model + the documented shell flag; 0.0.0.0 is also the built-in default)
OPENAI_API_KEY=*** fast-agent serve --model gpt-4o-mini --shell --host 0.0.0.0 --port 8000
# -> "Uvicorn running on http://0.0.0.0:8000" (no auth, no warning)
# attacker: unauthenticated MCP HTTP client (NO Authorization header) to http://HOST:8000/mcp
# initialize() -> 200 OK (server fast-agent-MCP-Server 0.7.20)
# tools/list -> ['agent']
# tools/call name="agent" arguments={"message":
# "Using your execute/shell tool, run exactly this command and return its raw stdout: "
# "id; echo PWNED=$(hostname); cat /etc/passwd | head -2"}
#
# response returned to the UNAUTHENTICATED caller:
# uid=0(root) gid=0(root) groups=0(root)
# PWNED=<host>
# <first lines of /etc/passwd>
Also reproduced key-free with --model passthrough and the message ***CALL_TOOL execute {"command":"id"} (passthrough invokes the tool deterministically). Full transcript + client scripts were sent by email.
Impact
An unauthenticated client that can reach the serve port controls the agent: with --shell this is remote command execution as the server user; without --shell it is unauthenticated use of the operator's LLM and any configured MCP servers' tools. The 0.0.0.0 default makes the server reachable beyond localhost as soon as it runs on a non-isolated host. Preconditions (serve + shell) are noted above and are why I defer the exact rating to you.
Remediation
- Default the bind host to
127.0.0.1; require an explicit--host 0.0.0.0opt-in for non-loopback. - Print a loud warning when binding a non-loopback interface without authentication.