Data ownership
Everything VoicEra stores — call recordings, transcripts, knowledge-base documents, provider credentials — stays in the Postgres, MinIO, and FerretDB containers you run. Nothing is sent to a VoicEra-operated service, and there is no telemetry or usage reporting baked into the platform. The only external network calls the stack makes are the ones you configure yourself: your chosen STT/TTS/LLM providers, and your telephony provider. Self-hosting every model (see Self-hosted models) removes those too, for a fully air-gapped deployment.Change every default
All of these are public knowledge — they are in.env.example and docker-compose.yaml.
The three generated secrets
make application-up (which wraps ./scripts/start-application-services.sh) generates these when blank and never overwrites them.
Generate:
CORS
apps/api/app/main.py sets:
The internal API key
INTERNAL_API_KEY is a single shared credential with organisation-wide reach. POST /users/bot/token exchanges it plus an org_id for a token with role admin in that organisation — for any organisation.
The unauthenticated runtime endpoints
Mitigations:- Rate limit
/answerand/agentat the proxy. - IP-allowlist your telephony provider’s published ranges.
- Treat org and agent ids as semi-secret — do not put them in public pages or client-side code.
- Monitor call volume for unexplained sessions.
Network exposure
Publish only what must be public:
To stop publishing a port, remove its
ports: entry or bind it to loopback:
TLS
Telephony providers require HTTPS for webhooks and WSS for audio, so TLS is mandatory rather than optional. Terminate at a reverse proxy — see Production deployment for a working nginx configuration, including the WebSocket upgrade headers and the long read timeouts calls need. For Redis over TLS use arediss:// URL; the ARQ settings enable TLS when they see that scheme.
Email enumeration
Health probes
GET /health returns HTTP 200 even when the database is down — only the body changes to "status": "degraded". Configure probes to parse the body, or a broken API will look healthy.
Images and dependencies
minio/minio:latestis unpinned — pin a digest for reproducible deployments. The FerretDB, Postgres, and Redis images are already pinned.- Rebuild periodically to pick up base-image security updates.
- VoicEra has no CI, so nothing scans dependencies automatically. Run
pip-auditor equivalent yourself.
Log hygiene
Logs go tojson-file, rotating at 10 MB with three files kept. Before shipping them anywhere central, confirm no provider keys or tokens appear — and note that DEBUG=True substantially increases what is logged. Keep it False in production.
Data protection
You hold call recordings, transcripts, and contact lists. That is regulated data in most jurisdictions.- Encrypt volumes at rest.
- Set a retention policy — nothing expires automatically.
- Restrict MinIO access; artifacts are served through the authenticated API proxy, so the bucket never needs to be public.
- Remember
docker compose down -vdestroys all of it irreversibly.
Checklist
-
MONGODB_PASSWORDchanged -
MINIO_ROOT_USERandMINIO_ROOT_PASSWORDchanged -
REDIS_PASSWORDchanged -
SECRET_KEYset and non-empty -
INTERNAL_API_KEYset and non-empty -
PROVIDER_AUTH_ENCRYPTION_KEYset and backed up -
SECRET_KEYidentical across API replicas - CORS restricted
- TLS on the API and runtime
- FerretDB, MinIO console, and the model gateway not publicly reachable
- Rate limiting on
/answer,/agent, and/users/check -
DEBUG=False - Volumes encrypted, retention policy set
- Backups tested by restoring