Strip trailing slashes from PUBLIC_URL, CORS_ORIGINS, FRONTEND_BASE_URL

A trailing slash on CORS_ORIGINS never matches a browser's Origin header
(which never includes a path), silently breaking CORS with no error
message beyond the browser's generic "No Access-Control-Allow-Origin
header" — exactly what happened in production after switching domains.
A trailing slash on PUBLIC_URL/FRONTEND_BASE_URL similarly produced
double-slash URLs in API responses and reset-password links. All three
are now normalized at config-load time so a .env typo can't cause this
again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
MOHAN 2026-07-05 02:54:15 +05:30
parent 23a14a9710
commit 7384a6c05a

View File

@ -1,4 +1,5 @@
from __future__ import annotations from __future__ import annotations
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
@ -45,9 +46,20 @@ class Settings(BaseSettings):
admin_password: str = "change-me" admin_password: str = "change-me"
admin_session_ttl_hours: int = 12 admin_session_ttl_hours: int = 12
@field_validator("public_url", "frontend_base_url", mode="after")
@classmethod
def _strip_trailing_slash(cls, v: str) -> str:
# Every call site concatenates a leading-slash path directly onto
# these (e.g. f"{public_url}/mcp/sse") — a trailing slash in .env
# would silently produce a double-slash URL.
return v.rstrip("/")
@property @property
def cors_origins_list(self) -> list[str]: def cors_origins_list(self) -> list[str]:
return [o.strip() for o in self.cors_origins.split(",") if o.strip()] # A trailing slash here would never match a browser's Origin header
# (which never includes a path/slash), silently breaking CORS —
# strip it defensively so a .env typo can't cause that again.
return [o.strip().rstrip("/") for o in self.cors_origins.split(",") if o.strip()]
settings = Settings() settings = Settings()