CORS Middleware Configuration for FastAPI in Open Notebook: Production vs Development

Open Notebook configures FastAPI CORS through the CORS_ORIGINS environment variable, defaulting to wildcard (*) in development for unrestricted access while requiring explicit origin lists in production for security.

Open Notebook is an open-source knowledge management platform built on FastAPI. Its CORS middleware configuration provides a secure yet developer-friendly approach to cross-origin requests, using environment-driven settings that automatically adapt to development and production deployments.

How CORS Configuration Works in Open Notebook

The CORS implementation is centralized in api/main.py and follows a parse-load-register pattern that handles both happy-path requests and error responses.

Parsing the Environment Variable

The _parse_cors_origins function in api/main.py (lines 54-60) converts the raw environment string into a list of allowed origins:

def _parse_cors_origins(raw: str) -> list[str]:
    """Parse CORS_ORIGINS env value into a list of origins."""
    value = raw.strip()
    if value == "*":
        return ["*"]
    return [origin.strip() for origin in value.split(",") if origin.strip()]

This implementation supports comma-separated lists (e.g., https://app.com,https://admin.com) and treats a single asterisk as a wildcard that permits any origin.

Loading Values at Startup

At module import time (lines 62-66), the application evaluates the environment and sets flags for runtime behavior:

_cors_origins_raw = os.getenv("CORS_ORIGINS")
CORS_ALLOWED_ORIGINS = _parse_cors_origins(_cors_origins_raw or "*")
CORS_IS_DEFAULT_WILDCARD = _cors_origins_raw is None

If CORS_ORIGINS is unset, the system defaults to ["*"], enabling permissive cross-origin access suitable for local development. The CORS_IS_DEFAULT_WILDCARD flag tracks whether this fallback is active to trigger security warnings.

Middleware Registration

The CORSMiddleware is registered in api/main.py (lines 37-44) with credentials, methods, and headers fully permitted:

app.add_middleware(
    CORSMiddleware,
    allow_origins=CORS_ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

FastAPI processes middlewares in reverse order of registration. By adding CORSMiddleware last, it executes first on each request, ensuring CORS headers are injected before authentication or other processing layers.

Handling CORS on Error Responses

To ensure CORS headers appear even on authentication failures or server errors, Open Notebook implements a custom exception handler (lines 49-63) that reflects the request's origin when allowed:

@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request: Request, exc: StarletteHTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail},
        headers={**(exc.headers or {}), **_cors_headers(request)},
    )

The helper _cors_headers (lines 76-94) mirrors Starlette's behavior, ensuring preflight and actual error responses both carry the appropriate Access-Control-Allow-Origin headers.

Development vs Production Settings

The behavior differs significantly based on the CORS_ORIGINS environment variable:

Setting Typical Value Effect
Variable unset None (default) CORS_ALLOWED_ORIGINS = ["*"] → any origin may call the API. A warning is logged at startup.
Explicit origins https://notebook.example.com Only listed origins receive the Access-Control-Allow-Origin header. No startup warning.
Explicit wildcard * Same as default but intention is clear; startup warning is suppressed because the variable is present.

Production Warning: When CORS_ORIGINS is not set, Open Notebook logs a warning at startup (lines 212-221):

if CORS_IS_DEFAULT_WILDCARD:
    logger.warning(
        "CORS_ORIGINS is not set — API accepts cross-origin requests from any "
        "origin (default: '*'). For production deployments, set CORS_ORIGINS to "
        "your frontend origin(s)"
    )

Configuration Examples

Production Docker Compose

Restrict CORS to your specific frontend domain:


# docker-compose.yml

services:
  api:
    image: open-notebook/api:latest
    environment:
      - CORS_ORIGINS=https://notebook.example.com

Local Development

Omit the variable to enable unrestricted access:


# .env (development)

# No CORS_ORIGINS entry → wildcard mode

OPEN_NOTEBOOK_ENCRYPTION_KEY=dev-secret
uv run python -m api.main

# Log output: "CORS_ORIGINS is not set — API accepts cross-origin requests from any origin"

Runtime Inspection

Verify the effective configuration programmatically:

from api.main import CORS_ALLOWED_ORIGINS, CORS_IS_DEFAULT_WILDCARD

print("Allowed origins:", CORS_ALLOWED_ORIGINS)
print("Using default wildcard?", CORS_IS_DEFAULT_WILDCARD)

Key Implementation Files

  • api/main.py: Central FastAPI app creation, CORS parsing, middleware registration, and custom exception handling.
  • api/auth.py: Password authentication middleware; shows how CORS preflight (OPTIONS) requests bypass auth checks.
  • .env.example: Documents the CORS_ORIGINS variable and other required settings.

Summary

  • Open Notebook uses the CORS_ORIGINS environment variable to control FastAPI CORS middleware, defaulting to wildcard (*) when unset.
  • The _parse_cors_origins function supports comma-separated origin lists and explicit wildcards.
  • Registering CORSMiddleware last ensures it executes first, guaranteeing headers are present before authentication processing.
  • A custom exception handler ensures CORS headers are included even on HTTP error responses.
  • Always set CORS_ORIGINS to specific HTTPS origins in production to prevent cross-site request forgery and information leakage.

Frequently Asked Questions

How do I enable CORS for multiple subdomains in production?

Set CORS_ORIGINS to a comma-separated list of your trusted origins. For example: CORS_ORIGINS=https://app.example.com,https://admin.example.com,https://api.example.com. The _parse_cors_origins function splits on commas and trims whitespace, allowing you to authorize multiple frontends or subdomains from a single environment variable.

Why does Open Notebook warn about the default CORS configuration?

The warning appears when CORS_ORIGINS is unset because the system defaults to ["*"], which permits any website to make cross-origin requests to your API. According to the source code in api/main.py, this default is convenient for local development but exposes production deployments to cross-site request forgery (CSRF) and unauthorized data access.

Does the CORS middleware handle OPTIONS preflight requests?

Yes. Because CORSMiddleware is registered with allow_methods=["*"] and allow_headers=["*"], it automatically handles OPTIONS preflight requests with appropriate Access-Control-Allow-Methods and Access-Control-Allow-Headers responses. In api/auth.py, authentication checks typically skip these preflight requests to ensure browsers can verify permissions before sending credentials.

What happens if I set CORS_ORIGINS to an empty string?

An empty string evaluates to falsy, triggering the same wildcard default as an unset variable. The _parse_cors_origins function receives an empty string (via or "*"), returns ["*"], and sets CORS_IS_DEFAULT_WILDCARD = True, resulting in the permissive development mode with a startup warning. To restrict access, provide at least one valid origin URL.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →