Skip to content

Tool server binds 0.0.0.0 with no auth on /get_observation (code execution); shipped scripts hardcode it #32

Description

@EvolveAegis

Summary

Agent0/executor_train/verl_tool/servers/serve.py binds the tool-execution server to 0.0.0.0:5000 by default, and POST /get_observation (serve.py:472-479) has no authentication dependency — the only dependency is a concurrency semaphore (serve.py:463-471). The endpoint forwards actions to tool backends including python_code (executes the action as Python), bash_terminal, ipython_code and sql. Any host that can reach the port can submit code without presenting any credential.

This is not just a theoretical default: the shipped launcher scripts hardcode it. eval_service/scripts/start_api_service.sh:3 sets host=0.0.0.0 and runs python -m verl_tool.servers.serve --host $host --port $port --tool_type "python_code" ..., and examples/train/math_tir/train_7b_grpo_megatron.sh:62 / train_mnodes.sh:52-60 / train_1.5b_grpo.sh:63 run the same server with --tool_type "python_code" bound to the host's primary address.

I confirmed the routing layer has no auth gate by starting the real AsyncToolServer class in-process (FastAPI TestClient, no real network bind) and sending an unauthenticated request: POST /get_observation with no Authorization header → 200, action processed, observations returned. GET /health200.

Details

  • serve.py:77-78host: str = "0.0.0.0", port: int = 5000 (defaults; main() at serve.py:604-605).
  • serve.py:463-471get_semaphore(): concurrency limiter only, no identity check.
  • serve.py:472-479@self.app.post("/get_observation")Depends(get_semaphore) is the only dependency; handler routes to tool_manager.process_actions → per-tool execution (tools/python_code.py:456 executes the action; tools/bash_terminal.py, tools/ipython_code.py, tools/sql.py, tools/sandbox_fusion.py registered).
  • eval_service/scripts/start_api_service.sh:3host=0.0.0.0, --tool_type "python_code" (the code-exec tool), --workers_per_tool 32.
  • tools/python_code.py:21-52 — the only input filter is check_forbidden_imports, exact-string matching ("import subprocess", "os.system"); import subprocess (double space), __import__('sub'+'process'), importlib.import_module('subprocess') and getattr(os, 'system') all bypass it.
  • tools/python_code.py:193-235 — firejail is used only if installed; otherwise a bare [python_path, file] process with the full original environment, silently (no warning about the downgrade).
  • Related surface (code-level; service not started locally): eval_service/app.py:46-52 CORSMiddleware(allow_origins=["*"], allow_credentials=True); POST /completions + /chat/completions (app.py:75-107) with no auth; the vLLM api-key there is the hardcoded public token-abc123 (model_service.py:213); eval_service/config.py:44 binds 0.0.0.0.

How to reproduce

# as shipped (start_api_service.sh binds 0.0.0.0, python_code enabled):
curl -X POST http://<host>:<port>/get_observation \
  -H 'Content-Type: application/json' \
  -d '{"trajectory_ids":["t0"],"actions":["print(\"hello\")"]}'
# -> 200, no credentials required

Impact

Unauthenticated code execution from any reachable host on machines running the shipped training/eval launchers (the scripts bind the primary interface, not loopback). The tool backends execute Python actions with the server process's environment; without firejail there is no sandbox at all, and the filter is string-matching only.

Suggested change

  • Default host to 127.0.0.1 in serve.py and in start_api_service.sh / math_tir scripts (and document the remote case explicitly).
  • Require a token for /get_observation (the server already knows ServerConfig; add a bearer check) — or bind a unix socket.
  • Replace the string-match filter with an allowlist/deny policy that cannot be bypassed by formatting, and fail loudly when firejail is unavailable instead of silently downgrading.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions