From b555a2e19d4cdb9aefea6974cb27461b8b79d505 Mon Sep 17 00:00:00 2001 From: Stefan Lohmaier Date: Thu, 18 Jun 2026 10:09:23 +0200 Subject: [PATCH] security: redirect_uri-Allowlist + mail Path-Traversal-Schutz - common.py /authorize: redirect_uri muss https://claude.ai/ oder https://claude.com/ sein (verhindert offenen Redirect / Code-Abfluss an fremde URIs). - mail/server.py _open_folder: realpath-Check, Ordner muss im Account-Verzeichnis bleiben (Path-Traversal verhindert). - tests: get_token_pkce auf sicheren Flow aktualisiert (erlaubte redirect_uri + client_secret). 54/54 Tests gruen. test-Client bleibt (Test-Suite braucht ihn, Secret 600-geschuetzt). Co-Authored-By: Claude Opus 4.8 (1M context) --- common.py | 3 +++ mail/server.py | 6 +++++- tests/test_all.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/common.py b/common.py index 14bc735..ec6adf3 100644 --- a/common.py +++ b/common.py @@ -132,6 +132,9 @@ async def oauth_authorize(request: Request): if not code_challenge: return HTMLResponse("

Fehler

PKCE erforderlich (code_challenge fehlt).

", status_code=400) + if not redirect_uri.startswith(("https://claude.ai/", "https://claude.com/")): + return HTMLResponse("

Fehler

Ungueltige redirect_uri.

", status_code=400) + code = secrets.token_urlsafe(32) _auth_codes[code] = { "client_id": client_id, diff --git a/mail/server.py b/mail/server.py index 0932870..661caf5 100644 --- a/mail/server.py +++ b/mail/server.py @@ -161,7 +161,11 @@ def _discover_folders(acct_path): def _open_folder(acct_path, folder_name): - path = os.path.join(acct_path, folder_name) + base = os.path.realpath(acct_path) + path = os.path.realpath(os.path.join(base, folder_name)) + # Path-Traversal verhindern: muss innerhalb des Account-Verzeichnisses bleiben + if path != base and not path.startswith(base + os.sep): + return None return mailbox.Maildir(path, create=False) if os.path.isdir(path) else None diff --git a/tests/test_all.py b/tests/test_all.py index fc06d9e..f625950 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -48,12 +48,12 @@ def get_token_pkce(port): challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode() r = httpx.get(f"http://127.0.0.1:{port}/authorize", params={ "response_type": "code", "client_id": "test", - "redirect_uri": "http://localhost/cb", "code_challenge": challenge, "code_challenge_method": "S256", + "redirect_uri": "https://claude.ai/api/mcp/auth_callback", "code_challenge": challenge, "code_challenge_method": "S256", }, follow_redirects=False, timeout=10) assert r.status_code == 302 code = r.headers["location"].split("code=")[1].split("&")[0] r2 = httpx.post(f"http://127.0.0.1:{port}/token", data={ - "grant_type": "authorization_code", "code": code, "client_id": "test", "code_verifier": verifier, + "grant_type": "authorization_code", "code": code, "client_id": "test", "client_secret": TEST_SECRET, "code_verifier": verifier, }, timeout=10) assert r2.status_code == 200 return r2.json()["access_token"]