Spaces:
Sleeping
Download docs/security.md from GarchenArchive/ArchiveAI-TTS: direct link, hf CLI and curl.
- Browser
- Download file 5.02 kB
-
https://huggingface.co/spaces/GarchenArchive/ArchiveAI-TTS/resolve/main/docs/security.md
- Command line
-
hf download hf://spaces/GarchenArchive/ArchiveAI-TTS/docs/security.md
-
curl -L -o security.md https://huggingface.co/spaces/GarchenArchive/ArchiveAI-TTS/resolve/main/docs/security.md
Security
App-specific notes. The shared controls β firewall, container isolation, the OAuth gate, host hardening β are documented once, for the whole droplet, in the Orchestrator's security doc. Read that first; this covers what's particular to ArchiveAI-TTS.
Two deployments
| DO droplet | HuggingFace Space | |
|---|---|---|
| UI access | Google OAuth, 2-address allowlist | HF Space visibility settings |
| API access | X-API-Token, OAuth-exempt path |
Same app check, no Caddy in front |
| Project storage | Local files under TTS_DATA_DIR on the volume |
HF dataset tts-turn-projects |
| Glossary storage | HF dataset tts-glossary β on both |
Same |
The fail-closed API_TOKEN behavior applies to both.
This is the one app that writes to HuggingFace during normal droplet operation
Exception to how the rest of the droplet works. TTS_DATA_DIR=/data is set, so projects and audio go to the attached volume. The pronunciation glossary is stored on a HuggingFace dataset regardless (GarchenArchive/tts-glossary, overridable with GLOSSARY_DATASET_REPO), because it's shared with the HF Space deployment.
Consequences for credentials:
- This container's
HF_TOKENneeds HuggingFace access scoped to write on the datasetGarchenArchive/tts-glossary, and nothing else. Glossary reads fall back to a local cache, so a read-only or absent token degrades gracefully; saves and removals need write. - Today it's the account-owner token, which also grants org-admin on
GarchenArchiveandkhyentsevisionplus write onBDRCandopenpecha, far beyond what this app needs. Full scoping table: Orchestrator security β the HuggingFace token. - The same value doubles as the
POST /api/drainoperator secret. If the token is scoped down, drain's bearer value becomes this app's glossary-write token, meaning anyone who can drain also holds glossary write. Acceptable as-is; split into a separate secret if that risk is unacceptable.
Third-party API keys
This app holds the most valuable non-HF credentials on the droplet, because both cost money per call:
| Key | Used for | Notes |
|---|---|---|
ELEVENLABS_API_KEY |
Speech synthesis | Metered per character. A leak is a direct billing risk. |
GEMINI_API_KEY |
Turn-plan generation | Metered per call. |
Handling:
- Both are passed via
--env-file, never-eon the command line, so they don't appear in the host'spsoutput. - Callers may override either per job (
elevenlabs_api_key,gemini_api_key). Caller-supplied values are sanitised at the job-store layer on write, so they never reach a response body β relevant becauseGET /api/jobslists every job, not just the caller's. Because it's done on write, any new response builder inherits the protection. - Nothing logs either key. Preserve this if adding logging around the ElevenLabs or Gemini calls.
The /api/* OAuth carve-out
This app's Caddy site block uses authed_app_with_api, exempting /api/* from forward_auth. The rule that keeps that safe:
Every route under
/api/*must authenticate, and must fail closed. A route that doesn't is published to the internet.
/api/ping is the one intentional exception.
Input validation
project_idis validated against^[A-Za-z0-9_-]+$on bothPOST /api/jobsandGET /api/projects/{project_id}. This matters most in this app: its local-file backend builds real filesystem paths from the value (projects/{project_id}/project.json), and at the time the gap was found the container was still running as root. The UI only ever generates IDs via slugify+uuid, so nothing was exploitable through the browser, only through the API.- Webhook URLs are re-resolved and IP-pinned immediately before delivery. Synthesis jobs are long-running, so the window between submission-time validation and actual delivery is wide β the DNS-rebinding gap this closes.
- Uploaded caption and turns files are parsed, not executed.
turns_fileis JSON, parsed withjson.loads; there is no pickle or YAML path anywhere in the upload handling.
Known gaps
- The UI's synthesis handlers bypass the job semaphore. Three separate call sites plus a sweeper thread were out of scope when the API was added. Security-relevant consequence:
/api/draindoes not stop a UI batch already running, andactive_jobsundercounts real work, so "drained" is not a guarantee that nothing is writing. See api.md. HF_TOKENis over-privileged (needs onlytts-glossarywrite). Fix requires a manual change in the HF web UI.- Job state is in-memory. A restart loses queued and running jobs; projects and their audio are durable.
- Single-tenant trust model. Any holder of
API_TOKENcan read every project and job. Fine for one CMS plus the Orchestrator; would need per-caller scoping before wider exposure.