gel-mcp Arbitrary File Read via fetch_rule Path Traversal
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: geldata
- Product: gel-mcp
- Repository: https://github.com/geldata/gel-mcp.git
- Reviewed local source path:
python-batch-04/datasets/geldata-gel-mcp
- Confirmed affected tool:
fetch_rule
- Reviewed source file:
src/gel_mcp/server.py
4) Vulnerability Type
- CWE: CWE-73 (External Control of File Name or Path)
- Short title: path traversal in
fetch_rule lets callers read files outside the bundled Gel rule set
5) Affected Versions
- Confirmed affected: 0.1.0
- 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
gel-mcp exposes two rule-related tools: list_rules(), which enumerates the bundled Markdown rule files under RULES_DIR, and fetch_rule(rule_name), which is supposed to return one of those bundled files by name. The implementation of fetch_rule() joins attacker-controlled rule_name directly under RULES_DIR and then calls exists() / read_text() on the resulting path.
Because the code does not reject path separators, traversal tokens, or absolute paths, a caller can request files outside the bundled rule directory. For example, ../../../../../pyproject.toml resolves from src/gel_mcp/static/gel-ai-rules/src/ back to the repository root and returns the project's pyproject.toml instead of a packaged rule file.
7) Technical Root Cause
- Intended trust boundary
src/gel_mcp/server.py:18-20
RULES_DIR is a fixed directory containing bundled Gel AI rule files.
list_rules() restricts visible files to RULES_DIR.glob("*.md"), which establishes the expected "rule name only" input model.
- Vulnerable tool entry point
src/gel_mcp/server.py:143-152
fetch_rule(rule_name: str) accepts an arbitrary string rather than a member of the list_rules() result set.
- Unsafe path resolution
src/gel_mcp/server.py:149-152
rule_path = RULES_DIR / rule_name
if not rule_path.exists(): raise FileNotFoundError(...)
return rule_path.read_text()
- Audit conclusion
- The server intends to expose only packaged rule documents, but the unchecked join allows traversal to any readable local file reachable from the server process.
8) Attack Prerequisites
- The attacker can invoke the
fetch_rule MCP tool.
- The target file is readable by the account running
gel-mcp.
- No upstream proxy or wrapper constrains
rule_name to the filenames returned by list_rules().
9) Proof of Concept / Reproduction Guidance
- Reproduction input
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "fetch_rule",
"arguments": {
"rule_name": "../../../../../pyproject.toml"
}
}
}
- Why this works
RULES_DIR points to src/gel_mcp/static/gel-ai-rules/src/.
- Appending
../../../../../pyproject.toml escapes that directory back to the repository root.
- The server checks only whether the resolved path exists and then returns its contents.
- Expected observable result
- The tool returns the text of
pyproject.toml instead of a bundled Gel rule such as gel.md.
- Any other readable file reachable by traversal from
RULES_DIR can be exfiltrated the same way.
10) Security Impact
- Confidentiality: High. Attackers can read arbitrary local files that the service account can access.
- Integrity: None directly from the reviewed tool, which only reads files.
- Availability: Low. Repeated reads could expose operational metadata, but no direct write or delete primitive was confirmed here.
- 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:N/A:N
- Suggested base score:
7.5 (High) when untrusted callers can reach the MCP tool.
12) Workarounds / Mitigations
- Do not expose
fetch_rule to untrusted callers until path validation is fixed.
- Prefer serving only names returned by
list_rules() and reject any input containing /, \\, or ...
- Run the service with minimal filesystem privileges so non-rule files are not broadly readable.
13) Recommended Fix
- Canonicalize the candidate path with
resolve() and verify that it remains under RULES_DIR.
- Restrict
rule_name to a validated basename or allowlisted filename set.
- Add regression tests for traversal payloads such as
../, mixed separators, and absolute paths.
14) References
15) Credits
- Discoverer:
Winegee
- Discovery method: Static analysis (CodeQL) plus manual source-code audit
16) Additional Notes for Form Mapping
- Audit verdict: Confirmed vulnerability.
- Total reviewed SARIF results for this repository/rule group: 2
- Realistic PoC payload verified against the repository layout in the local dataset.
- Version-range accuracy should still be finalized by the maintainer against release history before public disclosure.
gel-mcp Arbitrary File Read via
fetch_rulePath Traversal1) CNA / Submission Type
2) Reporter Contact (fill before submit)
WinegeeWinegee@zju.edu.cnYes3) Vendor / Product Identification
python-batch-04/datasets/geldata-gel-mcpfetch_rulesrc/gel_mcp/server.py4) Vulnerability Type
fetch_rulelets callers read files outside the bundled Gel rule set5) Affected Versions
6) Vulnerability Description
gel-mcpexposes two rule-related tools:list_rules(), which enumerates the bundled Markdown rule files underRULES_DIR, andfetch_rule(rule_name), which is supposed to return one of those bundled files by name. The implementation offetch_rule()joins attacker-controlledrule_namedirectly underRULES_DIRand then callsexists()/read_text()on the resulting path.Because the code does not reject path separators, traversal tokens, or absolute paths, a caller can request files outside the bundled rule directory. For example,
../../../../../pyproject.tomlresolves fromsrc/gel_mcp/static/gel-ai-rules/src/back to the repository root and returns the project'spyproject.tomlinstead of a packaged rule file.7) Technical Root Cause
src/gel_mcp/server.py:18-20RULES_DIRis a fixed directory containing bundled Gel AI rule files.list_rules()restricts visible files toRULES_DIR.glob("*.md"), which establishes the expected "rule name only" input model.src/gel_mcp/server.py:143-152fetch_rule(rule_name: str)accepts an arbitrary string rather than a member of thelist_rules()result set.src/gel_mcp/server.py:149-152rule_path = RULES_DIR / rule_nameif not rule_path.exists(): raise FileNotFoundError(...)return rule_path.read_text()8) Attack Prerequisites
fetch_ruleMCP tool.gel-mcp.rule_nameto the filenames returned bylist_rules().9) Proof of Concept / Reproduction Guidance
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "fetch_rule", "arguments": { "rule_name": "../../../../../pyproject.toml" } } }RULES_DIRpoints tosrc/gel_mcp/static/gel-ai-rules/src/.../../../../../pyproject.tomlescapes that directory back to the repository root.pyproject.tomlinstead of a bundled Gel rule such asgel.md.RULES_DIRcan be exfiltrated the same way.10) Security Impact
11) CVSS v3.1 Suggestion
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N7.5 (High)when untrusted callers can reach the MCP tool.12) Workarounds / Mitigations
fetch_ruleto untrusted callers until path validation is fixed.list_rules()and reject any input containing/,\\, or...13) Recommended Fix
resolve()and verify that it remains underRULES_DIR.rule_nameto a validated basename or allowlisted filename set.../, mixed separators, and absolute paths.14) References
src/gel_mcp/server.py15) Credits
Winegee16) Additional Notes for Form Mapping