Title: Python: model MCP server handler parameters (`mcp`, `fastmcp`) as remote sources · Issue #22702 · github/codeql · GitHub
Open Graph Title: Python: model MCP server handler parameters (`mcp`, `fastmcp`) as remote sources · Issue #22702 · github/codeql
X Title: Python: model MCP server handler parameters (`mcp`, `fastmcp`) as remote sources · Issue #22702 · github/codeql
Description: Description of the issue MCP (Model Context Protocol) servers expose tools to LLM agents. Every parameter of a tool, resource or prompt handler is chosen by the model, which prompt injection can steer, or by any client that can reach the...
Open Graph Description: Description of the issue MCP (Model Context Protocol) servers expose tools to LLM agents. Every parameter of a tool, resource or prompt handler is chosen by the model, which prompt injection can st...
X Description: Description of the issue MCP (Model Context Protocol) servers expose tools to LLM agents. Every parameter of a tool, resource or prompt handler is chosen by the model, which prompt injection can st...
Opengraph URL: https://github.com/github/codeql/issues/22702
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Python: model MCP server handler parameters (`mcp`, `fastmcp`) as remote sources","articleBody":"**Description of the issue**\n\nMCP (Model Context Protocol) servers expose tools to LLM agents. Every parameter of a tool,\nresource or prompt handler is chosen by the model, which prompt injection can steer, or by any\nclient that can reach the server over HTTP. That makes these parameters remote input. CodeQL\nhas no models for the Python MCP SDKs, so in an MCP server the security queries have no source to\nstart from.\n\n**Evidence.** [mcp-vulnbench](https://github.com/Alex-Hofer/mcp-vulnbench) pins real, publicly\ndisclosed vulnerabilities in open-source Python MCP servers as vulnerable/fixed commit pairs with\nfunction-level ground truth. It covers command injection, path traversal, SSRF, SQL injection and\ncode injection.\n- With the `security-extended` suite, CodeQL 2.27.1 detects 1 of 26 cases in v0.1.0, and that\n single hit comes from `py/shell-command-constructed-from-input` through its library-input source.\n- Example misses:\n - CVE-2025-68144: argument injection in `mcp-server-git`'s `git_diff`;\n - CVE-2026-43901: path traversal in Wireshark-MCP;\n - CVE-2026-57496: SSRF in NetLicensing-MCP.\n- With the prototype models below (v0.2.0), the same CodeQL finds 13 of 28 cases. The models were\n written on a development half of 12 cases and frozen before the other 16 were measured; on that\n held-out half CodeQL goes from 0 to 9 detected cases (56 %), at 0.27 to 1.09 alarms per KLOC.\n A detection means a finding of the right class inside a function the fix changed or the sink\n function; nothing CodeQL found before is lost. Details:\n [docs/results-v0.2.0.md](https://github.com/Alex-Hofer/mcp-vulnbench/blob/main/docs/results-v0.2.0.md).\n\n**Proposal.** Source models of kind `remote` as Models-as-Data in\n`python/ql/lib/semmle/python/frameworks/`, next to the existing `Stdlib.model.yml` sources and the\n`openai`/`anthropic` sink models, plus `typeModel` rows for the import aliases. With them the\nexisting queries work unchanged and interprocedurally:\n`py/command-line-injection`, `py/path-injection`, `py/full-ssrf`, `py/code-injection` and\n`py/sql-injection`. Coverage:\n- `mcp` 1.x `FastMCP`: `tool()`, `resource()`, `prompt()`, `add_tool(fn)`.\n- `mcp` 2.x `MCPServer` (the renamed FastMCP): the same entry points.\n- The low-level `Server`, two generations:\n - 1.x decorators `call_tool()`, `read_resource()`, `get_prompt()`;\n - 2.x constructor handlers `on_call_tool` and friends, where `params.arguments` / `params.uri`\n is the source.\n- `fastmcp` 2.x–4.x:\n - `tool` and `prompt` (bare, called or as a plain call), `resource()`;\n - `add_tool`, `add_prompt`, `Tool.from_function`, `fastmcp.tools.tool`;\n - the transport headers via `get_http_headers()` and `get_http_request().headers`.\n- Both SDKs: the bearer token of the `Authorization` header, where token verifiers receive it\n (`verify_token` in subclasses of `TokenVerifier`, and of `AuthProvider` in `fastmcp`) and through\n `get_access_token().token`. Real servers build file paths and queries from it.\n\nA prototype pack with a test fixture (one handler per row) is in\n[models/codeql/mcp](https://github.com/Alex-Hofer/mcp-vulnbench/tree/main/models/codeql/mcp):\n- without the models the fixture has 0 alerts, with them every expected alert appears;\n- documented limits:\n - Pydantic-typed parameters (attribute reads are not taint steps; a QL model like the one for\n FastAPI's Pydantic parameters would cover them);\n - headers read through the `Context` object;\n - handlers behind a project decorator (auth or error handling), which is common in real\n servers. With `functools.wraps` the API graph loses the handler. Without it, the source\n lands on the wrapper, and the call `func(*args, **kwargs)` does not lead back to the\n handler. The Flask and FastAPI modeling avoids this by reading the decorator list\n (`result.getADecorator()`).\n\nThe library test would follow `library-tests/frameworks/asyncpg/MaDTest.ql`: a `MaDTest.ql`\nimporting `experimental.meta.MaDTest`, and a fixture where every row has a handler with a\n`# $ mad-source__remote=...` expectation.\n\n**One observation while preparing this.** Data-extension sources are `ThreatModelSource`s but not\n`RemoteFlowSource`s. Three stable queries, `py/nosql-injection`, `py/xml-bomb` and `py/xxe`, take\n`RemoteFlowSource` directly, while the other security customizations take\n`ActiveThreatModelSource`. Remote sources from data extensions therefore never reach those three\nqueries. I checked this with CodeQL 2.27.1 (`security-extended`) on a small file:\n- the same three sinks, once fed from a Flask request and once from an MCP tool parameter\n (a data-extension source of kind `remote`);\n- a control where the MCP parameter reaches `os.system`.\n\nThe Flask variants alert (`py/xxe`, `py/xml-bomb`, `py/nosql-injection`) and so does the control\n(`py/command-line-injection`). The MCP variants of the three queries stay silent.\n\n\u003cdetails>\u003csummary>Reproducer\u003c/summary>\n\n```python\nimport os\nimport xml.etree.ElementTree as ET\n\nimport pymongo\nfrom flask import Flask, request\nfrom lxml import etree\nfrom mcp.server.fastmcp import FastMCP\n\nmcp = FastMCP(\"probe\")\napp = Flask(__name__)\nusers = pymongo.MongoClient().db.users\n\n\n# MCP tool parameters: MaD sources of kind remote\n@mcp.tool()\ndef mcp_xxe(xml: str) -> str:\n parser = etree.XMLParser(resolve_entities=True)\n return str(etree.fromstring(xml, parser=parser)) # MCP-XXE\n\n\n@mcp.tool()\ndef mcp_bomb(xml: str) -> str:\n return ET.fromstring(xml).tag # MCP-BOMB\n\n\n@mcp.tool()\ndef mcp_nosql(name: str) -> str:\n return str(list(users.find({\"$where\": \"this.name == '\" + name + \"'\"}))) # MCP-NOSQL\n\n\n@mcp.tool()\ndef mcp_shell(command: str) -> int:\n return os.system(command) # MCP-SHELL (control: the models are active)\n\n\n# Flask request data: RemoteFlowSource, the control for the three queries\n@app.route(\"/xxe\")\ndef flask_xxe():\n parser = etree.XMLParser(resolve_entities=True)\n return str(etree.fromstring(request.args[\"xml\"], parser=parser)) # FLASK-XXE\n\n\n@app.route(\"/bomb\")\ndef flask_bomb():\n return ET.fromstring(request.args[\"xml\"]).tag # FLASK-BOMB\n\n\n@app.route(\"/nosql\")\ndef flask_nosql():\n name = request.args[\"name\"]\n return str(list(users.find({\"$where\": \"this.name == '\" + name + \"'\"}))) # FLASK-NOSQL\n```\n\n\u003c/details>\n\n**Questions**\n1. Is Models-as-Data the form you prefer here? A QL module (`semmle/python/frameworks/Mcp.qll`)\n could also:\n - find handlers behind project decorators through the decorator list, as `Flask.qll` does;\n - cover Pydantic-typed parameters;\n - reach the three queries above.\n In the benchmark's development half, 2 of 8 misses are servers with such decorators.\n2. Is `remote` the right threat-model kind? Over stdio the input comes from the local client\n process, but its content is chosen by an LLM that reads remote content.\n3. Should the three queries above move to `ActiveThreatModelSource`? I can do that in a separate PR.\n\nI'm happy to open a PR with the models, the library test and a change note.\n","author":{"url":"https://github.com/Alex-Hofer","@type":"Person","name":"Alex-Hofer"},"datePublished":"2026-09-30T02:48:23Z","url":"https://github.com/github/codeql/issues/22702"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:1a022ab3-8236-787d-30ec-dddd88aa068f |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | AAE8:38057F:AA9011:EBD2BF:6AF98F09 |
| html-safe-nonce | d8380dadb81d0f3e719c7245ff673caa09254a3578537a28a886ac4c4455f4c4 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBQUU4OjM4MDU3RjpBQTkwMTE6RUJEMkJGOjZBRjk4RjA5IiwidmlzaXRvcl9pZCI6IjgxODE5Njc3MDYzMjA4OTk4NDkiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 98127242c35948a99f812a1446c74679a53405e97b15f12623e593c5dcc6967f |
| hovercard-subject-tag | issue:5640094952 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/github/codeql/22702/issue_layout |
| twitter:image | https://opengraph.githubassets.com/265b00a316c5283c0001e8b20c98090b736ef7e1560714393a71d57457332880/github/codeql/issues/22702 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/265b00a316c5283c0001e8b20c98090b736ef7e1560714393a71d57457332880/github/codeql/issues/22702 |
| og:image:alt | Description of the issue MCP (Model Context Protocol) servers expose tools to LLM agents. Every parameter of a tool, resource or prompt handler is chosen by the model, which prompt injection can st... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | Alex-Hofer |
| hostname | github.com |
| expected-hostname | github.com |
| None | 612d3e6ffd693e7ea8d9fcf452370995872baef31d825b1fedef704cd3d24ac7 |
| turbo-cache-control | no-preview |
| go-import | github.com/github/codeql git https://github.com/github/codeql.git |
| octolytics-dimension-user_id | 9919 |
| octolytics-dimension-user_login | github |
| octolytics-dimension-repository_id | 143040428 |
| octolytics-dimension-repository_nwo | github/codeql |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 143040428 |
| octolytics-dimension-repository_network_root_nwo | github/codeql |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 072e34035c13c31543fba9f025cbbbda4210dae4 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width