OVA is a single-user, localhost voice assistant. It is not designed for internet exposure. The default configuration is safe for local use — it binds to localhost, requires no authentication, and disables tools and MCP by default.
The frontend uses Python's built-in static file server, which has no TLS, no rate limiting, and no security headers. The Content Security Policy hardcodes localhost URLs. These are deliberate choices for a local-only application.
- Security Defaults
- Single-Tenant Limitations
- Tool & MCP Safety
- API-Only Mode (SDK / Headless)
- Exposing OVA Beyond Localhost
- Configuration Reference
- Not Implemented
- Reporting Vulnerabilities
Out of the box, OVA provides the following protections:
- Localhost binding — both the backend API and frontend bind to
localhostonly - Input size limits — text input capped at 4096 characters, audio and image uploads capped at 20 MB each
- Tools disabled — LLM tool calling is off by default (
OVA_ENABLE_TOOLS=false) - MCP disabled — external MCP server connections are off by default (
OVA_ENABLE_MCP=false) - ASR isolation — speech recognition runs in a separate subprocess with restricted inter-process communication
- Offline models —
HF_HUB_OFFLINE=1prevents model downloads after initial install - Settings validation — the settings API rejects unknown values for TTS engine, language, and stream format via allowlists
OVA is architecturally single-tenant. These are not bugs — they are inherent to the design:
- One conversation context shared across all connected clients
- One TTS instance and one interrupt flag — shared by all clients
- Voice and language changes are global — one client switching language affects everyone
- System prompt changes affect all clients
For separate users, run separate OVA instances.
When OVA_ENABLE_TOOLS=true, the LLM can invoke Python functions during conversation.
What to know:
- No sandbox — tools run in the OVA process with full host access
- Auto-discovery — any
.pyfile inova/tools/becomes LLM-callable - Prompt injection risk — crafted user input can trick the LLM into calling tools in unintended ways
- External data exposure — tools that make network requests (e.g., web search) can leak query content to third parties
How to manage:
| Variable | Purpose |
|---|---|
OVA_ENABLE_TOOLS |
Master toggle (default: false) |
OVA_DISABLED_TOOLS |
Comma-separated list of tool names to disable |
OVA_TOOL_<NAME>_ENABLED |
Per-tool override (true/false) |
OVA_MAX_TOOL_ITERATIONS |
Cap on LLM-tool round-trips per request (default: 5) |
When OVA_ENABLE_MCP=true, OVA connects to external MCP servers as a client.
What to know:
- MCP servers run as subprocesses with a filtered environment — OVA secrets are stripped automatically
- Failed MCP connections are logged but do not block startup
- On tool name collision, native OVA tools always take priority over MCP tools
How to manage:
| Variable | Purpose |
|---|---|
OVA_ENABLE_MCP |
Master toggle (default: false) |
OVA_MCP_CONFIG |
Path to MCP server config file (default: mcp_servers.json) |
OVA_MCP_CONNECT_TIMEOUT |
Per-server connection timeout in seconds (default: 10) |
OVA_MCP_TOOL_TIMEOUT |
Per-tool call timeout in seconds (default: 30) |
Setting OVA_DISABLE_FRONTEND_ACCESS=true skips launching the static file server (port 8080). The API (port 5173) remains fully exposed — this flag only removes the frontend, it does not add any security by itself.
API-only mode still requires hardening:
OVA_API_KEYis mandatory — without it, all API endpoints are unauthenticated- All endpoints remain accessible: chat, TTS, ASR, settings, restart, etc.
- The browser authentication bypass does not apply — SDK clients must always provide the API key
- Single-tenant limitations still apply — multiple SDK clients share context, voice state, and interrupt
Recommended API-only settings:
OVA_API_KEY=<strong-random-key>
OVA_DISABLE_RESTART_ENDPOINT=true
OVA_DISABLE_FRONTEND_SETTINGS=true
OVA_DISABLE_FRONTEND_ACCESS=true
API-only is the safer way to expose OVA on a network compared to exposing the frontend, but it still requires all hardening steps from the next section.
This is discouraged. OVA is designed for local use. If you expose it to a network, you do so at your own risk.
- The frontend server is not production-grade — no TLS, no rate limiting, no security headers
- The Content Security Policy hardcodes
localhost— WebSocket and fetch calls will break on other hostnames without manual HTML edits - No built-in rate limiting
- The browser authentication bypass can be spoofed by non-browser clients
- No rate limiting — a client can flood the server with requests
- No per-user isolation — all clients share state
- No built-in TLS — requires a reverse proxy
OVA_API_KEY=<strong-random-key>— mandatory, without this everything is unauthenticatedOVA_DISABLE_RESTART_ENDPOINT=true— prevents remote server restartsOVA_DISABLE_FRONTEND_SETTINGS=true— prevents remote configuration changesOVA_DISABLE_FRONTEND_ACCESS=true— strongly recommended, skip the frontend and use API-only- HTTPS via reverse proxy — use nginx or Caddy for TLS termination
- Rate limiting — use a reverse proxy (nginx
limit_req, Caddyrate_limit) or application-level with slowapi (pip install slowapi) OVA_DEBUG=false— this is the default, but verify it is not set totrue- Firewall to trusted IPs — restrict network access to known clients
OVA_DISABLE_MULTIMODAL=true— if vision/image input is not neededOVA_ENABLE_TOOLS=falseandOVA_ENABLE_MCP=false— these are the defaults, keep them disabled unless needed- Keep
HF_HUB_OFFLINE=1— prevents the server from making outbound model download requests - Audit the
ova/tools/directory — guard against unauthorized file drops, since any Python file there becomes LLM-callable - Never set
OVA_MAX_TEXT_LENGTH,OVA_MAX_AUDIO_SIZE, orOVA_MAX_IMAGE_SIZEto0— this disables the limit entirely - Review
OVA_MAX_CONTEXT_MESSAGES— lower values reduce memory usage per session
All security-relevant environment variables, grouped by category.
| Variable | Default | Description |
|---|---|---|
OVA_API_KEY |
(empty) | API key for all endpoints. Empty = authentication disabled. |
| Variable | Default | Description |
|---|---|---|
OVA_DISABLE_RESTART_ENDPOINT |
false |
Disable POST /v1/restart endpoint |
OVA_DISABLE_FRONTEND_SETTINGS |
false |
Disable settings panel and POST /v1/settings |
OVA_DISABLE_MULTIMODAL |
false |
Disable text/image input, restrict to voice-only |
OVA_DISABLE_FRONTEND_ACCESS |
false |
API-only mode, skip static file server |
| Variable | Default | Description |
|---|---|---|
OVA_MAX_TEXT_LENGTH |
4096 |
Max text input in characters (0 = no limit) |
OVA_MAX_AUDIO_SIZE |
20971520 |
Max audio upload in bytes (20 MB, 0 = no limit) |
OVA_MAX_IMAGE_SIZE |
20971520 |
Max image upload in bytes (20 MB, 0 = no limit) |
OVA_MAX_CONTEXT_MESSAGES |
50 |
Conversation history sliding window (0 = unlimited) |
| Variable | Default | Description |
|---|---|---|
OVA_ENABLE_TOOLS |
false |
Enable LLM tool/function calling |
OVA_MAX_TOOL_ITERATIONS |
5 |
Max LLM-tool round-trips per request |
OVA_DISABLED_TOOLS |
(empty) | Comma-separated tool names to disable |
OVA_ENABLE_MCP |
false |
Enable MCP server connections |
OVA_MCP_CONFIG |
mcp_servers.json |
Path to MCP server config file |
OVA_MCP_CONNECT_TIMEOUT |
10 |
Per-server connection timeout (seconds) |
OVA_MCP_TOOL_TIMEOUT |
30 |
Per-tool call timeout (seconds) |
| Variable | Default | Description |
|---|---|---|
OVA_DEBUG |
false |
Enable debug endpoints and verbose error messages |
The following are out of scope for OVA and would require external infrastructure:
- Rate limiting — use a reverse proxy (nginx
limit_req, Caddyrate_limit) or application-level with slowapi for FastAPI-native rate limiting - Per-user session isolation — OVA is single-tenant by design
- Built-in TLS — use a reverse proxy for HTTPS termination
- WebSocket connection limits — use a reverse proxy to cap concurrent connections
Report security issues via GitHub Issues.