Don't allow cookies with Authorization: Bearer to bypass CSRF

Refs #2689
This commit is contained in:
Simon Willison 2026-04-14 19:23:21 -07:00
commit 028cc2446f
2 changed files with 18 additions and 1 deletions

View file

@ -68,7 +68,10 @@ class CrossOriginProtectionMiddleware:
# before evaluating Sec-Fetch-Site / Origin. Only "Bearer" is exempt;
# schemes like Basic or Digest can be browser-managed and ambient.
authorization = headers.get(b"authorization", b"").decode("latin-1")
if authorization:
cookie_header = headers.get(b"cookie")
# If the request also carries a Cookie header, ambient cookie auth
# could be in play, so do NOT treat it as exempt.
if authorization and not cookie_header:
scheme = authorization.split(None, 1)[0].lower()
if scheme == "bearer":
await self.app(scope, receive, send)

View file

@ -252,6 +252,20 @@ async def test_cross_site_post_without_auth_still_blocked(bare_ds):
assert response.status_code == 403
@pytest.mark.asyncio
async def test_bearer_with_cookie_does_not_bypass():
# Bearer + Cookie => ambient cookie auth is in play, not exempt.
scope = _http_scope(
{
"sec-fetch-site": "cross-site",
"host": "example.com",
"authorization": "Bearer dstok_abc",
"cookie": "ds_actor=anything",
}
)
assert await _run_middleware(scope) == ("blocked", 403)
def test_legacy_skip_csrf_hookimpl_does_not_break_loading():
# Plugins that still define skip_csrf must load cleanly - pluggy ignores
# unknown hook implementations - even though the hook is no longer