SECURITY.md
Rama main · réplica de lectura del repositorio original.
# Security
## Reporting a vulnerability
Please report security issues privately through
[GitHub's private vulnerability reporting](https://github.com/rancur/fade-out/security/advisories/new)
rather than opening a public issue.
Expect an initial response within a few days. This is a hobby project maintained
by one person — please size your expectations accordingly.
## The single most important thing to know
**fade-out has no authentication. Do not expose it directly to the internet.**
There is no login, no API key, no session — every endpoint is reachable by
anyone who can reach the port. That includes endpoints that read and write
stored credentials:
- `GET /api/auth/credentials` lists which credentials are configured and returns
masked values (first 6 and last 4 characters).
- `PUT /api/auth/credentials` overwrites stored credentials.
- `POST /api/mixes/{id}/upload` and the pipeline endpoints publish to your real
SoundCloud and YouTube accounts.
This is a deliberate design point, not an oversight: fade-out is built as a
single-user tool on a trusted home network. But it means the deployment model is
your only security boundary.
### Deploy it behind something
Safe:
- Bound to a LAN the app can trust, reachable only from inside it.
- Behind a reverse proxy that enforces authentication (Authelia, oauth2-proxy,
Cloudflare Access, basic auth).
- On a private overlay network such as Tailscale or WireGuard.
Not safe:
- Port-forwarded straight from your router.
- A Cloudflare Tunnel with no Access policy in front of it.
- Any public hostname that resolves to the container without an auth layer.
### The OAuth callback exception
YouTube's OAuth flow requires a real, publicly resolvable domain, which is why
`PUBLIC_URL` exists. Publishing a hostname for the OAuth callback does **not**
mean publishing the dashboard: put the auth layer in front of everything, and if
your proxy needs an exception for the callback, scope it to the
`/api/auth/*/callback` paths only.
### CORS
`CORS_ALLOW_ORIGINS` defaults to the local Vite dev-server origins, with
`PUBLIC_URL` folded in automatically. The dashboard is served from the same
origin as the API, so most deployments need no additional entry.
Setting it to `*` is strongly discouraged. With no authentication in front of
the API, a wildcard lets any website you happen to visit read your stored
credential metadata and drive your pipeline from your own browser. If `*` is
set, credentialed CORS is disabled automatically, but the request-forgery
exposure remains.
## Secrets
- Secrets come from the environment or the settings database. Nothing is
committed — `.env` is gitignored, and `gitleaks` runs as a pre-commit hook
(`.pre-commit-config.yaml`).
- Credential values are masked on read, never returned in full.
- `GITHUB_TOKEN` is only ever used to read release and tag metadata for the
deployment-freshness check. Use a fine-grained token scoped to this one
repository with *Contents: Read-only*, and nothing else.
## Container
The image runs as root so that bind-mounted NAS shares keep working out of the
box across the many UID/GID setups people deploy on. If your mounts permit it,
running unprivileged is better — uncomment the `user:` line in
`docker-compose.yml` and set it to a UID/GID that can read the watch folders and
write the output folders.
## Automation
The workflows under `.github/workflows/` run Claude with write access to this
repository. `claude.yml` is gated on `author_association`, so only the owner,
org members, and invited collaborators can trigger it — pull requests from forks
do not receive repository secrets. Please keep that gate in place in any PR that
touches those files.