filesystem-mcp-server ALLOWED_PATHS Prefix-Check Bypass via read_file_tool / write_file_tool
1) CNA / Submission Type
- Submission type: Report a vulnerability (CVE ID request)
- Reporter role: Independent security researcher
- Report date: April 13, 2026
2) Reporter Contact (fill before submit)
- Reporter name:
Winegee
- Reporter email:
Winegee@zju.edu.cn
- Permission to share contact with vendor:
Yes
3) Vendor / Product Identification
- Vendor: geekgod382
- Product: filesystem-mcp-server
- Repository: https://github.com/geekgod382/filesystem-mcp-server.git
- Reviewed local source path:
python-batch-04/datasets/geekgod382-filesystem-mcp-server
- Confirmed affected function:
is_path_allowed (reached by read_file_tool, write_file_tool, and the other file-management tools)
- Reviewed source file:
server.py
4) Vulnerability Type
- CWE: CWE-73 (External Control of File Name or Path)
- Short title: the
ALLOWED_PATHS guard can be bypassed with sibling-prefix paths, exposing file operations outside the intended root
5) Affected Versions
- Confirmed affected: current scanned revision in the local dataset
- Suspected affected range: revisions containing the same request-to-sink flow documented below
- Fixed version: Not available at time of report (April 10, 2026)
6) Vulnerability Description
The repository README says access is limited to configured ALLOWED_PATHS, with the default root set to the operator's home directory. In server.py, that policy is implemented by is_path_allowed(path), which computes os.path.abspath(path) and then checks abs_path.startswith(allowed) for each configured root.
That string-prefix check is bypassable. If /home/alice is allowlisted, a sibling path such as /home/alice_backup/loot.txt still starts with /home/alice and is therefore treated as allowed even though it is outside the intended root.
Because the same helper guards read_file_tool, write_file_tool, delete, move, copy, and directory-listing operations, an untrusted MCP caller can use prefix-sharing paths to read, write, or delete files outside the configured ALLOWED_PATHS boundary.
7) Technical Root Cause
- Documented trust boundary
- The README says access should stay inside configured
ALLOWED_PATHS and specifically claims "No access to system paths."
- Relevant docs:
README.md:29-37
- Vulnerable allowlist check
server.py:29-32 implements the guard as abs_path = os.path.abspath(path) followed by abs_path.startswith(allowed).
- This compares raw string prefixes instead of canonical path ancestry.
- Reachable sinks after the bypass
read_file_tool uses the attacker-controlled path in open(path, 'r', ...) at server.py:278.
write_file_tool creates directories and writes to the attacker-controlled path at server.py:313-315.
- Additional tools reuse the same guard before delete, copy, move, list, and stat operations elsewhere in
server.py.
- Audit conclusion
- The real issue is allowlist-bypass by prefix confusion, not complete absence of a path check.
8) Attack Prerequisites
- The attacker can invoke any exposed file-management MCP tool that relies on
is_path_allowed().
- The server accepts attacker-controlled
path without a stricter validation layer in front of the confirmed sink.
- The executing process has the network or filesystem privileges needed to make the sink security-relevant.
9) Proof of Concept / Reproduction Guidance
The most direct reproduction is to choose a path that shares the allowlisted prefix but is not actually inside the allowlisted directory.
- Example environment assumption
- Assume the operator leaves the default policy in place and
ALLOWED_PATHS = ["/home/alice"].
- Prepare a sibling directory such as
/home/alice_backup/.
- Reproduction request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_file_tool",
"arguments": {
"path": "/home/alice_backup/codex-poc.txt",
"content": "prefix bypass proof",
"encoding": "utf-8"
}
}
}
- Why this works
os.path.abspath("/home/alice_backup/codex-poc.txt") still starts with the string /home/alice, so is_path_allowed() returns true even though the path is outside the real allowlisted directory.
- The same bug also accepts sibling-prefix paths such as
/Users/alice_evil/... on macOS when /Users/alice is allowlisted.
- Expected result
is_path_allowed("/home/alice_backup/codex-poc.txt") returns true because the absolute path still starts with the string /home/alice.
- The server reaches the write sinks in
server.py:313-315 and creates /home/alice_backup/codex-poc.txt, even though that file is outside the intended allowlisted root.
- The same prefix-confusion pattern can then be reused against read, delete, copy, move, and directory-listing tools.
10) Security Impact
- Confidentiality: High when attacker-controlled paths can expose sensitive local files.
- Integrity: High when attacker-controlled paths can overwrite, create, or delete files.
- Availability: Medium to High depending on whether critical files or working directories are affected.
- Scope: Unchanged.
11) CVSS v3.1 Suggestion
- Suggested vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Suggested base score: 9.8 (Critical) when arbitrary file read/write/delete is reachable via the exposed tool.
12) Workarounds / Mitigations
- Restrict filesystem operations to a fixed allowlisted root directory.
- Resolve and canonicalize attacker-controlled paths before use, then enforce root-boundary checks.
- Reject traversal tokens, absolute paths, and symlink escapes where not explicitly required.
- Restrict sensitive file tools to trusted authenticated callers only.
13) Recommended Fix
- Add canonicalization plus root-boundary enforcement before each filesystem sink.
- Replace free-form path inputs with structured identifiers or allowlisted relative paths.
- Add regression tests covering
.., absolute paths, symlink escapes, and mixed-encoding traversal payloads.
- Document the intended trust boundary for all file-oriented MCP/HTTP tools.
14) References
15) Credits
- Discoverer:
Winegee
- Discovery method: Static analysis (CodeQL) plus local source-code audit
16) Additional Notes for Form Mapping
- Audit verdict: Confirmed vulnerability.
- Total reviewed SARIF results for this repository/rule group: 34
- Dynamic exploit replay status: not completed in this batch update.
- Version-range accuracy should still be finalized by the maintainer against release history before public disclosure.
filesystem-mcp-server ALLOWED_PATHS Prefix-Check Bypass via
read_file_tool/write_file_tool1) CNA / Submission Type
2) Reporter Contact (fill before submit)
WinegeeWinegee@zju.edu.cnYes3) Vendor / Product Identification
python-batch-04/datasets/geekgod382-filesystem-mcp-serveris_path_allowed(reached byread_file_tool,write_file_tool, and the other file-management tools)server.py4) Vulnerability Type
ALLOWED_PATHSguard can be bypassed with sibling-prefix paths, exposing file operations outside the intended root5) Affected Versions
6) Vulnerability Description
The repository README says access is limited to configured
ALLOWED_PATHS, with the default root set to the operator's home directory. Inserver.py, that policy is implemented byis_path_allowed(path), which computesos.path.abspath(path)and then checksabs_path.startswith(allowed)for each configured root.That string-prefix check is bypassable. If
/home/aliceis allowlisted, a sibling path such as/home/alice_backup/loot.txtstill starts with/home/aliceand is therefore treated as allowed even though it is outside the intended root.Because the same helper guards
read_file_tool,write_file_tool, delete, move, copy, and directory-listing operations, an untrusted MCP caller can use prefix-sharing paths to read, write, or delete files outside the configuredALLOWED_PATHSboundary.7) Technical Root Cause
ALLOWED_PATHSand specifically claims "No access to system paths."README.md:29-37server.py:29-32implements the guard asabs_path = os.path.abspath(path)followed byabs_path.startswith(allowed).read_file_tooluses the attacker-controlled path inopen(path, 'r', ...)atserver.py:278.write_file_toolcreates directories and writes to the attacker-controlled path atserver.py:313-315.server.py.8) Attack Prerequisites
is_path_allowed().pathwithout a stricter validation layer in front of the confirmed sink.9) Proof of Concept / Reproduction Guidance
The most direct reproduction is to choose a path that shares the allowlisted prefix but is not actually inside the allowlisted directory.
ALLOWED_PATHS = ["/home/alice"]./home/alice_backup/.{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "write_file_tool", "arguments": { "path": "/home/alice_backup/codex-poc.txt", "content": "prefix bypass proof", "encoding": "utf-8" } } }os.path.abspath("/home/alice_backup/codex-poc.txt")still starts with the string/home/alice, sois_path_allowed()returns true even though the path is outside the real allowlisted directory./Users/alice_evil/...on macOS when/Users/aliceis allowlisted.is_path_allowed("/home/alice_backup/codex-poc.txt")returns true because the absolute path still starts with the string/home/alice.server.py:313-315and creates/home/alice_backup/codex-poc.txt, even though that file is outside the intended allowlisted root.10) Security Impact
11) CVSS v3.1 Suggestion
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H12) Workarounds / Mitigations
13) Recommended Fix
.., absolute paths, symlink escapes, and mixed-encoding traversal payloads.14) References
server.py15) Credits
Winegee16) Additional Notes for Form Mapping