SECURITY.md
Rama main · réplica de lectura del repositorio original.
# Security Policy
## Reporting a Vulnerability
Report privately via GitHub's [Report a vulnerability](https://github.com/0xzerolight/topic_watch/security/advisories/new) (the repo's **Security** tab → **Report a vulnerability**). The report stays private until a fix ships. For non-sensitive issues, open a [GitHub Issue](https://github.com/0xzerolight/topic_watch/issues/new) labeled `security`.
Include: steps to reproduce, potential impact, and any suggested fix.
## Scope
**In scope:**
- Vulnerabilities in the application code (`app/`)
- Dependency vulnerabilities that affect Topic Watch
- Docker / docker-compose configuration issues
- CSRF or injection issues in the web UI
**Out of scope** (report to the relevant project instead):
- Your LLM provider (OpenAI, Anthropic, etc.)
- Apprise notification services ([Apprise project](https://github.com/caronc/apprise))
- Your reverse proxy or hosting configuration
## Why No Built-in Authentication?
Topic Watch is a single-user, self-hosted tool. Built-in auth would mean managing users, passwords, and sessions — complexity that doesn't fit one user. Safe as-is on localhost.
For remote deployment, put it behind a reverse proxy with your auth layer (Authelia, Caddy basicauth, Nginx basic auth) — see [Reverse Proxy Auth Examples](#reverse-proxy-auth-examples) below.
## Deployment Security
When deploying Topic Watch on a public network:
- **TLS is required.** Terminate TLS at your reverse proxy (Caddy, Nginx, Traefik) before forwarding to Topic Watch. Without TLS, CSRF tokens and session cookies are transmitted in plaintext.
- **Enable secure cookies.** Set `TOPIC_WATCH_SECURE_COOKIES=true` (or `secure_cookies: true` in `data/config.yml`) so cookies are only sent over HTTPS connections.
- **Declare your hostname.** Topic Watch answers only to `localhost`, `*.localhost`, `*.local` and IP addresses; anything else gets a 400. That stops a hostile site from re-pointing its own domain at this machine and driving the console from your browser. When a reverse proxy forwards a hostname, list it: `TOPIC_WATCH_ALLOWED_HOSTS=topic-watch.example.com` (comma-separated for several; `*.example.com` covers the subdomains and the apex; `*` turns the check off).
- **Restrict network access.** Both compose files publish the port on `127.0.0.1` by default, so a stock install is reachable only from the host. To reach it from other devices, set `TOPIC_WATCH_BIND_ADDR=0.0.0.0` in `.env` — and put an authenticating reverse proxy in front before you do. Do not expose port 8000 directly to the internet.
Note that this is not something a host firewall will save you from: Docker inserts its own iptables rules ahead of the ones `ufw` and `firewalld` manage, so a published port stays reachable even when the firewall is configured to deny incoming traffic. The bind address is the control that actually applies.
- **Protect `data/config.yml`.** This file contains your LLM API key. Ensure it is not world-readable (`chmod 600 data/config.yml`). Saving from the Settings page applies that itself: the file is written owner-only, and a permissive mode is tightened rather than carried forward.
- **Keep dependencies updated.** `requirements.txt` is a hash-pinned lockfile (exact `==` versions plus `--hash` entries), so `pip install --upgrade -r requirements.txt` cannot raise versions — it is a no-op for upgrades. Updates land through the configured Dependabot PRs; to bump versions locally, run `make lock-tools` (installs the pinned `pip-compile` toolchain — use a throwaway venv, it pins pip to 25.1.1), then `make lock-upgrade` and reinstall. Plain `make lock` reproduces the current pins and will not raise versions.
- **Use the Docker image.** It runs as a non-root user with resource limits.
### Reverse Proxy Auth Examples
#### Caddy
```
topic-watch.example.com {
basicauth {
admin $2a$14$YOUR_HASHED_PASSWORD
}
reverse_proxy localhost:8000
}
```
Generate hash: `caddy hash-password`
#### Nginx
```nginx
server {
listen 443 ssl;
server_name topic-watch.example.com;
auth_basic "Topic Watch";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
Create credentials: `htpasswd -c /etc/nginx/.htpasswd admin`
## Install Script Trust
The one-line installers (`scripts/install.sh`, `scripts/install.ps1`) are meant to be run with `curl | bash` / `irm | iex`. That convenience carries the usual trade-off: your shell executes whatever the URL returns, and by default the scripts fetch the `docker-compose` file (which selects the container image) from the same source. Both are pulled from the **mutable `main` branch** with no commit pin, tag, signature, or checksum, so a repository/branch compromise or a man-in-the-middle proxy could run arbitrary code as the invoking user.
To reduce that trust before running either installer:
- **Read it first.** Download the script and review it, or run it from a local checkout, instead of piping straight to a shell.
- **Pin a ref.** Set `TOPIC_WATCH_REF` to a release tag or commit SHA and fetch the installer from that same ref. This pins both the installer and the `docker-compose` file it downloads:
```bash
# Linux / macOS
TOPIC_WATCH_REF=v1.1.2 curl -fsSL \
https://raw.githubusercontent.com/0xzerolight/topic_watch/v1.1.2/scripts/install.sh | bash
```
```powershell
# Windows (PowerShell)
$env:TOPIC_WATCH_REF="v1.1.2"
irm https://raw.githubusercontent.com/0xzerolight/topic_watch/v1.1.2/scripts/install.ps1 | iex
```
### Autostart persistence
The installers can set up boot/login autostart (a systemd user service + `loginctl enable-linger` on Linux; a Startup-folder shortcut on Windows). This is **opt-in**: the installer prompts when run interactively and skips autostart in a non-interactive piped run unless you pass `TOPIC_WATCH_AUTOSTART=yes`. The closing summary echoes the exact uninstall commands.
To remove autostart later:
```bash
# Linux
systemctl --user disable --now topic-watch
rm -f ~/.config/systemd/user/topic-watch.service
loginctl disable-linger "$USER"
```
```powershell
# Windows — delete the Startup-folder shortcut
Remove-Item "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\Topic Watch.lnk"
```
## Known Limitations
- **SSRF / DNS rebinding (TOCTOU).** Feed and webhook URLs are validated against private/reserved addresses, including a DNS-resolution layer that now fails closed (an unresolvable host is blocked). Redirects are re-validated per hop. However, validation resolves DNS at check time while httpx re-resolves at connect time, leaving a narrow rebinding window between validation and fetch. Eliminating it would require a pinned-IP connect transport that risks breaking HTTPS feed fetching (SNI / cert verification), so it is an accepted limitation for this single-user self-hosted tool.
## JSON API
The `/api/v1/` JSON API endpoints are unauthenticated, the same as the web UI. GET endpoints provide read access to all topic data including knowledge states. The single mutation endpoint (`POST /api/v1/topics/{id}/check`) is protected by CSRF.
If you expose Topic Watch to a network, apply the same reverse proxy authentication to API endpoints as you do to the web UI.