API key authentication
Whenserver_api_key is set, Voxray enforces bearer token authentication on all sensitive endpoints. Requests missing a valid key receive 401 Unauthorized with a standard error envelope.
Enabling authentication
- config.json
- Environment Variable
Protected and unprotected endpoints
/health and /ready are intentionally unauthenticated so load balancers and orchestrators can probe them without credentials. Restrict /metrics access at the network level rather than relying on API key auth.Sending the API key
Voxray accepts the key in either of two headers — use whichever fits your client library:CORS configuration
Voxray’s CORS behavior is explicitly opt-in. An emptycors_allowed_origins does not mean “allow all” — it means no Access-Control-Allow-Origin header is set, which will cause browsers to block cross-origin requests.
Setting allowed origins
- config.json
- Environment Variable
Headers always allowed
Regardless of origin configuration, Voxray always includes these inAccess-Control-Allow-Headers:
Authorization: Bearer or X-API-Key without needing custom CORS preflight configuration.
CORS and WebSocket
WebSocket connections (GET /ws) go through the same CORS check as HTTP endpoints. If your browser client uses the native WebSocket API, ensure the origin header sent by the browser matches one of your configured allowed origins.
Request body limits
Voxray limits JSON request body size on all endpoints that accept a body (/start, /webrtc/offer, /sessions/{id}/api/offer, /daily-dialin-webhook). This prevents large-payload denial-of-service without requiring any middleware.
- config.json
- Environment Variable
400 Bad Request with an error indicating the body was too large. The connection is not terminated — only the oversized request is rejected.
The default 256 KB limit is sufficient for all standard API payloads. The 1 MiB recommendation adds headroom for large SDP offers with many ICE candidates or custom
body fields in /start without creating risk of multi-megabyte abuse.Secrets management
Never commit API keys to git
Voxray supports two places to specify API keys: theapi_keys map in config.json, and environment variables. The environment variable path is strongly preferred for production.
What not to do:
config.json is committed to git, the keys are permanently in history even after deletion.
What to do instead:
config.json and git
Voxray’s default.gitignore excludes config.json. Verify two things before your first commit:
config.jsonis listed in.gitignore(or is absent from the repo entirely)config.example.json, if it exists, does not contain real provider keys — only placeholder strings like"YOUR_OPENAI_KEY_HERE"
Key rotation
Voxray caches resolved API keys in memory after the first lookup. To rotate any key — provider API keys orserver_api_key — a process restart is required. There is no hot-reload mechanism for secrets in the current release.
For zero-downtime rotation in Kubernetes:
- Update the Secret with the new key value
- Perform a rolling deployment (
kubectl rollout restart deployment/voxray) - Kubernetes will bring up new pods with the new key before terminating old pods
Container hardening
Non-root user
The Voxray Dockerfile runs the server as a non-root user namedvoxray. This is the default — no configuration change is required. Verify with:
--user root in production. If a bind port below 1024 is required, use a reverse proxy instead of running as root.
Read-only config mount
Mountconfig.json as read-only. The :ro flag prevents the process from modifying the config file, which closes a class of config-corruption bugs and limits blast radius if the process is compromised:
Minimal base image
The Voxray image is built on Alpine Linux 3.20, which has a base size of approximately 7 MB. The attack surface is minimal — no shell utilities, no package manager, no cron, no sshd in the final stage.Network policies
In Kubernetes, restrict outbound traffic from Voxray pods to only the external APIs they need to reach. Voxray does not require inbound connections from other pods except your load balancer or ingress. Example NetworkPolicy skeleton:Production security checklist
Run through every item before promoting to production.- TLS enabled — on-server (
tls_enable: true) or reverse proxy termination -
server_api_keyset to a long random string — use a secrets manager or environment variable; rotate on suspected compromise - CORS origins explicitly set —
cors_allowed_originslists every domain your front-end runs on; no wildcards - Body size limit configured —
max_request_body_bytes: 1048576or appropriate for your payload sizes -
/metricsnot publicly accessible — firewalled at the network level or restricted to your Prometheus scrape CIDR - API keys in environment variables —
OPENAI_API_KEY,DAILY_API_KEY,VOXRAY_SERVER_API_KEY, and all other provider keys sourced from env or a secrets manager - Config file not committed to git —
config.jsonin.gitignore;config.example.jsoncontains only placeholder values - Running as non-root — default Docker image user is
voxray(uid 1001); do not override with--user root - Network policies restrict outbound to only required provider APIs — no unrestricted egress from Voxray pods