Security check & secrets
Two small tools that keep a pod safe to run and safe to commit: orbiter doctor finds risky setups, and ORBITER_SECRET encrypts stored credentials.
orbiter doctor
orbiter doctor [pod-path]
Reads the pod and reports what to fix. Exit code 1 if anything failed, so it works in CI. Admins also see the same findings in the Security Check card on the dashboard.
| Check | Level |
|---|---|
| Pod file is tracked by git (it contains accounts, password hashes and live sessions) | fail |
| No admin account | fail |
| Credentials stored in plaintext (FTP, SMTP, AI, GitHub, S3) | warn |
| Content API enabled with no token and no required key | warn |
| Plaintext API token or API keys (older versions) | warn |
| Many admins or many live sessions | warn |
Encrypted secrets
Set ORBITER_SECRET to a long random string in the environment of the admin server and restart it:
ORBITER_SECRET="$(openssl rand -base64 32)" npm run admin
On startup the admin encrypts every stored credential in place (AES-256-GCM, key derived with scrypt, each value bound to its setting name). Reading and saving settings works as before. The pod can then be committed or backed up without carrying usable credentials.
ORBITER_SECRET the encrypted values can't be read — the settings show as empty, and saving an empty field will not overwrite them. Store the secret in your password manager or hosting provider's environment settings, not in the repository. If you don't set it, nothing changes: credentials stay plaintext.Covered: GitHub tokens, AI API key, S3 keys, SMTP password, FTP password. API tokens and API keys are stored as hashes instead. Passwords of user accounts are always hashed (scrypt).
Two-factor sign-in (TOTP)
Account → Two-factor authentication: confirm your password, add the shown setup key to an authenticator app (Google Authenticator, 1Password, Aegis …) and enter a code to switch it on. You get eight one-time recovery codes — save them, they are shown once. From then on sign-in asks for the 6-digit code after the password; a code can't be used twice. Turning 2FA on signs out your other devices.
Lost your phone and your recovery codes? An admin can reset your 2FA from Users (this also signs you out everywhere). The TOTP secret is encrypted at rest when ORBITER_SECRET is set.
Active sessions
Account → Active sessions lists every device that is signed in (browser, IP, time). Sign out a single device or all others. A login session lasts 30 days.
API key limits
When you create a key (Settings → API Keys) you can limit it to certain collections (for the REST API and MCP, reads and draft writes), give it an expiry date (an expired key is rejected with 401), and a rate limit in requests per minute (over it: 429). The rate limit is counted in memory per server process.
Browser hardening of the admin
The admin sends X-Frame-Options, frame-ancestors 'self', nosniff, a same-origin referrer policy and forbids plugins and foreign form targets. A strict script policy (a per-request nonce on every script) is running in report-only mode: nothing is blocked yet, and admins can see what a strict policy would have blocked at GET /api/security-check/csp-reports. Once the remaining inline event handlers are converted, the policy is switched to enforcing.
Draft-only API keys for agents
An API key can optionally be created with drafts allowed (Settings → API Keys). Over MCP such a key gets two extra tools, create_draft and update_draft. They can only create new entries as drafts and edit entries that are still drafts. They can't publish, schedule, delete, or change an entry that is already live — a human publishes in the admin. Every change shows up in the entry's activity log as mcp:<key label>.