Skip to content

Security & hardening

LabPod maps each platform account to a Linux account on the host. Rootless Podman runs every workspace as the owner’s Linux user - not as root, not as a shared service account.

What the admin can and cannot do:

Admin canAdmin cannot
View all workspaces and resource usageEnter another user’s terminal session
Force-stop another user’s workspaceStart, edit, delete, or open another user’s workspace
Create and delete usersAccess another user’s files via LabPod
Manage global workspace templatesList, pull, build, delete, or prune workspace images

The admin boundary is intentional: force-stop reclaims the container and its reserved resources; anything deeper requires direct host access.

Root cannot own workspaces. The root account cannot create or start its own workspaces because workspace containers must run as a non-root Linux user. Root also cannot remove another user’s images. Image usage and operation history are observable to administrators, but image lifecycle remains owner-only.

Every workspace container launched by LabPod includes these flags:

FlagWhat it does
--userns=keep-idHost UID = container UID - files have natural ownership
--cap-drop=ALLDrops all Linux capabilities
--security-opt no-new-privilegesPrevents setuid privilege escalation
--initRuns catatonit as PID 1 for zombie reaping
--shm-size <half-of-memory>Prevents PyTorch DataLoader from hitting the 64 MB default
127.0.0.1:<port> bindWorkspace ports bind only to loopback - never directly exposed

Workspace traffic is proxied through LabPod’s reverse proxy, which enforces session-cookie authentication before forwarding. Workspace-template launchers use /ws/<id>/<launcher>/; user-declared ports use /ws/<id>/_user/<name>/. The separate namespaces allow a launcher and user port to share a name.

The workspace gateway also confines a web application’s service worker to that application’s URL prefix. A launcher or user-added app cannot register a broad worker that controls another app or workspace on the shared gateway origin.

Containers started directly with Podman are outside the workspace trust boundary. LabPod may inspect them for read-only safety and capacity accounting, but it does not proxy into them or offer launchers, terminals, logs, declared ports, or lifecycle actions. The legacy /ws/c:<container>/ and /ws/_discovered/ forms return 404.

The default systemd unit runs LabPod as root (User=root). Rootless workspaces still run as their owner’s Linux account: the server builds argv arrays and executes Podman as sudo -u <owner> / equivalent user-context commands. It never shells out through bash -c with user input.

By default LabPod serves plain HTTP on port 24680. For HTTPS:

Option 1 - Self-signed certificate (for private LAN use):

Terminal window
# In /etc/labpod/labpod.env:
LABPOD_TLS_SELF_SIGNED=true
LABPOD_TLS_HOSTS=192.168.1.10,labpod.lan # optional extra SANs

On first start, LabPod generates and caches a self-signed cert under /var/lib/labpod/tls/. The cert renews automatically when it nears expiry.

Get the SHA-256 fingerprint for the desktop launcher’s trust-on-first-use pin:

Terminal window
sudo labpod admin tls-fingerprint
Terminal window
# Run these as root
labpod admin tls-fingerprint

Option 2 - Operator-provided certificate:

Terminal window
LABPOD_TLS_CERT=/etc/labpod/tls/server.crt
LABPOD_TLS_KEY=/etc/labpod/tls/server.key

Both LABPOD_TLS_CERT and LABPOD_TLS_KEY must be set together.

The owner-aware loopback port guard is enabled by default. It lets a workspace owner use their own assigned localhost or SSH-forwarded ports while rejecting access by other Linux users, so workspace application traffic follows the authenticated LabPod gateway. The server must run as root (the default) and nft must be available.

Terminal window
# Disable only when you intentionally accept unguarded same-host access:
LABPOD_PORT_GUARD=none

If nftables is missing or unsupported, LabPod warns the administrator and continues rather than blocking startup or workspace lifecycle. Normal launcher and user-port links continue to work.

doctor reports whether the port guard is installed correctly:

Terminal window
sudo labpod admin doctor
Terminal window
# Run these as root
labpod admin doctor

If LabPod sits behind a reverse proxy, enable proxy header forwarding and declare the direct peer that connects to each LabPod listener:

Terminal window
# Same-host nginx, Caddy, or OpenResty:
LABPOD_TRUST_PROXY_HEADERS=true
LABPOD_TRUSTED_PROXY_PEERS=loopback,unix
# A separate edge network: use the source address or CIDR LabPod sees.
# LABPOD_TRUSTED_PROXY_PEERS=10.40.8.0/24,2001:db8:40::/64

Only enable this if LabPod is not directly internet-accessible. With the flag off, forwarded headers are ignored. With it on, only an exact IP address, CIDR, loopback, or unix peer may supply forwarded metadata; hostnames are not accepted. Keep the backend ports firewalled to those peers and configure the proxy to replace, not append, client-supplied forwarding headers. Restart labpod.service after changing either setting.

For the WebSocket terminal endpoint, add trusted origins if users connect through a domain name that differs from the server’s address:

Terminal window
LABPOD_WS_ALLOWED_ORIGINS=https://labpod.example.com