fix(prod): harden production configuration for launch
All checks were successful
Mirror to GitHub / mirror (push) Successful in 5s

- Gate Swagger/ReDoc/OpenAPI behind DEBUG (no public API schema in prod)
- Sentry send_default_pii only in dev (no auth headers/bodies in events)
- Remove alembic from Dockerfile CMD (releaseCommand owns migrations; CMD copy raced across replicas/restarts)
- Decouple rate limiting from DEBUG via RATE_LIMIT_ENABLED (PR envs with DEBUG=true were unlimited); tests disable the live limiter in conftest
- max_instances=1 on the 4 scheduler jobs missing it
- Boot-time failure when SELF_SERVE_ENABLED without RESEND_API_KEY/ANTHROPIC_API_KEY/FRONTEND_URL
- Reject localhost OAUTH_REDIRECT_BASE outside DEBUG
- pool_pre_ping + pool_recycle on the app engine
- Frontend: DEV-gate stale-async console.warn; document VITE_SELF_SERVE_ENABLED fallback semantics in Dockerfile

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 19:22:35 -04:00
parent 87236b57d2
commit 4dc4894fc7
9 changed files with 70 additions and 14 deletions

View File

@@ -85,6 +85,10 @@ class Settings(BaseSettings):
# Security
BCRYPT_ROUNDS: int = 12
# Rate limiting — independent of DEBUG so PR/staging envs running with
# DEBUG=true still rate-limit auth and AI endpoints.
RATE_LIMIT_ENABLED: bool = True
# Security Headers
CSP_REPORT_ONLY: bool = True # Set False to enforce CSP
CSP_EXTRA_SCRIPT_SOURCES: list[str] = [] # Additional script-src domains
@@ -255,6 +259,18 @@ class Settings(BaseSettings):
MS_CLIENT_SECRET: Optional[str] = None
OAUTH_REDIRECT_BASE: str = "http://localhost:5173"
@field_validator("OAUTH_REDIRECT_BASE", mode="after")
@classmethod
def reject_localhost_redirect_in_production(cls, v: str, info) -> str:
"""OAuth code exchange against a localhost redirect_uri is always a
misconfiguration outside DEBUG — fail at boot, not at first sign-in."""
debug = info.data.get("DEBUG", False)
if not debug and v.startswith("http://localhost"):
raise ValueError(
"OAUTH_REDIRECT_BASE must be set to the public frontend URL in production"
)
return v
# Monitoring
SENTRY_DSN: Optional[str] = None

View File

@@ -7,7 +7,10 @@ from app.core.tenant_context import register_tenant_listener
engine = create_async_engine(
settings.DATABASE_URL,
echo=settings.DEBUG,
future=True
future=True,
# Detect connections dropped by DB restarts/maintenance instead of failing requests
pool_pre_ping=True,
pool_recycle=1800,
)
# Create async session factory

View File

@@ -3,4 +3,4 @@ from slowapi.util import get_remote_address
from app.core.config import settings
limiter = Limiter(key_func=get_remote_address, enabled=not settings.DEBUG)
limiter = Limiter(key_func=get_remote_address, enabled=settings.RATE_LIMIT_ENABLED)