Better-PaaS

Security

How Better-PaaS protects your dashboard, your secrets, and your server.

Better-PaaS is built so the secure path is the default path. You don't have to be a security expert to run it safely. This page explains how it protects you and the few things you should do yourself.

The admin token

The entire control plane is protected by a single admin bearer token.

  • On first run, Better-PaaS generates a strong random token, prints it to the logs, and saves it to backend/data/admin_token.txt.
  • On later starts, Better-PaaS does not print an existing token by default. Retrieve it with ./server token, or set SHOW_ADMIN_TOKEN_ON_STARTUP=true if your deployment process intentionally depends on startup logs.
  • You paste it into the dashboard sign-in screen to log in.
  • Every privileged API request must carry it as Authorization: Bearer <token>.
  • Browser WebSocket sessions first request a short-lived, one-use ticket from /api/auth/ws-ticket, then connect with that ticket instead of putting the long-lived admin token in the WebSocket URL.
  • The dashboard stores the token in browser session storage, so closing the browser session signs you out and limits how long a future browser bug could expose the token.

To pin or rotate the token, set ADMIN_TOKEN in the backend environment.

Reprint the token any time
cd ~/better-paas/backend && ./server token

Treat the token like a password

Anyone with the token has full control of your Better-PaaS instance. Don't commit it, paste it into chats, or put it in client-side code.

Agent tokens (scoped access)

Instead of sharing the admin token with every tool and teammate, create agent tokens - scoped, revocable credentials for machines and automation.

  • Scoped: Each token is limited to specific actions (e.g. read logs only, or deploy but not delete).
  • Revocable: Delete an agent instantly without rotating your admin password.
  • Auditable: Every agent action is recorded in the audit log with its name.
  • Free: Create as many as you need.

Agent tokens are prefixed with bpagt_ and are validated via SHA-256 hash - only the hash is stored in the database.

Agent tokens are the recommended way to authenticate CI/CD pipelines, AI coding assistants, monitoring scripts, and team members who only need read access.

See Agent Access & Audit Logs for how to create, rotate, and manage them. The fastest path is the paas CLI:

paas connect <dashboard-url>   # authorize in browser
paas setup                     # configure Cursor / Claude Code MCP

Restart your editor after paas setup. Your admin token is never saved on your laptop - only a scoped bpagt_ agent token in ~/.paas/config.json.

Audit logs

Every mutating API request is recorded: who (or what) made it, what action was taken, what resource was affected, and when. This includes both admin actions and all agent token actions.

View logs via the API:

curl -H "Authorization: Bearer <admin_token>" \
  "http://localhost:8080/api/audit-logs?limit=50"

The audit trail is stored in your SQLite database and has no automatic pruning yet. Review it regularly, especially after granting new agent tokens.

Brute-force protection

Could an attacker just guess the token? No. Repeated bad tokens from an IP trigger an escalating lockout - the server responds with HTTP 429 and a Retry-After header, slowing guesses to a crawl. Combined with a 256-bit token, guessing is infeasible.

If the API is reachable only through a trusted reverse proxy, set TRUST_PROXY=true so lockouts key on the real client IP instead of the proxy's. Leave it disabled when port 8080 is directly exposed, otherwise clients can spoof forwarded IP headers.

Remote server SSH

When you connect a remote server, Better-PaaS pins that server's SSH host key fingerprint on first successful connection and verifies it on later SSH operations. If the remote host key changes unexpectedly, Better-PaaS rejects the connection instead of silently trusting a possible man-in-the-middle.

Encryption at rest

Sensitive values are encrypted before they're written to the database using AES-256-GCM:

  • Per-app deploy tokens (gitToken)
  • Your saved GitHub token

The encryption key comes from BETTER_PAAS_SECRET_KEY, or is generated on first run at backend/data/secret.key (mode 0600).

This protects leaked database copies - an accidental commit, a stolen backup, an old snapshot. Existing cleartext values are read transparently and upgraded to ciphertext on the next write.

For stronger protection

By default the key lives next to the database, so encryption at rest doesn't stop an attacker who can already read the entire data directory. For defense against that, supply BETTER_PAAS_SECRET_KEY out-of-band (a secrets manager or systemd credential) and keep it off the host.

Network exposure

By default the API binds to all interfaces so the dashboard is reachable from a remote browser. Because every route requires the admin token, this is safe to start with.

For extra hardening, put Better-PaaS behind a reverse proxy and set LISTEN_ADDR to loopback (127.0.0.1:8080) so the API isn't directly exposed.

Origin checks

CORS and browser WebSocket upgrades are restricted by origin:

  • If DASHBOARD_ORIGIN is set, only those comma-separated origins are allowed.
  • If it isn't set, Better-PaaS allows the same hostname on any port. This keeps the default dashboard (:3000) and API (:8080) setup working without reflecting arbitrary third-party origins.

Set DASHBOARD_ORIGIN when the dashboard and API use different hostnames, for example https://paas.example.com and https://api.example.com.

The data directory

backend/data/ holds your SQLite database, admin token, encryption key, and logs. It's created with 0700 permissions and is gitignored.

Never commit backend/data/

It contains secrets. It's already in .gitignore - keep it that way.

Your security checklist

A short list of things worth doing on a production instance:

Keep the admin token secret and rotate it if you suspect exposure.

Open only the ports you need on your server's firewall (typically 80, 443, and your SSH port).

Set BETTER_PAAS_SECRET_KEY out-of-band for stronger encryption at rest.

Set DASHBOARD_ORIGIN if the dashboard and API are served from different hostnames.

Set TRUST_PROXY=true if you run behind a reverse proxy.

Keep backups off-server so a lost server doesn't mean lost data.

Keep Better-PaaS updated - see Updates.

Next step

On this page