How to Configure CORS Origins for Production Deployments in Open Notebook
Set the CORS_ORIGINS environment variable to a comma-separated list of your trusted frontend URLs (e.g., https://app.example.com) before starting the API to restrict cross-origin requests to specific domains only.
Open Notebook uses FastAPI's CORSMiddleware to manage cross-origin resource sharing between the API and frontend clients. By default, the application accepts requests from any origin ("*"), which is convenient for local development but insecure for production environments. This guide explains how to properly configure CORS origins for production deployments using the environment variable parsing logic implemented in api/main.py.
Understanding the Default CORS Behavior
When the Open Notebook API starts without the CORS_ORIGINS environment variable set, it falls back to a permissive wildcard configuration. In api/main.py (lines 63-67), the code checks for the presence of _cors_origins_raw and defaults to ["*"] if the variable is missing.
The application sets CORS_IS_DEFAULT_WILDCARD = True when this fallback occurs and logs a security warning to indicate that all origins are currently allowed. This default behavior is intentionally insecure for development convenience but must be overridden before deploying to production.
Configuring Allowed Origins via Environment Variables
To restrict the API to specific domains, set the CORS_ORIGINS environment variable to a comma-separated list of exact origin URLs. The API parses this value during module initialization and passes the resulting list to FastAPI's middleware.
Parsing Logic in api/main.py
The _parse_cors_origins function (lines 53-58) handles the transformation of the environment string into a validated list:
# api/main.py – parsing the env var
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()]
_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
This function strips whitespace from the input, handles the special "*" case, and splits comma-separated values into a clean list of origin strings.
Middleware Registration
After parsing, the origins are passed to CORSMiddleware (lines 88-95):
# api/main.py – attaching the middleware
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
The configuration also applies to custom exception handlers through the _cors_headers implementation (lines 67-88), ensuring error responses include the appropriate Access-Control-Allow-Origin header.
Production Deployment Configuration
For production deployments, create a .env file at the repository root or inject the variables through your container orchestration platform.
Example Production Environment File
# Core security
OPEN_NOTEBOOK_ENCRYPTION_KEY=very-strong-secret-key
OPEN_NOTEBOOK_PASSWORD=super-secure-password
# CORS configuration – replace with your actual frontend domains
CORS_ORIGINS=https://notebook.example.com,https://admin.example.com
# API address (used by the Next.js proxy)
API_URL=https://notebook.example.com
Critical: Changes to CORS_ORIGINS require a full restart of the API process because the variable is parsed only once at module load time. Hot-reloading does not refresh this configuration.
Verifying Your CORS Configuration
After deployment, confirm your CORS restrictions are active:
- Check startup logs for the message "CORS allowed origins:" followed by your specific domain list (not
["*"]). - Inspect response headers from a frontend request to verify
Access-Control-Allow-Originmatches your configured origin exactly. - Test rejection by attempting a request from an unauthorized domain, which should fail the CORS preflight or actual request.
Summary
- Default risk: Without
CORS_ORIGINS, the API allows all origins (["*"]) and logs a security warning. - Configuration method: Set
CORS_ORIGINSto a comma-separated list of trusted frontend URLs with protocols. - Implementation location: The parsing logic resides in
api/main.py(lines 53-58) with middleware registration at lines 88-95. - Restart required: Environment changes only take effect after restarting the API process.
- Error handling: CORS headers are included in error responses through custom exception handlers.
Frequently Asked Questions
What happens if I don't set CORS_ORIGINS in production?
If you omit the CORS_ORIGINS variable, the API defaults to allowing all origins (["*"]). While this won't break functionality, it exposes your API to cross-origin requests from any website, potentially allowing malicious sites to make authenticated requests. The application logs a warning at startup to alert you of this insecure configuration.
Can I use wildcards or patterns in CORS_ORIGINS?
No, the _parse_cors_origins function in api/main.py does not support pattern matching or subdomain wildcards (except the single "*" value to allow all origins). You must specify each exact origin URL, including the protocol (e.g., https://app.example.com). For multiple origins, separate them with commas without spaces.
Why do I need to restart the API after changing CORS_ORIGINS?
The environment variable is read and parsed only once when the api/main.py module loads during startup. The resulting list is stored in the CORS_ALLOWED_ORIGINS constant and passed to the middleware. Changes to environment variables are not hot-reloaded, so you must restart the process to re-parse the configuration.
Do CORS headers apply to error responses in Open Notebook?
Yes, the Open Notebook API ensures CORS headers are present even on error responses. The custom exception handlers in api/main.py (lines 67-88) include the _cors_headers logic, guaranteeing that browser clients receive the Access-Control-Allow-Origin header even when the API returns 4xx or 5xx status codes.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →