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 setSHOW_ADMIN_TOKEN_ON_STARTUP=trueif 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.
cd ~/better-paas/backend && ./server tokenTreat 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 MCPRestart 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_ORIGINis 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.