Reference
Security
Astrodock is built for one trusted operator on a single-tenant box — your own
machine or a VPS you control. It is powerful by design, with a few deliberately sharp edges.
This page explains the trust model in plain terms; the complete threat model lives in
SECURITY.md in the repo.
Read this before going public
Astrodock is not multi-tenant SaaS. It must not be exposed to untrusted users or untrusted networks without extra hardening. For the full, authoritative threat model and the remaining known limitations, read SECURITY.md in the repository.
The trust model
The whole design assumes a single box run by a trusted person:
- One box, one trusted operator. You — and any admins you create — are fully trusted. Admins are all equally privileged (there's no multi-admin RBAC yet).
- End-users are untrusted, and isolated. The people who log into your
apps (via
/verify) never touch the control plane. They only exist at app-level auth. - A deployed app runs arbitrary code — on purpose. The platform builds and runs whatever a connected repo contains: its dependencies, its build command, its runtime. That's the feature. It's also why you should connect only repos and apps you trust.
What protects you
Within that model, Astrodock has real protections:
- Secrets encrypted at rest. When
ASTRODOCK_SECRET_KEYis set, app secrets, app JWT secrets, internal DB passwords, webhook secrets, and secret env values are encrypted with AES-256-GCM. Set this key — without it secrets are stored in plaintext and the control plane warns at boot. - The build runs unprivileged, with no platform secrets.
npm, postinstall scripts, and yourbuildCommandrun as the app's own non-root user with a scrubbed environment containing only that app's injected vars — never the platform stack secrets (the secret key, the Postgres superuser password, the object-store master key, the admin JWT secret, the runner token, or the GitHub PAT). - Per-app isolation. Each app gets its own non-root OS user (with
600-permission env files so apps can't read each other's secrets), its own Postgres role with
CONNECTrevoked from others (including the control-plane DB), and its own scoped object-storage key limited to its own bucket. - The runner is isolated. The container holding the Docker socket and the GitHub PAT is separate from the control plane, reachable only on the internal network and token-authed. See Architecture for the api/runner split.
- Scoped API tokens. Tokens (
tk_…) are SHA-256-hashed at rest, shown once, and can manage apps/deploys/env but cannot manage users or other tokens. They can be restricted to specific app slugs. End-user passwords are bcrypt;/verifyis rate-limited.
The deliberate sharp edges
These are intentional, accepted under the trusted-operator model — stated plainly so you can decide with eyes open:
The runner builds and runs your code
Deploys run npm ci (which can run postinstall scripts), your
buildCommand, and your app on the runner. Connect only repos you trust.
Dockerfile apps use a mounted Docker socket
runtime.type: "docker" builds and runs sibling containers via
/var/run/docker.sock, which is ≈ root on the host. Put only
trusted Dockerfile apps on the box.
There is no shell into your apps
Earlier versions had a per-app terminal. It ran commands inside the control plane — the one
container holding the key that decrypts every app's secrets — so it has been removed rather
than fixed. The Operations tab replaces it: browse the deployed files, see
which configuration reached the running process, and run the commands the app declares in its
app.json. Those are code you committed, not a string composed at runtime.
Cloud metadata and your apps
Cloud providers serve information about the machine over HTTP at
169.254.169.254. Containers can usually reach it, because that traffic routes out
through the host — which means an app you deploy can read it too.
What that discloses depends on the provider. On DigitalOcean it's the droplet's user-data (including anything you put in an initialization script), its SSH public keys and its tags. On AWS the same endpoint hands out IAM role credentials, which is considerably worse. Since Astrodock's entire job is running code you or an AI agent wrote on this box, it's worth closing.
Astrodock checks this for you — Settings → Readiness probes it from a throwaway container and warns if it's reachable. It does not block it automatically, for the same reason it doesn't manage your firewall: the fix means writing host firewall rules that can collide with your own and are awkward to undo on a remote machine.
To close it:
iptables -I DOCKER-USER -d 169.254.169.254 -j DROP
It has to go in the DOCKER-USER chain — Docker inserts its own rules ahead of the
usual ones, so a plain ufw or INPUT rule won't take effect. Make it
persist across reboots with iptables-persistent, or your distribution's equivalent.
If you use the metadata service yourself
Some setups legitimately read metadata from within containers. If yours does, scope the rule to the app containers rather than dropping it wholesale.
Operator checklist
A short list to get the protections that aren't automatic:
-
Set the strong secrets
Set unique, random values for
ASTRODOCK_ADMIN_JWT_SECRET,ASTRODOCK_SECRET_KEY, andASTRODOCK_RUNNER_TOKEN(plus the Postgres and object-store passwords and a strong admin password). Generate each withopenssl rand -hex 32. Never commit.env. -
Firewall everything except 80/443
Caddy's
80and443are the only ports that should be public. Don't publish Postgres, the object store, the runner, or Caddy's admin API (:2019). -
Only trust your own repos
Connect only repos you control. Review the
buildCommandand dependencies of anything you deploy — the build and runtime run real code. -
Keep the terminal off
-
Back up your data
Back up the
pgdataandobjectdatavolumes. See Backups & restore.
The complete threat model
This page is the plain-language overview. The full threat model — including the remaining known limitations (the runner container itself is privileged; Dockerfile apps share one network with internal services; no multi-admin RBAC) and how to report a vulnerability — is in SECURITY.md in the repository. Read it before exposing the box to anything you don't fully trust.
Related
- How the pieces (and the trust boundary) fit: Architecture
- Mint and scope tokens: API tokens
- Where secrets live and how they're set: Secrets & env vars