Merge pull request #320 from DevamShah/fix-cors-credentials-and-icon-traversal

fix: wildcard CORS + credentials spec violation, path-traversal guard on /icons proxy
This commit is contained in:
Alexander Myasoedov
2026-07-31 19:27:20 +03:00
committed by GitHub
4 changed files with 229 additions and 3 deletions
+94
View File
@@ -0,0 +1,94 @@
"""Unit tests for CORS middleware configuration.
Verifies that the wildcard-origins + allow_credentials=True spec violation
(CORS spec §3.2, Fetch §4.7) has been removed. Browsers silently strip
credentials when the response carries Access-Control-Allow-Origin: * paired
with Access-Control-Allow-Credentials: true, so the old config was both
broken and misleading.
"""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.testclient import TestClient
from agentic_security.middleware.cors import setup_cors
def _get_cors_options(app: FastAPI) -> dict:
"""Extract CORS middleware options from the app's middleware stack."""
for middleware in app.user_middleware:
if middleware.cls is CORSMiddleware:
return middleware.kwargs
return {}
class TestCorsSetup:
"""CORS middleware is configured correctly."""
def test_cors_middleware_is_registered(self):
"""setup_cors adds CORSMiddleware to the app."""
app = FastAPI()
setup_cors(app)
cls_names = [m.cls.__name__ for m in app.user_middleware]
assert "CORSMiddleware" in cls_names
def test_wildcard_origins_without_credentials(self):
"""allow_origins=['*'] must not be paired with allow_credentials=True.
The combination is forbidden by the CORS spec and causes browsers to
silently drop credentials on every cross-origin request.
"""
app = FastAPI()
setup_cors(app)
opts = _get_cors_options(app)
allow_origins = opts.get("allow_origins", [])
allow_credentials = opts.get("allow_credentials", False)
if "*" in allow_origins or allow_origins == ["*"]:
assert not allow_credentials, (
"allow_origins=['*'] with allow_credentials=True is invalid per "
"the CORS spec — browsers reject it and credentials are silently dropped"
)
def test_cors_allows_cross_origin_requests(self):
"""Cross-origin preflight requests return a 200 with CORS headers."""
app = FastAPI()
@app.get("/probe")
async def probe():
return {"ok": True}
setup_cors(app)
client = TestClient(app, raise_server_exceptions=True)
response = client.options(
"/probe",
headers={
"Origin": "http://localhost:3000",
"Access-Control-Request-Method": "GET",
},
)
assert response.status_code == 200
assert "access-control-allow-origin" in response.headers
def test_cors_no_credentials_header_with_wildcard(self):
"""With wildcard origins, the response must not include
Access-Control-Allow-Credentials: true."""
app = FastAPI()
@app.get("/probe")
async def probe():
return {"ok": True}
setup_cors(app)
client = TestClient(app)
response = client.get("/probe", headers={"Origin": "http://evil.example.com"})
acao = response.headers.get("access-control-allow-origin", "")
acac = response.headers.get("access-control-allow-credentials", "false")
if acao == "*":
assert acac.lower() != "true", (
"Wildcard ACAO + ACAC:true is a spec violation (RFC 6454 §7.2, "
"Fetch §4.7) and silently breaks credentialed cross-origin requests"
)