简体中文 | English
Only main is supported. Run the latest published image or rebuild
from main.
This service uploads audio, runs speaker diarization, and stores
speaker voiceprints. It is designed for trusted deployments. By
default, any client that can reach :8780 and holds API_KEY can:
- Upload audio into
data/uploads/and trigger GPU inference - Read every transcript under
data/transcriptions/ - Manipulate every enrolled voiceprint (persistent speaker embeddings — biometric data)
Treat the service as if it were an internal database.
As of 0.8.4 the following protections are in place out of the box:
- Container runs as a non-root user. The Dockerfile creates an
appuser (uid/gid 1000 by default, overridable viaAPP_UID/APP_GID) and ends withUSER app. A code-execution bug inside the service only grants that low-privilege account — it cannot read root-owned files on the host. - Upload size limit via
MAX_UPLOAD_BYTES(default 2 GiB). Chunked streaming copy aborts with413on overflow and deletes the partial artifact — no disk-exhaustion DoS. - Upload filename sanitization. Only the final path component
of the client-supplied
filenameis kept.../../etc/passwd.wavreduces topasswd.wavbefore the save path is built. --inserted before the ffmpeg input path. Closes option parsing so a filename like-Y.mp4can't be interpreted as a flag.- Constant-time key comparison.
hmac.compare_digestinstead of!=removes any timing side channel. - Atomic, locked voiceprint DB. SQLite WAL mode provides atomic
writes at the database level; a process-level
threading.RLockserializes concurrent mutations so parallel enroll/delete operations never corrupt the store. np.load(..., allow_pickle=False)everywhere. Closes thetorch.load-style pickle RCE path.- Exact-match
/docs,/redoc,/openapi.json. The previousstartswith("/docs")let/docsXYZslip past middleware; now it correctly returns 401. - Path traversal protection:
safe_tr_dir()validatestr_idwith regex^tr_[A-Za-z0-9_-]{1,64}$+resolve()prefix check;safe_speaker_label()applies equivalent character set restrictions - Log injection prevention:
safe_log_filename()strips control characters from user-supplied filenames before they reach log lines - Route parameter validation: FastAPI
Path(pattern=...)rejects malformed IDs at the framework level - ffmpeg timeout:
FFMPEG_TIMEOUT_SEC(default 1800 s) prevents malformed audio from hanging the process - Pickle protection:
np.load(allow_pickle=False)prevents arbitrary code execution from malicious.npyembedding files - Zero-vector defense: voiceprint
identify()returns early on all-zero embeddings, preventing AS-norm scoring from producing false matches
Things the code can't enforce that the operator must get right:
- Set
API_KEY. Without it the service accepts unauthenticated requests and logs a startup warning. Any deployment not on a fully trusted LAN segment MUST set this env var to a long random string. Clients send it asAuthorization: Bearer <key>orX-API-Key: <key>.- Generate one:
openssl rand -hex 32 - If you are genuinely on a trusted internal network and do not need authentication, set
ALLOW_NO_AUTH=1to suppress the startup warning (this variable provides no authentication — it only declares "I understand the implications of running without auth").
- Generate one:
- Never commit
.env. Only.env.examplebelongs in git. - Do not expose
:8780to the public Internet. Put it behind a VPN, a reverse proxy with TLS, or at minimum an IP allow-list.API_KEYalone is not a substitute for transport encryption. - Keep your HuggingFace token out of logs and out of images. It
is read from
HF_TOKENat runtime and used only to download pyannote models — nothing else. - Back up
data/voiceprints/. Voiceprints are biometric data; losing them means re-enrolling every speaker, and leaking them is worse than leaking regular DB rows. - Match the host directory owner to
APP_UID/APP_GID. The container runs as uid 1000 by default — if yourDATA_DIRis owned by a different user, eitherchown -R 1000:1000 DATA_DIRor setAPP_UID/APP_GIDin.envto the real owner.
- No built-in rate limiting or failed-auth lockout. Acceptable in the single-tenant + long-random-key threat model; once the key leaks, brute-force isn't throttled. Put a rate limit at the reverse-proxy layer if you need one.
- No TLS terminated by this service. Intentional — the service is
meant to sit behind nginx/caddy/traefik. Only expose
:8780on a network you trust. server: uvicornresponse header. Minor fingerprint disclosure, no real attack surface, not scrubbed.
Please open a private security advisory on GitHub or email the maintainer. Do not file public issues for unpatched vulnerabilities.