Security model
Wharfbase holds the keys to your databases and talks to Docker, so it is built to give away as little as possible if something goes wrong.
Docker: through the socket-guard only
Section titled “Docker: through the socket-guard only”Wharfbase never touches the Docker socket. Socket-guard holds it and forwards an allowlist of read calls and a few narrow actions:
- joining networks, for Wharfbase itself only;
- reading files from containers;
- optionally, helper containers from official database images, without host access;
- optionally, relays: with network attach set to Relays, Wharfbase joins no database network, and a relay container of its own image forwards one port per database. See Relays.
Even a compromised Wharfbase can’t start a privileged container or touch another container. On Unraid, which runs single containers, this layer is missing, and the template says so.
Kubernetes: a read-only service account
Section titled “Kubernetes: a read-only service account”In a cluster, Wharfbase runs without Docker, as uid 10001 with a read-only root filesystem and no capabilities. Its roles only read; the one exception is get on Secrets, by name, for “Use the password from the pod”, audited and optional. See Kubernetes.
The containers
Section titled “The containers”- Wharfbase:
- non-root (uid 10001), with a read-only root filesystem;
- every capability dropped,
no-new-privileges; - one writable volume,
/data.
- The host scan, when you turn it on:
- mounts
/read-only; - adds one capability, to read root-only files.
- mounts
- Socket-guard:
- a distroless image with a read-only filesystem and no capabilities;
- on an internal network shared with Wharfbase alone.
- Images:
- multi-arch, on Docker Hub, signed with cosign (verify with
cosign verify --key https://fleeq.net/wharfbase/cosign.pub docker.io/wharfbase/wharfbase:<version>); - shipped with an SBOM, and scanned in the release pipeline.
- multi-arch, on Docker Hub, signed with cosign (verify with
Secrets
Section titled “Secrets”- Database passwords and other stored secrets (the SSO client secret, the audit stream’s secret) are encrypted with AES-256-GCM.
- Each value is bound to its row, so copying a value to another row fails.
- The master key is 32 random bytes in
secrets/master.key(mode 0400, outside the data volume), orWB_MASTER_KEY_FILE, orWB_MASTER_KEY. wb keys rotatere-encrypts everything with a new key in one transaction.
- On a PostgreSQL store (
WB_STORE_URL) the secrets reach PostgreSQL already encrypted: a dump of it without the master key holds no readable password. Give the URL, with its password, as a secret file (WB_STORE_URL_FILE); Wharfbase never logs it. - A wrong or damaged master key stops Wharfbase from starting. It never creates a new key over an existing data folder, and it touches nothing. Back up the key separately from the data.
- Passwords of Wharfbase accounts are hashed with argon2id.
- Sessions and API tokens are random 256-bit values; only their SHA-256 is stored.
- Secret values in containers’ environments (for example
POSTGRES_PASSWORD) are only read when you ask for a credential hint, and that is audited.WB_SECRET_HINTS=offturns hints off.
Who can do what
Section titled “Who can do what”- Roles: viewer, editor, admin, owner. Viewers only read.
- Per-database permissions (Pro): none, read, write or admin, for each person or role.
- Production databases (by label or tag) open read-only. Writing needs an unlock, which is audited and time-limited, or an approval (Team).
- API tokens act as you, capped by their scope.
- Two-factor sign-in (TOTP) with recovery codes. Admins can require it of every admin.
- SSO (Team): any OpenID Connect provider, with PKCE.
- The audit log is hash-chained, so changes to past entries show up (
wb audit verify). It records sign-ins, connections, scripts (their first 200 characters), exports, unlocks, permission changes and settings.
Read-only means read-only
Section titled “Read-only means read-only”A read-only connection is enforced by the database where the engine allows it:
- a
READ ONLYtransaction on PostgreSQL and CockroachDB; - a read-only session on MySQL and MariaDB;
- a read-only file on SQLite;
readonlyrequests on ClickHouse;- READ transactions on Neo4j.
On top of that, Wharfbase checks every statement, reading it the way the server will, including comments that a server runs or nests, and other ways to switch the mode off. On PostgreSQL and MySQL it also checks after each statement that the session is still read-only, and stops the script if not.
On SQL Server and Oracle, scripts run in a transaction that is rolled back, and statements that write or end the transaction are refused. A function that already exists and writes in its own transaction still looks like a read there. For a hard guarantee, connect with a user that only has read grants: each engine’s page shows one.
The web app
Section titled “The web app”- Cookies:
HttpOnlyandSameSite=Strict, plusSecurebehind HTTPS. - Requests: a CSRF token on every change.
- Headers:
- a strict Content-Security-Policy, no framing,
nosniff,no-referrer; Cache-Control: no-storeon API responses;- HSTS when the public URL is HTTPS.
- a strict Content-Security-Policy, no framing,
- Sign-in:
- a rate limit on attempts;
- a 15-minute lockout after 10 failures;
- the same answer, and the same time, for an unknown account as for a wrong password.
How it is checked
Section titled “How it is checked”- The review: an OWASP ASVS Level 2 review, with its findings and fixes.
- Fuzz tests: they run in CI and nightly, a million iterations per property:
- the filter compilers of every engine;
- the SQL splitter and the read-only checks;
- the MongoDB shell parser;
- the REST console parser;
- the socket-guard.
- Dependency audits:
pnpm auditandgovulncheckin CI, and Dependabot. - Reporting a vulnerability: see SECURITY.md. Reports are kept private for 90 days.