Skip to content

Commit 1c9b296

Browse files
committed
Python: Use the ai threat model for MCP handler input
Handler parameters are produced by a language model, so they are sources of the opt-in ai threat model added in github#22810. HTTP headers and bearer tokens come from the HTTP client and stay sources of the remote threat model.
1 parent c971b01 commit 1c9b296

8 files changed

Lines changed: 44 additions & 44 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
---
22
category: minorAnalysis
33
---
4-
* Added modeling of the `mcp` (the official Model Context Protocol SDK) and `fastmcp` PyPI packages. The parameters of MCP tool, resource and prompt handlers, the HTTP headers that `fastmcp` handlers read through `get_http_headers()` or `get_http_request()`, and the bearer token that token verifiers (`verify_token`) and `get_access_token()` receive, are now sources of the `remote` threat model.
4+
* Added modeling of the `mcp` (the official Model Context Protocol SDK) and `fastmcp` PyPI packages. The parameters of MCP tool, resource and prompt handlers are now sources of the opt-in `ai` threat model, as their values are produced by a language model. The HTTP headers that `fastmcp` handlers read through `get_http_headers()` or `get_http_request()`, and the bearer token that token verifiers (`verify_token`) and `get_access_token()` receive, are sources of the `remote` threat model.

‎python/ql/lib/semmle/python/frameworks/fastmcp.model.yml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ extensions:
55
data:
66
# `fastmcp` (PyPI), a standalone MCP server framework. The parameters of tool, resource and
77
# prompt handlers are chosen by the MCP client; `tool` and `prompt` also work without parentheses.
8-
- ['fastmcp.FastMCP', 'Member[tool,prompt].Argument[0,name_or_fn:].Parameter[any]', 'remote']
9-
- ['fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
10-
- ['fastmcp.FastMCP', 'Member[add_tool].Argument[0,tool:].Parameter[any]', 'remote']
11-
- ['fastmcp.FastMCP', 'Member[add_prompt].Argument[0,prompt:].Parameter[any]', 'remote']
12-
- ['fastmcp.tools.Tool!', 'Member[from_function].Argument[0,fn:].Parameter[any]', 'remote']
13-
- ['fastmcp', 'Member[tools].Member[tool].Argument[0,name_or_fn:].Parameter[any]', 'remote']
14-
- ['fastmcp', 'Member[tools].Member[tool].ReturnValue.Argument[0].Parameter[any]', 'remote']
8+
- ['fastmcp.FastMCP', 'Member[tool,prompt].Argument[0,name_or_fn:].Parameter[any]', 'ai']
9+
- ['fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'ai']
10+
- ['fastmcp.FastMCP', 'Member[add_tool].Argument[0,tool:].Parameter[any]', 'ai']
11+
- ['fastmcp.FastMCP', 'Member[add_prompt].Argument[0,prompt:].Parameter[any]', 'ai']
12+
- ['fastmcp.tools.Tool!', 'Member[from_function].Argument[0,fn:].Parameter[any]', 'ai']
13+
- ['fastmcp', 'Member[tools].Member[tool].Argument[0,name_or_fn:].Parameter[any]', 'ai']
14+
- ['fastmcp', 'Member[tools].Member[tool].ReturnValue.Argument[0].Parameter[any]', 'ai']
1515
# The HTTP headers of the transport.
1616
- ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_headers].ReturnValue', 'remote']
1717
- ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_request].ReturnValue.Member[headers]', 'remote']

‎python/ql/lib/semmle/python/frameworks/mcp.model.yml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ extensions:
66
# `mcp`, the official Model Context Protocol SDK. The parameters of tool, resource and prompt
77
# handlers are chosen by the MCP client, that is by an LLM or by anyone who can reach the server.
88
# `FastMCP` (1.x) was renamed to `MCPServer` (2.x).
9-
- ['mcp.server.fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
10-
- ['mcp.server.fastmcp.FastMCP', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote']
11-
- ['mcp.server.mcpserver.MCPServer', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
12-
- ['mcp.server.mcpserver.MCPServer', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote']
9+
- ['mcp.server.fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'ai']
10+
- ['mcp.server.fastmcp.FastMCP', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'ai']
11+
- ['mcp.server.mcpserver.MCPServer', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'ai']
12+
- ['mcp.server.mcpserver.MCPServer', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'ai']
1313
# The low-level server: decorators in 1.x, constructor handlers taking `(ctx, params)` in 2.x.
14-
- ['mcp.server.lowlevel.Server', 'Member[call_tool,read_resource,get_prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
15-
- ['mcp.server.lowlevel.Server!', 'Argument[on_call_tool:,on_get_prompt:].Parameter[1].Member[arguments]', 'remote']
16-
- ['mcp.server.lowlevel.Server!', 'Argument[on_read_resource:].Parameter[1].Member[uri]', 'remote']
14+
- ['mcp.server.lowlevel.Server', 'Member[call_tool,read_resource,get_prompt].ReturnValue.Argument[0].Parameter[any]', 'ai']
15+
- ['mcp.server.lowlevel.Server!', 'Argument[on_call_tool:,on_get_prompt:].Parameter[1].Member[arguments]', 'ai']
16+
- ['mcp.server.lowlevel.Server!', 'Argument[on_read_resource:].Parameter[1].Member[uri]', 'ai']
1717
# The bearer token of the Authorization header reaches token verifiers and the access-token helper.
1818
- ['mcp.server.auth.provider.TokenVerifier!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote']
1919
- ['mcp', 'Member[server].Member[auth].Member[middleware].Member[auth_context].Member[get_access_token].ReturnValue.Member[token]', 'remote']

‎python/ql/test/library-tests/frameworks/fastmcp/test.py‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,57 +14,57 @@
1414

1515

1616
@app.tool
17-
def bare(command): # $ mad-source__remote=command
17+
def bare(command): # $ mad-source__ai=command
1818
return command
1919

2020

2121
@server_app.tool(name="exec")
22-
def called(command): # $ mad-source__remote=command
22+
def called(command): # $ mad-source__ai=command
2323
return command
2424

2525

2626
@module_app.resource("files://{path}")
27-
def resource(path): # $ mad-source__remote=path
27+
def resource(path): # $ mad-source__ai=path
2828
return path
2929

3030

3131
@app.prompt
32-
def bare_prompt(topic): # $ mad-source__remote=topic
32+
def bare_prompt(topic): # $ mad-source__ai=topic
3333
return topic
3434

3535

3636
@app.prompt()
37-
def called_prompt(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style
37+
def called_prompt(topic, style="short"): # $ mad-source__ai=topic mad-source__ai=style
3838
return topic + style
3939

4040

41-
def added(command): # $ mad-source__remote=command
41+
def added(command): # $ mad-source__ai=command
4242
return command
4343

4444

4545
app.add_tool(added)
4646

4747

48-
def added_prompt(topic): # $ mad-source__remote=topic
48+
def added_prompt(topic): # $ mad-source__ai=topic
4949
return topic
5050

5151

5252
app.add_prompt(prompt=added_prompt)
5353

5454

55-
def via_tool(url): # $ mad-source__remote=url
55+
def via_tool(url): # $ mad-source__ai=url
5656
return url
5757

5858

59-
def via_function_tool(url): # $ mad-source__remote=url
59+
def via_function_tool(url): # $ mad-source__ai=url
6060
return url
6161

6262

63-
def via_base_tool(url): # $ mad-source__remote=url
63+
def via_base_tool(url): # $ mad-source__ai=url
6464
return url
6565

6666

67-
def via_module_function_tool(url): # $ mad-source__remote=url
67+
def via_module_function_tool(url): # $ mad-source__ai=url
6868
return url
6969

7070

@@ -75,12 +75,12 @@ def via_module_function_tool(url): # $ mad-source__remote=url
7575

7676

7777
@tool
78-
def standalone(command): # $ mad-source__remote=command
78+
def standalone(command): # $ mad-source__ai=command
7979
return command
8080

8181

8282
@tool()
83-
def standalone_called(command): # $ mad-source__remote=command
83+
def standalone_called(command): # $ mad-source__ai=command
8484
return command
8585

8686

‎python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@
66

77

88
@server.tool()
9-
def run(command): # $ mad-source__remote=command
9+
def run(command): # $ mad-source__ai=command
1010
return command
1111

1212

1313
@other.resource("notes://{name}")
14-
def note(name): # $ mad-source__remote=name
14+
def note(name): # $ mad-source__ai=name
1515
return name
1616

1717

1818
@server.prompt()
19-
def summarize(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style
19+
def summarize(topic, style="short"): # $ mad-source__ai=topic mad-source__ai=style
2020
return topic + style
2121

2222

23-
def fetch(url): # $ mad-source__remote=url
23+
def fetch(url): # $ mad-source__ai=url
2424
return url
2525

2626

@@ -29,7 +29,7 @@ def fetch(url): # $ mad-source__remote=url
2929

3030
def register(app: FastMCP):
3131
@app.tool()
32-
def remove(path): # $ mad-source__remote=path
32+
def remove(path): # $ mad-source__ai=path
3333
return path
3434

3535

@@ -39,7 +39,7 @@ def __init__(self):
3939

4040
def register(self):
4141
@self.mcp.tool()
42-
def lookup(host): # $ mad-source__remote=host
42+
def lookup(host): # $ mad-source__ai=host
4343
return host
4444

4545

‎python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99

1010
@server.call_tool()
11-
async def call_tool(name, arguments): # $ mad-source__remote=name mad-source__remote=arguments
11+
async def call_tool(name, arguments): # $ mad-source__ai=name mad-source__ai=arguments
1212
return arguments
1313

1414

1515
@lowlevel_server.read_resource()
16-
async def read_resource(uri): # $ mad-source__remote=uri
16+
async def read_resource(uri): # $ mad-source__ai=uri
1717
return uri
1818

1919

2020
@module_server.get_prompt()
21-
async def get_prompt(name, arguments): # $ mad-source__remote=name mad-source__remote=arguments
21+
async def get_prompt(name, arguments): # $ mad-source__ai=name mad-source__ai=arguments
2222
return arguments

‎python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33

44
async def handle_call_tool(ctx, params):
5-
return params.arguments # $ mad-source__remote=params.arguments
5+
return params.arguments # $ mad-source__ai=params.arguments
66

77

88
async def handle_read_resource(ctx, params):
9-
return params.uri # $ mad-source__remote=params.uri
9+
return params.uri # $ mad-source__ai=params.uri
1010

1111

1212
async def handle_get_prompt(ctx, params):
13-
return params.arguments # $ mad-source__remote=params.arguments
13+
return params.arguments # $ mad-source__ai=params.arguments
1414

1515

1616
async def not_registered(ctx, params):

‎python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@
88

99

1010
@server.tool()
11-
def run(command): # $ mad-source__remote=command
11+
def run(command): # $ mad-source__ai=command
1212
return command
1313

1414

1515
@package_server.resource("notes://{name}")
16-
def note(name): # $ mad-source__remote=name
16+
def note(name): # $ mad-source__ai=name
1717
return name
1818

1919

2020
@module_server.prompt()
21-
def summarize(topic): # $ mad-source__remote=topic
21+
def summarize(topic): # $ mad-source__ai=topic
2222
return topic
2323

2424

25-
def fetch(url): # $ mad-source__remote=url
25+
def fetch(url): # $ mad-source__ai=url
2626
return url
2727

2828

0 commit comments

Comments
 (0)