Open Notebook CORS Configuration and Security Hardening: Production Deployment Guide
Secure your Open Notebook deployment by restricting CORS origins to specific domains via the CORS_ORIGINS environment variable, enabling password authentication with OPEN_NOTEBOOK_PASSWORD, and applying container hardening measures including localhost binding and privilege restrictions.
Open Notebook ships with a development-friendly CORS policy that accepts requests from any origin ("*"), making it critical to harden settings before deploying to production. The FastAPI backend in lfnovo/open-notebook provides environment-based configuration for origin restrictions, password authentication, and encryption key management. This guide covers the exact steps to secure your API using the configuration system implemented in the source code.
CORS Configuration for Production
Default Development Behavior
If the environment variable CORS_ORIGINS is undefined, the API logs a security warning and accepts cross-origin requests from any origin. This behavior is implemented in api/main.py during application startup, where the middleware initializes with a wildcard origin list when no explicit configuration is provided.
Restricting Allowed Origins
For production, define CORS_ORIGINS as a comma-separated list of exact URLs including the scheme and port. The value is parsed once at module load by the _parse_cors_origins function and fed to FastAPI’s CORSMiddleware. Only the listed origins receive the Access-Control-Allow-Origin header, while browsers block unlisted origins and omit the header from error responses to prevent credential leakage.
# .env production example
CORS_ORIGINS=https://notebook.example.com,https://admin.example.com:8080
# api/main.py - CORS middleware initialization
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ALLOWED_ORIGINS, # Derived from CORS_ORIGINS env var
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Authentication and Encryption
Password Protection
The PasswordAuthMiddleware in api/auth.py enforces bearer-token authentication on every request except explicitly excluded public endpoints. The middleware retrieves the password from the OPEN_NOTEBOOK_PASSWORD environment variable or a corresponding Docker secret file.
# api/auth.py
class PasswordAuthMiddleware(BaseHTTPMiddleware):
def __init__(self, app, excluded_paths: Optional[list] = None):
super().__init__(app)
self.password = get_secret_from_env("OPEN_NOTEBOOK_PASSWORD")
API Key Encryption
Stored credentials are encrypted using Fernet symmetric encryption derived from OPEN_NOTEBOOK_ENCRYPTION_KEY. The key must be supplied in production; otherwise, the application fails to store credentials securely and logs a warning during startup in api/main.py. The key retrieval logic handles both environment variables and Docker secrets in open_notebook/utils/encryption.py.
# Generate secure keys
OPEN_NOTEBOOK_PASSWORD=$(openssl rand -base64 24)
OPEN_NOTEBOOK_ENCRYPTION_KEY=$(openssl rand -base64 32)
Container and Network Hardening
Docker Security Options
Run the container as a non-privileged user with explicit resource constraints. Bind the API port only to localhost to prevent direct external access, forcing traffic through a reverse proxy.
# docker-compose.yml production configuration
services:
open_notebook:
image: lfnovo/open_notebook:v1-latest
ports:
- "127.0.0.1:8502:8502" # Localhost only
environment:
- CORS_ORIGINS=https://notebook.example.com
- OPEN_NOTEBOOK_PASSWORD=${OPEN_NOTEBOOK_PASSWORD}
- OPEN_NOTEBOOK_ENCRYPTION_KEY=${OPEN_NOTEBOOK_ENCRYPTION_KEY}
security_opt:
- no-new-privileges:true
deploy:
resources:
limits:
memory: 2G
cpus: "1.0"
restart: always
Firewall and Reverse Proxy
Block direct access to internal ports using ufw or iptables. The API typically listens on port 8502 (SurrealDB) and 5055 (API), which should not be exposed to the public internet. Terminate TLS at a reverse proxy (nginx, Caddy, or Traefik) and forward requests to the internal bound address.
# UFW firewall configuration
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 8502/tcp # SurrealDB
sudo ufw deny 5055/tcp # API
sudo ufw enable
Additional Production Hardening
- Secret Management – Never commit credentials to source control; use Docker secrets or environment files with restricted permissions.
- HTTPS Enforcement – Since passwords are transmitted as bearer tokens, TLS encryption is mandatory to prevent eavesdropping.
- External Authentication – Consider integrating OAuth2 or SSO for multi-user enterprise deployments instead of relying solely on shared passwords.
- Monitoring – Enable log aggregation and alerting for authentication failures; implement rate-limiting at the reverse proxy level.
Summary
- Set
CORS_ORIGINSto a comma-separated list of specific domains to replace the default wildcard policy inapi/main.py. - Configure
OPEN_NOTEBOOK_PASSWORDandOPEN_NOTEBOOK_ENCRYPTION_KEYto enable thePasswordAuthMiddlewareand secure credential storage. - Bind Docker ports to
127.0.0.1only, applyno-new-privileges:true, and set resource limits to harden the container runtime. - Block direct access to ports
8502and5055using firewall rules and use a TLS-terminating reverse proxy for all traffic. - Reference the security documentation in
docs/5-CONFIGURATION/security.mdfor comprehensive hardening guidelines.
Frequently Asked Questions
What happens if I don't set CORS_ORIGINS in production?
The application will log a security warning and allow requests from any origin ("*"), exposing your API to cross-origin attacks from malicious websites. The CORSMiddleware in api/main.py defaults to permissive settings only when CORS_ORIGINS is undefined, making explicit configuration critical for production security.
How does Open Notebook handle password authentication?
The PasswordAuthMiddleware class in api/auth.py intercepts every incoming request and validates a bearer token against the OPEN_NOTEBOOK_PASSWORD environment variable. Requests to protected endpoints must include an Authorization: Bearer <password> header, while a few public paths (like health checks) are excluded from this requirement.
Can I use Docker secrets instead of environment variables for sensitive keys?
Yes. The application checks for suffixed *_FILE environment variables (e.g., OPEN_NOTEBOOK_PASSWORD_FILE) and reads the secret content from the specified file path. This pattern is implemented in the utility functions referenced in open_notebook/utils/encryption.py and supported for both the encryption key and authentication password.
Why should I bind the container port to 127.0.0.1?
Binding to localhost (127.0.0.1:8502:8502) ensures the API is not directly accessible from the network interface, eliminating attack vectors against the FastAPI application and SurrealDB. All external traffic must route through your reverse proxy, which handles TLS termination and request filtering before forwarding to the internal localhost address.
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 →