From 7ec31195466ad4219a8305edabdc70361c17c859 Mon Sep 17 00:00:00 2001 From: devthedevil <41816786+devthedevil@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:21:24 -0500 Subject: [PATCH] fix: don't allow credentials for the wildcard CORS fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app.add_middleware() was configured with allow_credentials=True regardless of which origins were resolved, including the "*" fallback used when ANTHROPIC_API_KEY isn't set at import time (e.g. a misconfigured deployment). Wildcard origin + credentials is an unsafe combination: browsers refuse a literal "*" on a credentialed response, so CORS middlewares (including Starlette's) work around that by reflecting back whatever Origin header the request sent — meaning any site could get its origin accepted and make credentialed requests, defeating CORS entirely for that fallback path. Extracts the credentials decision into _allow_credentials_for(), which only returns True for an explicit, non-wildcard origin list, and adds direct unit tests for both branches. No behavior change for the documented/normal setup (CORS_ORIGINS set to real origins). --- agentstore/server.py | 20 ++++++++++++++++++-- tests/test_server.py | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/agentstore/server.py b/agentstore/server.py index 7228a3d..5673715 100644 --- a/agentstore/server.py +++ b/agentstore/server.py @@ -38,10 +38,26 @@ def _cors_origins() -> list[str]: return ["*"] +def _allow_credentials_for(origins: list[str]) -> bool: + """Only allow credentialed cross-origin requests for an explicit origin list. + + Wildcard origin + allow_credentials=True is an unsafe combination: browsers + refuse a literal "*" on the response to a credentialed request, so CORS + middlewares (including Starlette's) work around that by reflecting back + whatever Origin header the request sent. That means any site can have its + origin accepted and make credentialed requests, which defeats CORS + entirely. Only enable credentials once real, explicit origins are + configured — never for the wildcard fallback above. + """ + return "*" not in origins + + +_ALLOWED_ORIGINS = _cors_origins() + app.add_middleware( CORSMiddleware, - allow_origins=_cors_origins(), - allow_credentials=True, + allow_origins=_ALLOWED_ORIGINS, + allow_credentials=_allow_credentials_for(_ALLOWED_ORIGINS), allow_methods=["*"], allow_headers=["*"], ) diff --git a/tests/test_server.py b/tests/test_server.py index 629ec6f..3e8755d 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -10,7 +10,7 @@ from fastapi.testclient import TestClient from langchain_core.messages import AIMessage -from agentstore.server import app +from agentstore.server import _allow_credentials_for, app def test_health_returns_ok(): @@ -43,3 +43,21 @@ def test_chat_rejects_empty_query(): with TestClient(app) as client: r = client.post("/api/chat", json={"query": ""}) assert r.status_code == 422 + + +# ── CORS ───────────────────────────────────────────────────────────────────── +# +# Wildcard origin + allow_credentials=True is an unsafe combination: browsers +# refuse a literal "*" on a credentialed response, so CORS middlewares +# (including Starlette's) reflect back whatever Origin header the request +# sent instead — meaning any site could get its origin accepted and make +# credentialed requests, defeating CORS entirely. Credentials must only be +# enabled once explicit, non-wildcard origins are configured. + + +def test_wildcard_origin_disallows_credentials(): + assert _allow_credentials_for(["*"]) is False + + +def test_explicit_origins_allow_credentials(): + assert _allow_credentials_for(["https://example.com", "http://localhost:5173"]) is True