Skip to content
Wharfbase

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_add with 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).

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.

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_backups volume.
  • No host or container: network mode.
  • None of Privileged, CapAdd, Devices, PidMode, IpcMode, UsernsMode, SecurityOpt, Sysctls, VolumesFrom, PortBindings, ExtraHosts, Runtime or Tmpfs. 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.

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 no or unless-stopped.
  • It joins exactly two networks: wharfbase_relay (also its NetworkMode) and one other, which can’t be host, none or labelled wharfbase.attach=false.
  • It carries wharfbase.relay=true and only wharfbase.* labels, and none of the refused HostConfig keys 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.

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.