Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ DATABASE_URL=postgresql+asyncpg://aaip:aaip_secret@db:5432/aaip
# Set to "true" to skip auth in local development (never in production)
AAIP_DEV_MODE=false

# ── CORS Security ────────────────────────────
# Environment: development or production
ENV=development
# Comma-separated list of allowed origins (no wildcards in production)
AAIP_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8000
# ── AI Judges (via OpenRouter) ───────────────
# Required for multi-model jury evaluation
# Get your key at https://openrouter.ai
Expand Down
30 changes: 29 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"""
from __future__ import annotations

import logging
import os
from contextlib import asynccontextmanager
from datetime import datetime
from typing import AsyncIterator
Expand Down Expand Up @@ -64,9 +66,35 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
redoc_url="/redoc",
)

# ── CORS Configuration ────────────────────────────────────────────────────────
logger = logging.getLogger(__name__)

# Safe ENV handling
env = os.getenv("ENV", "development").strip().lower()

# Parse AAIP_ALLOWED_ORIGINS
origins = os.getenv("AAIP_ALLOWED_ORIGINS", "")
origin_list = [o.strip() for o in origins.split(",") if o.strip()]

# Development defaults
if not origin_list and env != "production":
origin_list = ["http://localhost:3000", "http://localhost:8000"]

# Empty origins protection
if not origin_list:
raise ValueError("CORS configuration error: AAIP_ALLOWED_ORIGINS is empty or invalid")

# Production validation
if env == "production":
if "*" in origin_list:
raise ValueError("Wildcard '*' not allowed in production CORS configuration")

# Log only count (privacy)
logger.info(f"CORS configured with {len(origin_list)} allowed origin(s)")

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=origin_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Expand Down
2 changes: 2 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ services:
CELERY_RESULT_BACKEND: redis://redis:6379/1
REDIS_URL: redis://redis:6379/0
AAIP_DEV_MODE: "false"
ENV: development
AAIP_ALLOWED_ORIGINS: http://localhost:3000,http://localhost:8000
depends_on:
db:
condition: service_healthy
Expand Down
Loading