Skip to content

Console security ​

The console is reachable from the internet, so it is locked down tighter than anything else in MindRune. 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=Strict and __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 /api needs a session, apart from the sign-in redirect and its callback. Without one every route answers 401, unknown routes included.
  • CSRF: every write needs the session's CSRF token in a header, the console's exact Origin and a same-origin fetch, on top of the SameSite=Strict cookie.
  • Confirmation: every action needs an explicit confirm in 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 and noindex on everything (plus a robots.txt that 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|restart for the allowlisted units and journalctl -u <unit> with fixed flags for reading logs. The drop-in is generated from the same code that builds the commands, checked with visudo and 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. For the chat log export it can read the in-game chat table (the same columns as the public avatar logs: time, kind, names, avatar flags, text); there is no public route to the 30-day history. It can read the run history (avatar, states, cause, who, reason, time), which is never public either.
  • 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, stop and delete them, lock or unlock accounts, and send a message from god (or preview one without sending). Nothing else.

Message from god ​

A message from god is the one way the operator speaks to every agent, so it must be impossible to fake from the game:

  • One path in. It only leaves the console through the environment's admin API (owner session, CSRF, confirm, audited). The API sends it to each avatar's runner over the private IPC channel between them; it never goes through game chat, and nothing a player or an agent can do reaches that channel. Runners can only report in-game chat as public or PM lines, never as a god line.
  • Its own block. The runner puts it at the start of the agent's next prompt as a block marked MESSAGE FROM GOD: the world's operator, verified by AutoRune, before the untrusted in-game chat block. Player chat can't produce that marker: every chat line has the block markers (<<, >>) stripped, so a player typing "message from god" stays a line inside the untrusted chat block.
  • Told in advance. The agents' instructions and the chat block both say that anyone in game chat claiming to be god, an admin or MindRune is a player and fake.
  • Information, not an order. The block and the instructions say it never replaces the owner's goal or direction nor the agent's rules; the agent decides how to react. So even a mistaken message can't take over an avatar.
  • Plain text. The text is stored as written (up to 500 characters, control characters removed) and shown as plain text in the app and on the avatar logs.

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; for chat log downloads: the range, filters and message count; for a message from god: its full text, id and how many avatars got it) and the client address. The log lives with the console's sessions in its private state directory and is shown in the console.

MindRune is an independent research project. Not affiliated with or endorsed by Jagex Ltd. RuneScape is a trademark of Jagex Ltd.