From ebd7e1ce124f467f0633f5eefd2cf5f34d0fbe54 Mon Sep 17 00:00:00 2001 From: shootingallday Date: Mon, 25 May 2026 17:39:05 -0400 Subject: [PATCH 1/3] Guard MCP bounty search oversized numeric query --- app/main.py | 5 ++++- tests/test_api_mcp.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index 74ae49e..5dee1fe 100644 --- a/app/main.py +++ b/app/main.py @@ -1716,7 +1716,10 @@ def optional_clean_str_arg(field: str) -> str | None: def mcp_issue_number_search_value(query_text: str) -> int | None: if not query_text.isdigit(): return None - issue_number = int(query_text) + try: + issue_number = int(query_text) + except ValueError: + return None return issue_number if issue_number <= 2**63 - 1 else None def list_limit_arg(default: int = 25) -> int: diff --git a/tests/test_api_mcp.py b/tests/test_api_mcp.py index fd47c0b..fc4d923 100644 --- a/tests/test_api_mcp.py +++ b/tests/test_api_mcp.py @@ -551,6 +551,21 @@ def test_mcp_list_bounties_filters_status_query_and_limit(sqlite_url: str) -> No oversized_payload = json.loads(oversized_result["result"]["content"][0]["text"]) assert oversized_payload == [] + digit_limit_result = client.post( + "/mcp", + json={ + "jsonrpc": "2.0", + "id": 5, + "method": "tools/call", + "params": { + "name": "list_bounties", + "arguments": {"q": "9" * 5000}, + }, + }, + ).json() + digit_limit_payload = json.loads(digit_limit_result["result"]["content"][0]["text"]) + assert digit_limit_payload == [] + @pytest.mark.parametrize( ("arguments", "request_id"), From cab17adbe4aa9a44a4eb2842a741864ea1d74a26 Mon Sep 17 00:00:00 2001 From: shootingallday Date: Mon, 25 May 2026 17:48:42 -0400 Subject: [PATCH 2/3] Use shared SQLite integer limit in MCP search --- app/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index 5dee1fe..17f32d5 100644 --- a/app/main.py +++ b/app/main.py @@ -1720,7 +1720,7 @@ def mcp_issue_number_search_value(query_text: str) -> int | None: issue_number = int(query_text) except ValueError: return None - return issue_number if issue_number <= 2**63 - 1 else None + return issue_number if issue_number <= SQLITE_INTEGER_MAX else None def list_limit_arg(default: int = 25) -> int: if "limit" not in args or args.get("limit") is None: From 34de778efbcb4e03ace3782c03263a4081690660 Mon Sep 17 00:00:00 2001 From: shootingallday Date: Mon, 25 May 2026 18:00:53 -0400 Subject: [PATCH 3/3] Guard public bounty search oversized query --- app/main.py | 7 ++++--- tests/test_bounty_pages.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index 17f32d5..4e018bd 100644 --- a/app/main.py +++ b/app/main.py @@ -123,10 +123,11 @@ def _preserve_forwarded_https_redirect(request: Request, response: Response) -> def _issue_number_search_value(query: str) -> int | None: if not query.isdigit(): return None - issue_number = int(query) - if issue_number > SQLITE_INTEGER_MAX: + try: + issue_number = int(query) + except ValueError: return None - return issue_number + return issue_number if issue_number <= SQLITE_INTEGER_MAX else None def bounty_to_dict(bounty: Bounty) -> dict[str, Any]: diff --git a/tests/test_bounty_pages.py b/tests/test_bounty_pages.py index dc61a94..e2d3c90 100644 --- a/tests/test_bounty_pages.py +++ b/tests/test_bounty_pages.py @@ -123,6 +123,10 @@ def test_bounties_page_and_api_search_by_text_and_issue_number(sqlite_url: str) assert oversized_issue_search.status_code == 200 assert oversized_issue_search.json() == [] + digit_limit_issue_search = client.get("/api/v1/bounties", params={"q": "9" * 5000}) + assert digit_limit_issue_search.status_code == 200 + assert digit_limit_issue_search.json() == [] + oversized_issue_page = client.get("/bounties", params={"q": "9" * 40}) assert oversized_issue_page.status_code == 200 assert "No bounties yet." in oversized_issue_page.text