Socket guard
Access to the Docker socket is access to the host: whoever can call the Docker API can start a privileged container. So Wharfbase never gets the socket. It talks to socket-guard, a small Go proxy in its own container that holds the socket. The guard forwards only the calls on its allowlist, and refuses everything else with a 403.
The guard is about 800 lines of Go with no dependencies besides the standard library.
- Its container: a distroless image with a read-only filesystem, every capability dropped and
no-new-privileges. - Its network: internal only, shared with Wharfbase alone.
- Its user: root, because the socket’s group differs from host to host. To run it unprivileged, give it a user and
group_addwith your Docker group’s id. - Its tests: they run with the race detector, and a fuzzer checks that whatever the guard lets through is canonical (see Security model).
The allowlist
Section titled “The allowlist”A /v1.xx version prefix is allowed on every path. Paths with percent-encoded characters, characters other than letters, digits and /._:@-, empty segments, or ./.. segments are refused before matching.
| Method | Path | Notes |
|---|---|---|
| GET, HEAD | /_ping, /version, /info, /system/df |
|
| GET | /containers/json, /containers/{id}/json |
List and inspect containers |
| GET | /images/{id}/json |
Inspect an image |
| GET | /networks, /networks/{id} |
List and inspect networks |
| GET | /volumes, /volumes/{name} |
List and inspect volumes |
| GET | /events |
The event stream |
| GET, HEAD | /containers/{id}/archive |
Read a file from a container: SQLite snapshots and the SQLite scan. GUARD_ALLOW_ARCHIVE |
| POST | /networks/{id}/connect, /networks/{id}/disconnect |
Only for Wharfbase’s own container, and never for networks labelled wharfbase.attach=false. Joining needs GUARD_ALLOW_NETWORK_ATTACH, except the relay network with GUARD_ALLOW_RELAY; leaving is always allowed |
| POST | /networks/create |
Only the relay network wharfbase_relay, always an internal bridge. GUARD_ALLOW_RELAY |
| POST | /containers/create |
Short-lived helpers (backups, wb shell, GUARD_ALLOW_EPHEMERAL) and relays (GUARD_ALLOW_RELAY): see below |
| POST | /containers/{id}/start, /wait, /attach, /resize |
Only on containers that Wharfbase created as helpers; relays only start |
| DELETE | /containers/{id} |
Only on containers that Wharfbase created as helpers or relays |
Nothing else gets through: no exec, no other container’s start, stop or delete, no image pulls, no volume or network changes, no swarm, plugins or secrets.
Helper containers
Section titled “Helper containers”Backups and wb shell run a database’s own tool (pg_dump, mysqldump, psql, …) in a short-lived container, which needs GUARD_ALLOW_EPHEMERAL=true (off by default). Even then, the guard only creates containers that meet all of these:
- The image matches
GUARD_EPHEMERAL_IMAGES: by default, the official images of postgres, mysql, mariadb, mongo, redis and valkey. The pattern always has to match the whole image name. - The container carries the label
wharfbase.ephemeral=true. - Mounts and binds: only the
wharfbase_backupsvolume. - No
hostorcontainer:network mode. - None of
Privileged,CapAdd,Devices,PidMode,IpcMode,UsernsMode,SecurityOpt,Sysctls,VolumesFrom,PortBindings,ExtraHosts,RuntimeorTmpfs. Docker reads keys in any case, so the guard checks them that way too.
The guard then re-serializes the request from the fields it allows, so anything else in the body never reaches Docker.
Relays
Section titled “Relays”With network attach set to Relays (WB_NETWORK_ATTACH=relay, or Settings → Discovery), Wharfbase joins no database network. For each database that needs one, it starts a relay: a container of Wharfbase’s own image that joins the database’s network and wharfbase_relay, and forwards one TCP port (wharfbase relay --to host:port). Wharfbase itself joins only wharfbase_relay, so a compromised Wharfbase can reach the databases’ ports through their relays and nothing else on their networks.
Relays need GUARD_ALLOW_RELAY=true (off by default). The guard creates a relay only when all of these hold:
- The image is the one Wharfbase’s own container runs (its reference or its id).
- The command is exactly
wharfbase relay --to <host>:<port>, with no entrypoint and no environment. - It runs as a user other than root, with a read-only root filesystem, every capability dropped, at most 64 MiB of memory without swap, a process limit, and a restart policy of
noorunless-stopped. - It joins exactly two networks:
wharfbase_relay(also itsNetworkMode) and one other, which can’t behost,noneor labelledwharfbase.attach=false. - It carries
wharfbase.relay=trueand onlywharfbase.*labels, and none of the refusedHostConfigkeys above, nor binds or mounts.
The body is rebuilt from those fields, endpoint settings (aliases, addresses) included. Relays can only be started and removed. Wharfbase removes a relay once its database has been gone for 30 minutes, and all of them when you leave relay mode.
Settings
Section titled “Settings”| Variable | Default | |
|---|---|---|
GUARD_ALLOW_NETWORK_ATTACH |
true |
Wharfbase may join and leave networks |
GUARD_ALLOW_ARCHIVE |
true |
Wharfbase may read files from containers |
GUARD_ALLOW_EPHEMERAL |
false |
Wharfbase may run helper containers |
GUARD_ALLOW_RELAY |
false |
Wharfbase may run relays and create their network |
GUARD_EPHEMERAL_IMAGES |
official database images | Which images helpers may use |
GUARD_SELF_LABEL |
wharfbase.self=true |
How the guard recognizes Wharfbase’s container |
With the installer, set WB_GUARD_ALLOW_NETWORK_ATTACH, WB_GUARD_ALLOW_EPHEMERAL and WB_GUARD_ALLOW_RELAY in /opt/wharfbase/.env, then wharfbase up. For relays only: WB_GUARD_ALLOW_RELAY=true, WB_GUARD_ALLOW_NETWORK_ATTACH=false and WB_NETWORK_ATTACH=relay. Archive access is on in the official compose file: the SQLite scan and snapshots need it.
Unraid runs single containers only, so there Wharfbase mounts the Docker socket itself and none of this applies. The template says so.