Skip to content

fix: correct CORS allowed-methods/headers defaults#34

Merged
lesnik512 merged 1 commit into
mainfrom
fix/cors-default-methods
Jun 26, 2026
Merged

fix: correct CORS allowed-methods/headers defaults#34
lesnik512 merged 1 commit into
mainfrom
fix/cors-default-methods

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Bug

settings.py defaulted the CORS lists to a single empty string:

cors_allowed_methods: list[str] = [""]
cors_allowed_headers: list[str] = [""]

[""] is neither "allow all" (["*"]) nor empty ([]). It flows through lite-bootstrap into Litestar's CORSConfig, which on preflight produces:

Access-Control-Allow-Methods: ''                                          ← empty
Access-Control-Allow-Headers: ', Accept, Accept-Language, Content-Language, Content-Type'   ← stray leading comma

is_allow_all_methods is "*" in [""]False, but the list is non-empty, so Litestar emits ", ".join({""}) → the empty string.

Impact

A browser SPA at the configured dev origin http://localhost:5173 doing any preflighted request — every POST/PUT write endpoint with Content-Type: application/json — receives an empty Access-Control-Allow-Methods and the browser blocks the request. The template's default CORS was broken for the exact origin it ships with. (allowed_headers was functionally OK — Content-Type is covered by Litestar's defaults — but emitted a stray empty token.)

Fix

cors_allowed_methods: list[str] = ["*"]   # → DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT
cors_allowed_headers: list[str] = ["*"]   # → allow-all, no stray token

Permissive dev defaults; tighten via env in prod.

Test (TDD — failing first)

tests/test_cors.py sends a preflight OPTIONS /api/decks/ (Origin: localhost:5173, Access-Control-Request-Method: POST) through the client fixture and asserts POST/PUT appear in Access-Control-Allow-Methods — exercising the real settings → bootstrap → middleware wiring.

  • Before (RED): allow-methods is ''POST absent → fails.
  • After (GREEN): full method set → passes.

22 passed, 100% coverage.

🤖 Generated with Claude Code

cors_allowed_methods and cors_allowed_headers defaulted to [""], a list
holding one empty string. That is not "allow all" (which needs "*") nor
empty — it made litestar's CORSConfig emit an empty
Access-Control-Allow-Methods on preflight, so a browser at the
configured dev origin (localhost:5173) had every preflighted write
(POST/PUT with JSON) blocked. allowed-headers likewise emitted a stray
leading-comma token.

Default both to ["*"] (permissive dev defaults; tighten via env in
prod). Add a preflight integration test asserting POST/PUT appear in
Access-Control-Allow-Methods through the real settings -> bootstrap ->
middleware path; it fails on the old [""] default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit 14f7e3e into main Jun 26, 2026
2 checks passed
@lesnik512 lesnik512 deleted the fix/cors-default-methods branch June 26, 2026 18:13
lesnik512 added a commit to modern-python/fastapi-sqlalchemy-template that referenced this pull request Jun 26, 2026
The CORS defaults already ship correct (`["*"]`), but nothing guarded
the preflight contract. Add a regression test that sends an
`OPTIONS /api/decks/` preflight from the shipped dev origin
(`http://localhost:5173`) and asserts `POST`/`PUT` appear in
`Access-Control-Allow-Methods`, exercising the real
settings → lite-bootstrap → Starlette CORS middleware wiring.

Ported from modern-python/litestar-sqlalchemy-template#34. Verified it
fails (preflight 400, POST absent) if the methods default regresses to
`[""]`.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant