# Security hardening

> Vault secrets, admin 2FA, the pinned vendor key, and limiting exposure.

_Updated: 2026-06-10_

A production Railbase has a handful of things worth getting right. None are
exotic; they're the difference between a demo and a deployment you can trust.

## Protect the vault secrets

Your data is only as safe as two things:

- **The vault password** — supply it via `RAILBASE_VAULT_PASSWORD_FILE` (a file
  mode `0600`, or a secrets-manager mount) rather than a plain env var or a shell
  history. In production, an empty password is refused.
- **The master key** (`pb_data/.secret`) — auto-generated on first run. Guard it
  and back it up separately from the vault file. Losing it means losing access to
  your data; leaking it undermines encryption at rest.

```bash
export RAILBASE_PROD=true
export RAILBASE_VAULT_PASSWORD_FILE=/run/secrets/railbase-vault
```

## Admin accounts

- Admins are created explicitly (`railbase admin create`) — there's no default
  account and no default password.
- **Enroll two-factor authentication** from the admin's account settings (it's
  self-service, not forced). Keep the recovery codes you're shown somewhere
  safe — once enrolled, sign-in requires the 6-digit code.
- Reset a password from the CLI if needed: `railbase admin reset-password <email>`.

## Limit what's exposed

Two surfaces are powerful and should not be open to the world:

- **`/_/`** — the admin console.
- **`/_pm`** — the marketplace / plugin manager (on by default; set
  `RAILBASE_PLUGIN_MANAGER=0` to disable). Its install/update/restore/self-update
  actions require an admin session, but the surface is still worth restricting.

Options, in rough order of preference:

- Put the admin and `/_pm` behind your network boundary (VPN / private network),
  or restrict them at the reverse proxy (IP allow-list, mTLS, or proxy auth).
- Use `RAILBASE_ALLOW_IPS` / `RAILBASE_DENY_IPS` (CIDR lists) to filter at the
  app, and set `RAILBASE_TRUSTED_PROXIES` so those filters see real client IPs.
- Constrain browser origins with `RAILBASE_CORS_ALLOWED_ORIGINS`.

> [!TIP]
> If you don't need the marketplace running all the time, set
> `RAILBASE_PLUGIN_MANAGER=0` during normal operation and enable it only when
> you're installing or updating plugins.

## The plugin trust anchor

Plugins only run if their signature verifies against the **pinned vendor public
key** (see [How plugins work](how-plugins-work)). That pin is what makes
marketplace-only acquisition safe — there's no path to run an unsigned or
unverified binary. Don't disable verification or repoint the pin at an untrusted
key.

## Transport and operations

- **Always terminate TLS** at your reverse proxy; never serve the admin over
  plain HTTP. See [Deployment](deployment).
- Ship logs as JSON (`RAILBASE_LOG_FORMAT=json`) to your log pipeline, and keep an
  eye on the audit log — Railbase maintains a tamper-evident, hash-chained audit
  trail.
- Snapshot before risky changes and keep a copy off-box — [Backups & restore](backups-and-restore).
