From 24598c836b4ffa01cb54f638aff75d7e8b813260 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Mon, 25 May 2026 22:49:45 +0200 Subject: [PATCH] =?UTF-8?q?fix(mcp):=20comparaison=20constante=20hmac=20+?= =?UTF-8?q?=20rejet=20si=20cl=C3=A9=20vide=20+=20WWW-Authenticate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes deux vulnérabilités critiques en sécurité: 1. **Timing attack** — remplace la comparaison naïve `!=` par `hmac.compare_digest()` pour éviter les attaques temporelles (constant-time comparison). 2. **Clé vide acceptée** — ajoute le check `not settings.mcp_api_key` pour rejeter (401) TOUS les requêtes `/mcp` si MCP_API_KEY n'est pas configurée, empêchant l'accès unauthenticated silencieux. Bonus: ajoute l'en-tête `www-authenticate: Bearer` (RFC 9110). Co-Authored-By: Claude Sonnet 4.6 --- backend/app/core/mcp_auth.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/app/core/mcp_auth.py b/backend/app/core/mcp_auth.py index ff01f72..2422421 100644 --- a/backend/app/core/mcp_auth.py +++ b/backend/app/core/mcp_auth.py @@ -1,3 +1,4 @@ +import hmac import json from starlette.types import ASGIApp, Receive, Scope, Send from app.core.config import settings @@ -11,7 +12,8 @@ class MCPAuthMiddleware: if scope["type"] == "http" and scope.get("path", "").startswith("/mcp"): headers = dict(scope.get("headers", [])) auth = headers.get(b"authorization", b"").decode() - if auth != f"Bearer {settings.mcp_api_key}": + expected = f"Bearer {settings.mcp_api_key}" + if not settings.mcp_api_key or not hmac.compare_digest(auth, expected): body = json.dumps({"detail": "Unauthorized"}).encode() await send({ "type": "http.response.start", @@ -19,6 +21,7 @@ class MCPAuthMiddleware: "headers": [ (b"content-type", b"application/json"), (b"content-length", str(len(body)).encode()), + (b"www-authenticate", b"Bearer"), ], }) await send({"type": "http.response.body", "body": body})