Spaces:
Sleeping
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
5.02 kB
| # 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](../../Orchestrator/docs/security.md). 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_TOKEN` needs HuggingFace access scoped to **write on the dataset `GarchenArchive/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 `GarchenArchive` and `khyentsevision` plus write on `BDRC` and `openpecha`, far beyond what this app needs. Full scoping table: [Orchestrator security β the HuggingFace token](../../Orchestrator/docs/security.md#open-gap-the-huggingface-token). | |
| - The same value doubles as the `POST /api/drain` operator 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 `-e` on the command line, so they don't appear in the host's `ps` output. | |
| - 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 because `GET /api/jobs` lists 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_id` is validated** against `^[A-Za-z0-9_-]+$` on both `POST /api/jobs` and `GET /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_file` is JSON, parsed with `json.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/drain` does not stop a UI batch already running, and `active_jobs` undercounts real work, so "drained" is not a guarantee that nothing is writing. See [api.md](api.md#concurrency-the-ui-is-not-on-the-same-queue). | |
| - **`HF_TOKEN` is over-privileged** (needs only `tts-glossary` write). 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_TOKEN` can read every project and job. Fine for one CMS plus the Orchestrator; would need per-caller scoping before wider exposure. | |