From d707ea91305444e740f1e59854d0f3eab02e5cb4 Mon Sep 17 00:00:00 2001 From: Szymon Bednorz Date: Wed, 6 May 2026 13:35:44 +0200 Subject: [PATCH 1/4] chore(deps): bump fastmcp to >=3.2.0,<4 and move black to dev extra Resolves multiple security advisories that are only patched in fastmcp 3.2.0: - GHSA-rww4-4w9c-7733 / CVE-2026-27124 (High): OAuth proxy confused-deputy - GHSA-vv7q-7jx5-f767 / CVE-2026-32871 (High): OpenAPI provider SSRF / path traversal - GHSA-5h2m-4q8j-pqpj / CVE-2025-69196: OAuth token reuse across MCP servers - GHSA-m8x7-r2rg-vh5g / CVE-2025-64340: Command injection via server name Also moves black from runtime dependencies to the new `dev` extra, so it is no longer pulled into production container images. Resolves CVE-2026-32274 (arbitrary file write via unsanitized cache filename in black 25.9.0) for downstream services that scan production images. All 284 existing tests pass against fastmcp 3.2.4. --- pyproject.toml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6483888..598f651 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,11 +28,10 @@ classifiers = [ dependencies = [ "typer>=0.15.4", "rich>=14.0.0", - "fastmcp>=2.14.0,<2.15.0", + "fastmcp>=3.2.0,<4.0.0", "pydantic>=2.11.0", "pydantic-settings>=2.0.0", "python-dotenv>=1.1.0", - "black>=24.10.0", "pyjwt>=2.0.0", "httpx>=0.28.1", "posthog>=4.1.0", @@ -47,6 +46,9 @@ dependencies = [ metrics = [ "prometheus-client>=0.22.1" ] +dev = [ + "black>=24.10.0" +] [project.scripts] golf = "golf.cli.main:app" @@ -85,13 +87,12 @@ classifiers = [ [tool.poetry.dependencies] python = ">=3.8" # Match requires-python -fastmcp = ">=2.14.0,<2.15.0" +fastmcp = ">=3.2.0,<4.0.0" typer = {extras = ["all"], version = ">=0.15.4"} pydantic = ">=2.11.0" pydantic-settings = ">=2.0.0" rich = ">=14.0.0" python-dotenv = ">=1.1.0" -black = ">=24.10.0" pyjwt = ">=2.0.0" httpx = ">=0.28.1" posthog = ">=4.1.0" @@ -111,6 +112,7 @@ pytest-asyncio = "^0.23.0" ruff = "^0.1.0" mypy = "^1.6.0" pytest-cov = "^4.1.0" +black = ">=24.10.0" [tool.black] line-length = 120 From fecb4ed9084be9cb60b16df4d4bfbdb5782aa05a Mon Sep 17 00:00:00 2001 From: Szymon Bednorz Date: Wed, 6 May 2026 13:54:40 +0200 Subject: [PATCH 2/4] fix(deps): pin black>=26.3.1 instead of moving to dev extra Initial commit moved black to a dev extra to resolve CVE-2026-32274 (arbitrary file write via unsanitized cache filename in black 25.9.0). That broke CI: black is imported at module import time in src/golf/core/builder.py and is genuinely used at runtime to format generated server.py output. Tests assert on black-formatted output (double-quoted strings, etc.) so removing it changes generated code behavior, not just dev tooling. CVE-2026-32274 is patched in black 26.3.1. Pinning black>=26.3.1 closes the finding while keeping black available at runtime so generated code formatting is unchanged. All 284 existing tests pass against fastmcp 3.2.4 and black 26.3.1. --- pyproject.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 598f651..1ffe34e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "pydantic>=2.11.0", "pydantic-settings>=2.0.0", "python-dotenv>=1.1.0", + "black>=26.3.1", "pyjwt>=2.0.0", "httpx>=0.28.1", "posthog>=4.1.0", @@ -46,9 +47,6 @@ dependencies = [ metrics = [ "prometheus-client>=0.22.1" ] -dev = [ - "black>=24.10.0" -] [project.scripts] golf = "golf.cli.main:app" @@ -93,6 +91,7 @@ pydantic = ">=2.11.0" pydantic-settings = ">=2.0.0" rich = ">=14.0.0" python-dotenv = ">=1.1.0" +black = ">=26.3.1" pyjwt = ">=2.0.0" httpx = ">=0.28.1" posthog = ">=4.1.0" @@ -112,7 +111,6 @@ pytest-asyncio = "^0.23.0" ruff = "^0.1.0" mypy = "^1.6.0" pytest-cov = "^4.1.0" -black = ">=24.10.0" [tool.black] line-length = 120 From 2b7c2e2000caad01650405e68ebd19e8358f6643 Mon Sep 17 00:00:00 2001 From: Szymon Bednorz Date: Wed, 6 May 2026 14:01:45 +0200 Subject: [PATCH 3/4] fix(deps): move black to [dev] extra so it ships out of production MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Black is build-time only — it formats generated server.py inside golf.core.builder during `golf build`. The generated server.py itself does not import golf.core.builder at runtime, so production installs have no need for black. Keeping black as a runtime dep was forcing the package into production container images and tripping security scanners on CVE-2026-32274 even after the version bump. Changes: - Move black from [project].dependencies and [tool.poetry].dependencies into the new [project.optional-dependencies].dev extra (and the existing [tool.poetry.group.dev.dependencies] block). - Lazy-import black inside builder.py format calls. Production import path no longer touches black. If `golf build` is run without black installed, a yellow warning is printed and generated code is written unformatted. - Update CI workflow to install `.[dev]` so test/lint jobs still get black + pytest + ruff + mypy. (Previously installed `.[telemetry]`, which is not a defined extra in this repo.) Result for downstream consumers: - `pip install golf-mcp` → no black, no CVE-2026-32274 finding. - `pip install golf-mcp[dev]` → black 26.3.1, full tooling for developers running `golf build` and the test suite. All 284 existing tests pass against fastmcp 3.2.4 + black 26.3.1 when installed with [dev]. Builder is importable without black. --- .github/workflows/test.yml | 6 ++---- pyproject.toml | 11 +++++++++-- src/golf/core/builder.py | 18 ++++++++++++++---- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d8d8ed..1355704 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,8 +28,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e ".[telemetry]" - pip install pytest pytest-asyncio pytest-cov + pip install -e ".[dev]" - name: Run tests run: | @@ -57,8 +56,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e ".[telemetry]" - pip install ruff mypy + pip install -e ".[dev]" - name: Run ruff format check run: ruff format --check src/ diff --git a/pyproject.toml b/pyproject.toml index 1ffe34e..5b275ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "pydantic>=2.11.0", "pydantic-settings>=2.0.0", "python-dotenv>=1.1.0", - "black>=26.3.1", "pyjwt>=2.0.0", "httpx>=0.28.1", "posthog>=4.1.0", @@ -47,6 +46,14 @@ dependencies = [ metrics = [ "prometheus-client>=0.22.1" ] +dev = [ + "black>=26.3.1", + "pytest>=7.4.0", + "pytest-asyncio>=0.23.0", + "pytest-cov>=4.1.0", + "ruff>=0.1.0", + "mypy>=1.6.0" +] [project.scripts] golf = "golf.cli.main:app" @@ -91,7 +98,6 @@ pydantic = ">=2.11.0" pydantic-settings = ">=2.0.0" rich = ">=14.0.0" python-dotenv = ">=1.1.0" -black = ">=26.3.1" pyjwt = ">=2.0.0" httpx = ">=0.28.1" posthog = ">=4.1.0" @@ -111,6 +117,7 @@ pytest-asyncio = "^0.23.0" ruff = "^0.1.0" mypy = "^1.6.0" pytest-cov = "^4.1.0" +black = ">=26.3.1" [tool.black] line-length = 120 diff --git a/src/golf/core/builder.py b/src/golf/core/builder.py index 7f960ba..eb14716 100644 --- a/src/golf/core/builder.py +++ b/src/golf/core/builder.py @@ -8,7 +8,6 @@ from pathlib import Path from typing import Any -import black from rich.console import Console from golf.auth import is_auth_configured @@ -1504,9 +1503,16 @@ def _generate_server(self) -> None: + main_code ) - # Format with black + # Format with black (build-time dep; install with `pip install golf-mcp[dev]`) try: + import black + code = black.format_str(code, mode=black.Mode()) + except ImportError: + console.print( + "[yellow]Warning: black is not installed; generated server.py will not be formatted. " + "Install with `pip install golf-mcp[dev]`.[/yellow]" + ) except Exception as e: console.print(f"[yellow]Warning: Could not format server.py: {e}[/yellow]") @@ -1908,14 +1914,18 @@ def build_project( server_code_content[:app_pos] + auth_routes_code + "\n\n" + server_code_content[app_pos:] ) - # Format with black before writing + # Format with black before writing (build-time dep) + final_code_to_write = modified_code try: + import black + final_code_to_write = black.format_str(modified_code, mode=black.Mode()) + except ImportError: + pass # already warned above when generating server.py except Exception as e: console.print( f"[yellow]Warning: Could not format server.py after auth routes injection: {e}[/yellow]" ) - final_code_to_write = modified_code with open(server_file, "w") as f: f.write(final_code_to_write) From 35286d262554792ac1ff3b151137c3d5d6b17916 Mon Sep 17 00:00:00 2001 From: Szymon Bednorz Date: Thu, 7 May 2026 12:32:20 +0200 Subject: [PATCH 4/4] fix(builder): pass stateless_http to mcp.run() instead of removed FastMCP() ctor kwarg fastmcp 3.x removed the stateless_http kwarg from FastMCP() (see fastmcp/server/server.py: _REMOVED_KWARGS). The kwarg now lives on run_http_async() / http_app(). The builder still emitted mcp = FastMCP("name", auth=auth_provider, stateless_http=True) into generated dist/server.py, so any server with stateless_http: true in golf.json crashes on import: TypeError: FastMCP() no longer accepts `stateless_http`. Pass `stateless_http` to `run_http_async()` or `http_app()`, or set FASTMCP_STATELESS_HTTP. Drop the ctor emission. Append `, stateless_http=True` to the streamable-http mcp.run() call sites instead. stdio and sse transports are unaffected. --- src/golf/core/builder.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/golf/core/builder.py b/src/golf/core/builder.py index eb14716..c114916 100644 --- a/src/golf/core/builder.py +++ b/src/golf/core/builder.py @@ -1278,10 +1278,6 @@ def _generate_server(self) -> None: for key, value in auth_components["fastmcp_args"].items(): mcp_constructor_args.append(f"{key}={value}") - # Add stateless HTTP parameter if enabled - if self.settings.stateless_http: - mcp_constructor_args.append("stateless_http=True") - # Add OpenTelemetry parameters if enabled if self.settings.opentelemetry_enabled: mcp_constructor_args.append("lifespan=telemetry_lifespan") @@ -1322,6 +1318,7 @@ def _generate_server(self) -> None: f' transport_to_run = "{self.settings.transport}"', "", ] + stateless_kwarg = ", stateless_http=True" if self.settings.stateless_http else "" main_code.append("") @@ -1433,8 +1430,8 @@ def _generate_server(self) -> None: main_code.extend( [ " # Run HTTP server with middleware using FastMCP's run method", - ' mcp.run(transport="streamable-http", host=host, ' - 'port=port, log_level="info", middleware=middleware, show_banner=False)', + f' mcp.run(transport="streamable-http", host=host, ' + f'port=port, log_level="info", middleware=middleware, show_banner=False{stateless_kwarg})', ] ) else: @@ -1443,7 +1440,7 @@ def _generate_server(self) -> None: " # Run HTTP server with middleware using FastMCP's run method", f' mcp.run(transport="streamable-http", host=host, ' f'port=port, path="{endpoint_path}", log_level="info", ' - f"middleware=middleware, show_banner=False)", + f"middleware=middleware, show_banner=False{stateless_kwarg})", ] ) else: @@ -1451,8 +1448,8 @@ def _generate_server(self) -> None: main_code.extend( [ " # Run HTTP server using FastMCP's run method", - ' mcp.run(transport="streamable-http", host=host, ' - 'port=port, log_level="info", show_banner=False)', + f' mcp.run(transport="streamable-http", host=host, ' + f'port=port, log_level="info", show_banner=False{stateless_kwarg})', ] ) else: @@ -1461,7 +1458,7 @@ def _generate_server(self) -> None: " # Run HTTP server using FastMCP's run method", f' mcp.run(transport="streamable-http", host=host, ' f'port=port, path="{endpoint_path}", log_level="info", ' - f"show_banner=False)", + f"show_banner=False{stateless_kwarg})", ] ) else: