Mostly safe โ a couple of notes worth reading.
Scanned 5/12/2026, 7:16:00 PMยทCached resultยทDeep Scanยท91 rulesยทHow we decide โ
AIVSS Score
Low
Severity Breakdown
0
critical
0
high
32
medium
1
low
MCP Server Information
Findings
This package scores 71/100 with a B grade and carries 32 medium-severity findings, all related to server configuration readiness issues. While there are no critical or high-severity vulnerabilities, the configuration gaps suggest the server may not be fully hardened for production use and should be reviewed before deployment. The single low-severity readiness issue indicates minor gaps that are unlikely to cause immediate problems but should be addressed as part of standard security practices.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 โ 33 of 33 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
Showing 1โ30 of 33 findings
33 findings
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 68 | print(" - /include-tags-mcp: Only operations with the 'items' tag") |
| 69 | print(" - /exclude-tags-mcp: All operations except those with the 'search' tag") |
| 70 | print(" - /combined-include-mcp: Operations with 'search' tag or delete_item operation") |
| 71 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/03_custom_exposed_endpoints_example.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 26 | if __name__ == "__main__": |
| 27 | import uvicorn |
| 28 | |
| 29 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/05_reregister_tools_example.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 134 | if __name__ == "__main__": |
| 135 | import uvicorn |
| 136 | |
| 137 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/09_auth_example_auth0.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 31 | if __name__ == "__main__": |
| 32 | import uvicorn |
| 33 | |
| 34 | uvicorn.run(mcp_app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/04_separate_server_example.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 43 | if __name__ == "__main__": |
| 44 | import uvicorn |
| 45 | uvicorn.run(app, host="0.0.0.0", port=8000) |
| 46 | ``` |
| 47 | and run the server using `python fastapi_mcp_server.py`, which will serve you the MCP at `http://localhost:8000/mcp`. |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in docs/getting-started/quickstart.mdx. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 23 | if __name__ == "__main__": |
| 24 | import uvicorn |
| 25 | |
| 26 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/06_custom_mcp_router_example.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 58 | if __name__ == "__main__": |
| 59 | import uvicorn |
| 60 | |
| 61 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/08_auth_example_token_passthrough.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 23 | if __name__ == "__main__": |
| 24 | import uvicorn |
| 25 | |
| 26 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/02_full_schema_description_example.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 15 | if __name__ == "__main__": |
| 16 | import uvicorn |
| 17 | |
| 18 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/01_basic_usage_example.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 20 | if __name__ == "__main__": |
| 21 | import uvicorn |
| 22 | |
| 23 | uvicorn.run(app, host="0.0.0.0", port=8000) |
RemediationAI
The problem is that uvicorn.run() binds to 0.0.0.0, exposing the MCP server on all network interfaces and allowing any remote host to connect. Change the host parameter from "0.0.0.0" to "127.0.0.1" in the uvicorn.run() call in examples/07_configure_http_timeout_example.py. This restricts the server to localhost only, ensuring only the parent process on the same machine can communicate with it. Verify the fix by running the server and confirming netstat or ss shows the service listening only on 127.0.0.1:8000, not 0.0.0.0:8000.
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 21 | class HTTPRequestInfo(BaseType): |
| 22 | method: str |
| 23 | path: str |
| 24 | headers: Dict[str, str] |
| 25 | cookies: Dict[str, str] |
| 26 | query_params: Dict[str, str] |
RemediationAI
The problem is that the HTTPRequestInfo class in fastapi_mcp/types.py exposes unconstrained string fields (path, method, query_params) that could be exploited to pass arbitrary values to downstream operations. Add Pydantic Field constraints to each risky field: use `Field(max_length=2048, pattern='^[A-Z]+$')` for method, `Field(max_length=4096, pattern='^/[a-zA-Z0-9/_-]*$')` for path, and similar constraints for query_params keys and values. These constraints narrow the input space and prevent injection attacks. Verify the fix by attempting to instantiate HTTPRequestInfo with oversized or malformed values and confirming validation errors are raised.
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 502 | raise Exception(f"Unknown tool: {tool_name}") |
| 503 | |
| 504 | operation = operation_map[tool_name] |
| 505 | path: str = operation["path"] |
| 506 | method: str = operation["method"] |
| 507 | parameters: List[Dict[str, Any]] = operation.get("parameters", []) |
| 508 | arguments = arguments.copy() if arguments else {} # Deep copy arguments to avoid mutating the original |
RemediationAI
The problem is that the tool invocation logic in fastapi_mcp/server.py accepts unconstrained tool_name and path parameters from the operation_map without validating their format or length, allowing arbitrary strings to be passed to downstream HTTP calls. Add validation using Pydantic Field constraints or explicit regex checks on tool_name and path before use: e.g., `assert re.match(r'^[a-zA-Z0-9_-]+$', tool_name)` and `assert len(path) <= 4096`. This prevents injection of malicious paths or tool names. Verify the fix by attempting to call tools with oversized or special-character names and confirming validation errors are raised.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | <p align="center"><a href="https://github.com/tadata-org/fastapi_mcp"><img src="https://github.com/user-attachments/assets/609d5b8b-37a1-42c4-87e2-f045b60026b1" alt="fastapi-to-mcp" height="100"/></a></p> |
| 2 | <h1 align="center">FastAPI-MCP</h1> |
| 3 | <p align="center">ไธไธช้ถ้
็ฝฎๅทฅๅ
ท๏ผ็จไบ่ชๅจๅฐ FastAPI ็ซฏ็นๅ
ฌๅผไธบๆจกๅไธไธๆๅ่ฎฎ๏ผMCP๏ผๅทฅๅ
ทใ</p> |
| 4 | <div align="center"> |
| 5 | |
| 6 | [](https://pypi.org/project/fastapi-mcp/) |
| 7 | [ specifying the authentication method used, such as `"auth": {"type": "bearer"}` or `"auth": {"type": "none"}` if relying on network-layer auth. This allows reviewers to audit the security posture. Verify the fix by reviewing the manifest and confirming the auth field is present and matches the actual authentication implementation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | --- |
| 2 | title: Quickstart |
| 3 | icon: rocket |
| 4 | --- |
| 5 | |
| 6 | This guide will help you quickly run your first MCP server using FastAPI-MCP. |
| 7 | |
| 8 | If you haven't already installed FastAPI-MCP, follow the [installation instructions](/getting-started/installation). |
| 9 | |
| 10 | ## Creating a basic MCP server |
| 11 | |
| 12 | To create a basic MCP server, import or create a FastAPI app, wrap it with the `FastApiMCP` class and mount the MCP to your existing application: |
| 13 | |
| 14 | ```python {2, 8, 11} |
| 15 | from fastapi import FastAPI |
| 16 | from fastapi_mcp import FastApiMCP |
| 17 |
RemediationAI
The problem is that the MCP manifest in docs/getting-started/quickstart.mdx declares tools but does not explicitly document any authentication mechanism, making it unclear whether the server is protected. Add an explicit `auth` field to the manifest (or documentation) specifying the authentication method used, such as `"auth": {"type": "bearer"}` or `"auth": {"type": "none"}` if relying on network-layer auth. This allows reviewers to audit the security posture. Verify the fix by reviewing the manifest and confirming the auth field is present and matches the actual authentication implementation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | --- |
| 2 | title: Authentication & Authorization |
| 3 | icon: key |
| 4 | --- |
| 5 | |
| 6 | FastAPI-MCP supports authentication and authorization using your existing FastAPI dependencies. |
| 7 | |
| 8 | It also supports the full OAuth 2 flow, compliant with [MCP Spec 2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization). |
| 9 | |
| 10 | It's worth noting that most MCP clients currently do not support the latest MCP spec, so for our examples we might use a bridge client such as `npx mcp-remote`. We recommend you use it as w |
RemediationAI
The problem is that the MCP manifest in docs/advanced/auth.mdx declares tools but does not explicitly document any authentication mechanism in the manifest itself, making it unclear whether the server is protected. Add an explicit `auth` field to the manifest (or documentation) specifying the authentication method used, such as `"auth": {"type": "oauth2"}` or `"auth": {"type": "bearer"}`. This allows reviewers to audit the security posture. Verify the fix by reviewing the manifest and confirming the auth field is present and matches the actual authentication implementation described in the documentation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | """ |
| 2 | This example shows how to reject any request without a valid token passed in the Authorization header. |
| 3 | |
| 4 | In order to configure the auth header, the config file for the MCP server should looks like this: |
| 5 | ```json |
| 6 | { |
| 7 | "mcpServers": { |
| 8 | "remote-example": { |
| 9 | "command": "npx", |
| 10 | "args": [ |
| 11 | "mcp-remote", |
| 12 | "http://localhost:8000/mcp", |
| 13 | "--header", |
| 14 | "Authorization:${AUTH_HEADER}" |
| 15 | ] |
| 16 | }, |
| 17 | "env": { |
| 18 | "AUTH_HEADER": "Bearer <your-token>" |
| 19 | } |
| 20 | } |
| 21 | } |
| 22 | ` |
RemediationAI
The problem is that the MCP manifest in examples/08_auth_example_token_passthrough.py declares tools but does not explicitly document any authentication mechanism in the manifest itself, making it unclear whether the server is protected. Add an explicit `auth` field to the manifest (or documentation) specifying the authentication method used, such as `"auth": {"type": "bearer"}`. This allows reviewers to audit the security posture. Verify the fix by reviewing the manifest and confirming the auth field is present and matches the actual authentication implementation (token passthrough).
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | --- |
| 2 | title: MCP Transport |
| 3 | description: Understanding MCP transport methods and how to choose between them |
| 4 | icon: car |
| 5 | --- |
| 6 | |
| 7 | FastAPI-MCP supports two MCP transport methods for client-server communication: **HTTP transport** (recommended) and **SSE transport** (backwards compatibility). |
| 8 | |
| 9 | ## HTTP Transport (Recommended) |
| 10 | |
| 11 | HTTP transport is the **recommended** transport method as it implements the latest MCP Streamable HTTP specification. It provides better session management, more robust connection hand |
RemediationAI
The problem is that the MCP manifest in docs/advanced/transport.mdx declares tools but does not explicitly document any authentication mechanism in the manifest itself, making it unclear whether the server is protected. Add an explicit `auth` field to the manifest (or documentation) specifying the authentication method used, such as `"auth": {"type": "bearer"}` or `"auth": {"type": "none"}`. This allows reviewers to audit the security posture. Verify the fix by reviewing the manifest and confirming the auth field is present and matches the actual authentication implementation.
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | from typing_extensions import Annotated, Doc |
| 2 | from fastapi import FastAPI, HTTPException, Request, status |
| 3 | from fastapi.responses import RedirectResponse |
| 4 | import httpx |
| 5 | from typing import Optional |
| 6 | import logging |
| 7 | from urllib.parse import urlencode |
| 8 | |
| 9 | from fastapi_mcp.types import ( |
| 10 | ClientRegistrationRequest, |
| 11 | ClientRegistrationResponse, |
| 12 | AuthConfig, |
| 13 | OAuthMetadata, |
| 14 | OAuthMetadataDict, |
| 15 | StrHttpUrl, |
| 16 | ) |
| 17 | |
| 18 | |
| 19 | logger = logging.getLogger(__name__) |
| 20 | |
| 21 | |
| 22 | def setup_oauth_custom_metadata( |
| 23 | app: An |
RemediationAI
The problem is that fastapi_mcp/auth/proxy.py registers state-changing routes (POST/PUT/PATCH/DELETE) without CSRF protection middleware, allowing cross-site requests to exploit cookie-based session authentication. Add the `fastapi-csrf-protect` middleware to the FastAPI app by installing it (`pip install fastapi-csrf-protect`) and applying it: `from fastapi_csrf_protect import CsrfProtect; CsrfProtect(app)`. This ensures CSRF tokens are validated on all state-changing requests. Verify the fix by attempting a cross-site POST request without a valid CSRF token and confirming it is rejected with a 403 error.
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | import json |
| 2 | import httpx |
| 3 | from typing import Dict, Optional, Any, List, Union, Literal, Sequence |
| 4 | from typing_extensions import Annotated, Doc |
| 5 | |
| 6 | from fastapi import FastAPI, Request, APIRouter, params |
| 7 | from fastapi.openapi.utils import get_openapi |
| 8 | from mcp.server.lowlevel.server import Server |
| 9 | import mcp.types as types |
| 10 | |
| 11 | from fastapi_mcp.openapi.convert import convert_openapi_to_mcp_tools |
| 12 | from fastapi_mcp.transport.sse import FastApiSseTransport |
| 13 | from fastapi_mcp.transport.http import FastApiHttpSessio |
RemediationAI
The problem is that fastapi_mcp/server.py registers state-changing routes (POST/PUT/PATCH/DELETE) without CSRF protection middleware, allowing cross-site requests to exploit cookie-based session authentication. Add the `fastapi-csrf-protect` middleware to the FastAPI app by installing it (`pip install fastapi-csrf-protect`) and applying it: `from fastapi_csrf_protect import CsrfProtect; CsrfProtect(app)`. This ensures CSRF tokens are validated on all state-changing requests. Verify the fix by attempting a cross-site POST request without a valid CSRF token and confirming it is rejected with a 403 error.
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | """ |
| 2 | Simple example of using FastAPI-MCP to add an MCP server to a FastAPI app. |
| 3 | """ |
| 4 | |
| 5 | from fastapi import FastAPI, HTTPException, Query |
| 6 | from pydantic import BaseModel |
| 7 | from typing import List, Optional |
| 8 | |
| 9 | |
| 10 | app = FastAPI() |
| 11 | |
| 12 | |
| 13 | class Item(BaseModel): |
| 14 | id: int |
| 15 | name: str |
| 16 | description: Optional[str] = None |
| 17 | price: float |
| 18 | tags: List[str] = [] |
| 19 | |
| 20 | |
| 21 | items_db: dict[int, Item] = {} |
| 22 | |
| 23 | |
| 24 | @app.get("/items/", response_model=List[Item], tags=["items"], operation_id="list_items") |
| 25 | async def list_items(skip: |
RemediationAI
The problem is that examples/shared/apps/items.py registers state-changing routes (POST/PUT/PATCH/DELETE) without CSRF protection middleware, allowing cross-site requests to exploit cookie-based session authentication. Add the `fastapi-csrf-protect` middleware to the FastAPI app by installing it (`pip install fastapi-csrf-protect`) and applying it: `from fastapi_csrf_protect import CsrfProtect; CsrfProtect(app)`. This ensures CSRF tokens are validated on all state-changing requests. Verify the fix by attempting a cross-site POST request without a valid CSRF token and confirming it is rejected with a 403 error.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 19 | enable-cache: true |
| 20 | |
| 21 | - name: Set up Python |
| 22 | uses: actions/setup-python@v5 |
| 23 | with: |
| 24 | python-version-file: ".python-version" |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/release.yml uses `actions/setup-python@v5` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/setup-python@v5` with `uses: actions/setup-python@f643de2b0fde472100aafb19cbc5e7563523da5f # v5.0.0`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 13 | fetch-depth: 0 |
| 14 | |
| 15 | - name: Install uv |
| 16 | uses: astral-sh/setup-uv@v5 |
| 17 | with: |
| 18 | version: "0.6.12" |
| 19 | enable-cache: true |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/release.yml uses `astral-sh/setup-uv@v5` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: astral-sh/setup-uv@v5` with `uses: astral-sh/setup-uv@d46e38e99c373cacee601c5ebc7d838891d9b6b9 # v5`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 8 | deploy: |
| 9 | runs-on: ubuntu-latest |
| 10 | steps: |
| 11 | - uses: actions/checkout@v4 |
| 12 | with: |
| 13 | fetch-depth: 0 |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/release.yml uses `actions/checkout@v4` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/checkout@v4` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 11 | name: Ruff |
| 12 | runs-on: ubuntu-latest |
| 13 | steps: |
| 14 | - uses: actions/checkout@v4 |
| 15 | |
| 16 | - name: Install uv |
| 17 | uses: astral-sh/setup-uv@v5 |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `actions/checkout@v4` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/checkout@v4` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 64 | python-version: ["3.10", "3.11", "3.12"] |
| 65 | |
| 66 | steps: |
| 67 | - uses: actions/checkout@v4 |
| 68 | |
| 69 | - name: Install uv |
| 70 | uses: astral-sh/setup-uv@v5 |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `astral-sh/setup-uv@v5` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: astral-sh/setup-uv@v5` with `uses: astral-sh/setup-uv@d46e38e99c373cacee601c5ebc7d838891d9b6b9 # v5`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 38 | - uses: actions/checkout@v4 |
| 39 | |
| 40 | - name: Install uv |
| 41 | uses: astral-sh/setup-uv@v5 |
| 42 | with: |
| 43 | version: "0.6.12" |
| 44 | enable-cache: true |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `actions/checkout@v4` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/checkout@v4` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 81 | run: uv run pytest --cov=fastapi_mcp --cov-report=xml |
| 82 | |
| 83 | - name: Upload coverage to Codecov |
| 84 | uses: codecov/codecov-action@v5 |
| 85 | with: |
| 86 | token: ${{ secrets.CODECOV_TOKEN }} |
| 87 | fail_ci_if_error: false |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `codecov/codecov-action@v5` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: codecov/codecov-action@v5` with `uses: codecov/codecov-action@5ecb98a3c6b747a0d5ae3e244e67bebda0aebf23 # v5.0.0`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 14 | - uses: actions/checkout@v4 |
| 15 | |
| 16 | - name: Install uv |
| 17 | uses: astral-sh/setup-uv@v5 |
| 18 | with: |
| 19 | version: "0.6.12" |
| 20 | enable-cache: true |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `actions/checkout@v4` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/checkout@v4` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 21 | cache-dependency-glob: "uv.lock" |
| 22 | |
| 23 | - name: Set up Python |
| 24 | uses: actions/setup-python@v5 |
| 25 | with: |
| 26 | python-version-file: ".python-version" |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `actions/setup-python@v5` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/setup-python@v5` with `uses: actions/setup-python@f643de2b0fde472100aafb19cbc5e7563523da5f # v5.0.0`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 67 | - uses: actions/checkout@v4 |
| 68 | |
| 69 | - name: Install uv |
| 70 | uses: astral-sh/setup-uv@v5 |
| 71 | with: |
| 72 | version: "0.6.12" |
| 73 | python-version: ${{ matrix.python-version }} |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `actions/checkout@v4` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/checkout@v4` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 35 | name: MyPy |
| 36 | runs-on: ubuntu-latest |
| 37 | steps: |
| 38 | - uses: actions/checkout@v4 |
| 39 | |
| 40 | - name: Install uv |
| 41 | uses: astral-sh/setup-uv@v5 |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `actions/checkout@v4` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/checkout@v4` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 45 | cache-dependency-glob: "uv.lock" |
| 46 | |
| 47 | - name: Set up Python |
| 48 | uses: actions/setup-python@v5 |
| 49 | with: |
| 50 | python-version-file: ".python-version" |
RemediationAI
The problem is that the GitHub Actions workflow in .github/workflows/ci.yml uses `actions/setup-python@v5` which is a mutable tag that can be silently updated to a malicious version. Pin the action to a specific commit SHA: replace `uses: actions/setup-python@v5` with `uses: actions/setup-python@f643de2b0fde472100aafb19cbc5e7563523da5f # v5.0.0`. This ensures the exact version is used and prevents supply-chain attacks. Verify the fix by checking the workflow file and confirming all `uses:` statements reference 40-character commit SHAs.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 130 | if self._manager_task and not self._manager_task.done(): |
| 131 | self._manager_task.cancel() |
| 132 | try: |
| 133 | await self._manager_task |
| 134 | except asyncio.CancelledError: |
| 135 | pass |
| 136 | self._manager_started = False |
RemediationAI
The problem is that the except clause in fastapi_mcp/transport/http.py silently swallows the asyncio.CancelledError exception with `pass`, preventing any logging or monitoring of the cancellation event. Replace the silent `pass` with explicit logging: `except asyncio.CancelledError: logger.debug('Manager task cancelled'); pass` or use a more specific handler. This ensures the exception is recorded for debugging and incident response. Verify the fix by running the code with logging enabled and confirming that task cancellation events are logged to the application logs.
Hammer