Console security
The console is reachable from the internet, so it is locked down tighter than anything else in AutoRune. In short: one Discord account, short revocable sessions, no shell, a fixed list of operations, and an audit row for everything.
Who can get in
- Discord only. OAuth2 with PKCE, scope
identify, the same Discord application as the app. There is no password, no other provider and no fallback: if Discord sign-in isn't configured, or its redirect URI isn't registered, nobody gets in. - One account. The console has its own allowlist of Discord IDs (stable, unlike usernames), today the owner's only. It is checked at sign-in and on every request: removing an ID ends its live sessions at once.
- Refusals are logged. Every denied sign-in lands in the audit log with the Discord ID that tried.
Sessions
- A random 256-bit token in a cookie that is
httpOnly,Secure,SameSite=Strictand__Host-prefixed (this host only, path/, no subdomain can set or read it). - Only a hash of the token is stored, server-side, so every session can be revoked: sign out, "sign out everywhere", or an allowlist change.
- Sessions end after 12 hours whatever happens, and after 2 hours without use.
Requests
- Everything under
/apineeds a session, apart from the sign-in redirect and its callback. Without one every route answers401, unknown routes included. - CSRF: every write needs the session's CSRF token in a header, the console's exact
Originand a same-origin fetch, on top of theSameSite=Strictcookie. - Confirmation: every action needs an explicit
confirmin its body; the app only sends it from the confirm dialog. - Rate limits per address in nginx and in the console API (sign-in much tighter), and per session for actions.
- Headers: a strict Content Security Policy (scripts from the console itself only, no framing, no forms, no other origins), HSTS,
nosniff, no referrer, a restrictive permissions policy andnoindexon everything (plus arobots.txtthat disallows all).
What it can do on the server
- Its own system user. The console API runs as a dedicated user with no shell and no home, not as the account that maintains the server. It can't read the repository, the other services' secrets or anyone's home directory, and its systemd unit is sandboxed (read-only system, private
/tmp, no kernel tunables). - A fixed list of root commands. Its only root rights are a sudoers drop-in with exact command lines:
systemctl start|stop|restartfor the allowlisted units andjournalctl -u <unit>with fixed flags for reading logs. The drop-in is generated from the same code that builds the commands, checked withvisudoand compared against the committed copy on every deploy. Any other command, unit or flag is refused by sudo itself. - No shell. Commands are started as fixed argument lists, never through a shell, and a unit name must be on the registry's list before anything runs. The console can't stop or restart itself.
- Reading data: each environment's database through a read-only role that can only see the columns the console shows (no token hashes, no encrypted keys or game passwords), with read-only transactions and a statement timeout.
- Changing an environment: through that environment's admin API, a separate listener of the environment's own API that is bound to loopback, never proxied by nginx, and requires a shared secret known only to the console. It can list avatars, start and stop them, and lock or unlock accounts. Nothing else.
Metrics
Netdata listens on loopback only. The console's Metrics page asks the console API for a fixed set of charts. The full Netdata dashboard is served under /netdata/ only after nginx has checked the console session; without one it answers 401. The dashboard gets its own CSP that lets it talk to this site only, so it never contacts Netdata Cloud. Netdata telemetry is off.
Known trade-off: the Netdata dashboard runs on the console's own origin, so a flaw in it could act with the console session. It only runs when the operator opens it. Moving it to its own hostname is planned.
Audit log
Every sign-in (allowed, refused or failed), sign-out and action is written before the response goes out: who (Discord ID and name), what, which environment, target, result (ok, error, denied), details (for allowlist edits: what was added and removed) and the client address. The log lives with the console's sessions in its private state directory and is shown in the console.