VoiceStudio Loopback-Only API Authentication Model: Share PIN, Bearer Key, and Admin-Gate Separation Explained
VoiceStudio's backend defaults to a loopback-only API where local processes require no credentials, with optional Share PIN, Bearer Key, and trusted-network gates for remote access, plus strict admin-route isolation that never permits PIN-based authorization.
VoiceStudio's authentication architecture prioritizes zero-friction local development while providing layered security for network deployments. The debpalash/VoiceStudio repository implements this through three distinct protection mechanisms and a critical administrative boundary that prevents privilege escalation.
How VoiceStudio's Loopback-Only Foundation Works
By default, any process on the same machine can access http://localhost:3900 without authentication. Connections from 127.0.0.1, ::1, or localhost bypass all gates unconditionally in both NetworkAccessMiddleware and BearerKeyMiddleware as implemented in backend/main.py【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/main.py#L65-L106】【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/main.py#L71-L118】.
This design ensures desktop integrations, local scripts, and the first-party UI operate without configuration friction while the API surface remains protected from remote access until deliberately exposed.
Share PIN Gate: Session-Based HTTP Protection
The Share PIN gate controls access to non-loopback HTTP routes and is activated via the in-app Network share toggle. When enabled, NetworkAccessMiddleware intercepts every incoming HTTP request from external clients.
PIN Presentation Methods
VoiceStudio accepts the 6-digit PIN through three channels:
- Header:
X-OmniVoice-Pin: <pin> - Query parameter:
?pin=<pin> - Cookie:
ov_pin=<pin>(set automatically after first valid submission)
The middleware in backend/main.py extracts and validates against the generated PIN, returning 401 "PIN required" for mismatches. Valid PINs are echoed back as short-lived cookies to streamline subsequent requests.
PIN Gate Limitations
The Share PIN only protects HTTP routes. WebSocket connections remain unauthenticated unless the Bearer Key gate is active. Static SPA assets are also exempted to allow the UI shell to load before authentication.
Bearer Key Gate: Durable HTTP and WebSocket Protection
The Bearer Key gate provides stronger, persistent authentication activated by setting OMNIVOICE_API_KEY as an environment variable. BearerKeyMiddleware enforces this on all non-loopback HTTP and WebSocket traffic.
Key Presentation Methods
Per backend/main.py【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/main.py#L71-L118】:
- Preferred:
Authorization: Bearer <key>header - Legacy cookie:
ov_key=<key> - Legacy query:
?api_key=<key>
Invalid or missing keys trigger 401 "API key required" for HTTP or WebSocket handshake termination with code 1008 (policy violation).
Bearer Key Use Cases
This gate suits:
- Long-lived service integrations
- WebSocket-based real-time transcription
- Automated CI/CD pipelines
- SDK and API clients requiring stable credentials
Trusted-Network Exemption: LAN and Tailscale Deployment
The OMNIVOICE_TRUSTED_NETWORKS environment variable accepts a CIDR list (e.g., 192.168.1.0/24,100.64.0.0/10) parsed in backend/api/dependencies.py【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/api/dependencies.py#L45-L59】. When a client's IP falls within any listed range, is_local_host() returns true, exempting consumption routes from both Share PIN and Bearer Key requirements.
This exemption does not extend to administrative routes regardless of network membership.
Admin-Gate Separation: The Critical Security Boundary
VoiceStudio implements strict privilege isolation through require_admin in backend/api/dependencies.py. The admin surface (/system/*, /api/settings/*) operates under fundamentally different rules:
Default Desktop Build Behavior
Admin routes are true-loopback only. Even with valid Share PIN or Bearer Key presented from a remote machine, administrative endpoints return 403. This prevents brute-forceable 6-digit PINs from compromising engine installation or system configuration endpoints.
Server Mode Relaxation
When OMNIVOICE_SERVER_MODE=1 (Docker deployments), the loopback check relaxes because NAT can mask source IPs. However, admin access still requires either:
- Valid Bearer Key in
Authorizationheader - Short-lived admin session cookie from
/api/auth/session
The Share PIN never authorizes admin actions. Trusted-network membership is ignored for admin routes.
Practical Authentication Examples
Calling HTTP Endpoints with Share PIN
import requests
host = "192.168.1.42:3900"
pin = "123456" # 6-digit PIN from Network share UI
resp = requests.get(
f"http://{host}/v1/audio/voices",
headers={"X-OmniVoice-Pin": pin},
)
print(resp.json())
HTTP Bearer Key Authentication (Recommended)
import os
import requests
api_key = os.getenv("OMNIVOICE_API_KEY")
resp = requests.post(
f"https://gpu-box:3900/v1/audio/speech",
headers={"Authorization": f"Bearer {api_key}"},
json={"model": "tts-1", "voice": "alloy", "input": "Hello world"},
)
with open("speech.wav", "wb") as f:
f.write(resp.content)
WebSocket Connection with API Key
import websockets
import asyncio
import os
api_key = os.getenv("OMNIVOICE_API_KEY")
ws_url = f"ws://gpu-box:3900/ws/transcribe?api_key={api_key}"
async def listen():
async with websockets.connect(ws_url) as ws:
await ws.send(b"... audio frames ...")
async for msg in ws:
print(msg)
asyncio.run(listen())
Admin Session Bootstrap
import requests
# Exchange master key for short-lived admin session
resp = requests.post(
"http://localhost:3900/api/auth/session",
headers={"Authorization": f"Bearer {api_key}"},
json={"transport": "cookie"},
)
session_cookie = resp.cookies.get("ov_session")
# Subsequent admin calls use this HttpOnly cookie automatically
Gate Combinations and Interaction
| Configuration | Result |
|---|---|
| No env vars, no UI toggle | Loopback-only, no credentials required |
| Share PIN only | Remote HTTP needs PIN; WebSocket open; admin loopback-only |
| Bearer Key only | Remote HTTP and WebSocket need key; admin needs key or loopback |
| Both gates active | HTTP must pass PIN or key checks; WebSocket needs key |
| Trusted networks defined | Listed CIDRs skip PIN/key for consumption routes |
Both gates remain independent—enabling both does not create layered requirements on the same route. Each middleware checks only the credential types it recognizes.
Summary
- Loopback traffic (
127.0.0.1,::1,localhost) bypasses all authentication unconditionally - Share PIN protects HTTP consumption routes with temporary, UI-generated 6-digit codes
- Bearer Key (
OMNIVOICE_API_KEY) secures HTTP and WebSocket with durable credentials - Trusted networks (
OMNIVOICE_TRUSTED_NETWORKS) exempt consumption routes from PIN/key requirements for LAN/Tailscale deployments - Admin routes (
/system/*,/api/settings/*) require master API key or admin session; PIN authorization is explicitly rejected; trusted-network membership is ignored
Frequently Asked Questions
Can I use the Share PIN for WebSocket authentication?
No. The Share PIN gate implemented in NetworkAccessMiddleware only inspects HTTP requests. WebSocket connections remain unauthenticated unless you activate the Bearer Key gate with OMNIVOICE_API_KEY. For WebSocket protection, present the key via ?api_key= query parameter or exchange it for a session first.
Why does my valid PIN return 403 on /system or /api/settings endpoints?
Administrative routes reject PIN authentication by design. The require_admin dependency in backend/api/dependencies.py enforces that admin operations require either loopback origin (desktop default) or Bearer Key/server-mode session. This prevents brute-force attacks against the 6-digit PIN from compromising engine installation or system configuration capabilities.
How do I enable authentication for a Tailscale deployment without sharing the master API key?
Set OMNIVOICE_TRUSTED_NETWORKS to your Tailscale CIDR (typically 100.64.0.0/10). This exempts consumption routes from PIN/key requirements while keeping admin routes protected. For stricter control, enable the Share PIN in the UI—remote users on the Tailscale network will then need the temporary PIN without you distributing long-lived credentials.
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 →