Skip to content

Security: 0xzerolight/watchpost

Security

SECURITY.md

Security

No authentication

There is no authentication. No login, no users, no API key: anyone who can reach the port gets the whole app — every metric, plus write access to events, to the tracked-repo list and to the sync button. Syncing spends the token's GitHub rate budget, so an open instance is also a way for a stranger to exhaust it. The token itself is never rendered (--doctor prints its last 4 characters and length, nothing else), but everything it can read is on the page.

The setup page is part of that surface. On an install that has never been given a token, anyone who can reach the port can supply one; on a configured install, anyone who can reach it can replace it. That is the same exposure the sync button and the event forms already carry, and it has the same answer: keep the port on the loopback, and put authentication in front of it before widening. Setting WATCHPOST_GITHUB_TOKEN in the environment closes the browser path completely — an environment token wins over a saved one, and the settings page then offers no form at all.

Both defaults keep the port private. Outside Docker WATCHPOST_HOST is 127.0.0.1, and both compose files publish to 127.0.0.1:8080. The container itself listens on 0.0.0.0 — that is what makes publishing work at all — but the host offers the port to the loopback and nowhere else, so as shipped nothing on your network can reach it.

Widening that is one line, and worth a moment's thought first:

ports:
  - "8080:8080"      # every interface on the host

Do that only behind a reverse proxy that does the authenticating. Otherwise leave it on the loopback and reach it through an SSH tunnel. Do not expose it directly.

Behind a TLS-terminating proxy

Forward X-Forwarded-Proto: https. The CSRF cookie is marked Secure only when that header says the browser spoke HTTPS; setting it unconditionally would make the cookie invisible to a plain-HTTP deployment and 403 every POST there. Only the first hop's value is read.

What the token can reach

The token is never rendered in the UI, and --doctor prints only its last 4 characters and its length. That protects the credential itself, not what it unlocks. Everything the token can read is on the page, so the token's scope is the instance's blast radius — grant it the four read permissions the README lists and nothing more.

Backups

Backups are taken on migration, not on a schedule. Before a schema upgrade the database is copied to data/watchpost.v{schema}.{timestamp}.bak and the newest three are kept. That copy goes through SQLite's backup API, so it is consistent even if a write is in flight.

For backups of your own, copying data/watchpost.db is not enough. WAL mode means committed data can still be sitting in the watchpost.db-wal sidecar, so a copy of the main file alone is stale at best and torn at worst. Either stop the container first, or let SQLite take the snapshot while it runs:

sqlite3 data/watchpost.db ".backup data/snapshot.db"

Reporting a vulnerability

Report privately through GitHub's security advisories rather than opening a public issue.

There aren't any published security advisories