Security and Hardening¶
Read this page before exposing any Selkies container beyond your local machine.
The threat model in one paragraph¶
This container provides privileged access to the host system
The web interface includes a terminal with passwordless sudo. Any user who can reach the GUI can gain root inside the container, install arbitrary software, and probe your local network. Do not expose it to the internet unless you have secured it properly.
Treat a Selkies session like an SSH login to a machine on your network, because functionally that is what it is.
HTTPS is required¶
Modern browser features the client depends on, WebCodecs for video and audio in particular, only work in a secure context. That is why:
- Port
3001serves HTTPS with a self signed certificate and is the port you browse to directly. - Port
3000serves plain HTTP and exists only to sit behind a reverse proxy that terminates TLS.
Authentication layers¶
By default there is no authentication. Your options, from weakest to strongest:
- HTTP basic auth: set
CUSTOM_USERandPASSWORD. Adequate for a trusted home LAN, nothing more. - Reverse proxy with real auth: for anything internet facing, put the container behind a reverse proxy such as SWAG with a robust authentication mechanism (Authelia, Authentik, OAuth2 proxy, client certificates). See Reverse Proxy.
- SealSkin: if you are serving multiple users or apps, SealSkin handles authentication with public key cryptography and only proxies sessions to their owners.
Container isolation options¶
- Avoid
--privilegedunless you specifically want the Docker in Docker feature. It dramatically widens the blast radius. - Mounting
/var/run/docker.sockhands the container control of the host Docker daemon, which is root on the host in practical terms. Only do this deliberately. --security-opt seccomp=unconfineddisables a key Docker security layer. Use it only when a legacy host kernel or old libseccomp genuinely requires it, and treat it as a temporary workaround.
Embedding and origins¶
Two settings decide which web pages may reach a session at all. SELKIES_ALLOWED_ORIGINS is the cross site WebSocket hijacking guard on the streaming socket; the containers relax it to * because Nginx fronts the server, so tighten it to your own origin when the container is reachable from the open internet without a proxy. SELKIES_FRAME_ANCESTORS is the Content-Security-Policy frame-ancestors directive, 'self' by default, so another site cannot lay the session under clicks meant for itself. A portal that embeds the desktop in a frame from another origin lists that origin there. Both are in the Configuration Reference.
Hardening variables¶
For kiosk deployments, single app terminals, classrooms, or any situation where the person at the keyboard is not the administrator, the baseimage ships lockdown variables.
Note that the window manager level switches (HARDEN_OPENBOX, DISABLE_CLOSE_BUTTON, DISABLE_WINDOW_BUTTONS, DISABLE_MOUSE_BUTTONS, HARDEN_KEYBINDS) only apply to single application containers, which run under labwc or Openbox. Full desktop Webtops manage their own windows, so only the desktop wide switches (HARDEN_DESKTOP and the SELKIES_* locks) matter there. The mechanics are documented in depth in the developer guide.
Umbrella switches¶
| Variable | Description |
|---|---|
HARDEN_DESKTOP | Enables DISABLE_OPEN_TOOLS, DISABLE_SUDO, and DISABLE_TERMINALS. Also sets the related client settings (SELKIES_FILE_TRANSFERS, SELKIES_COMMAND_ENABLED, SELKIES_PRINTING_ENABLED, SELKIES_UI_SIDEBAR_SHOW_FILES, SELKIES_UI_SIDEBAR_SHOW_APPS) unless you set them explicitly yourself |
HARDEN_OPENBOX | Window manager lockdown for single app containers. Enables DISABLE_CLOSE_BUTTON, DISABLE_MOUSE_BUTTONS, and HARDEN_KEYBINDS, and flags RESTART_APP unless you set it, so the primary application restarts automatically if closed |
Individual switches¶
| Variable | Description |
|---|---|
DISABLE_OPEN_TOOLS | Disables the xdg-open and exo-open binaries by removing their execute permissions, so apps cannot spawn arbitrary helpers |
DISABLE_SUDO | Disables sudo by removing execute permissions and invalidating the passwordless sudo configuration |
DISABLE_TERMINALS | Disables common terminal emulators and hides them from the right click menu |
DISABLE_CLOSE_BUTTON | Removes the close button from window title bars |
DISABLE_WINDOW_BUTTONS | Removes the minimize, maximize and close buttons from window title bars |
DISABLE_MOUSE_BUTTONS | Disables right click and middle click context menus and actions in the window manager |
HARDEN_KEYBINDS | Disables window manager keybinds that could bypass the other options, such as alt+f4 to close windows or alt+escape for the root menu |
RESTART_APP | Watchdog that restarts the main application if it is closed. The user's autostart script is made read only and root owned to prevent tampering |
Locking client settings¶
Any boolean SELKIES_* setting can be pinned so the user cannot change it in the sidebar by appending |locked:
-e SELKIES_ENABLE_CLIPBOARD="false|locked"
-e SELKIES_FILE_TRANSFERS="none"
-e SELKIES_UI_SHOW_SIDEBAR="false|locked"
See the Configuration Reference for the full list and value syntax.
Example: locked down kiosk browser¶
---
services:
kiosk:
image: lscr.io/linuxserver/chromium:latest
shm_size: 1gb
environment:
- HARDEN_DESKTOP=true
- HARDEN_OPENBOX=true
- "SELKIES_ENABLE_CLIPBOARD=false|locked"
- SELKIES_FILE_TRANSFERS=none
- "SELKIES_UI_SHOW_SIDEBAR=false|locked"
- NO_DECOR=true
- "CHROME_CLI=--kiosk https://youtube.com"
ports:
- 3001:3001
restart: unless-stopped
Locking the sidebar off entirely (SELKIES_UI_SHOW_SIDEBAR=false|locked) is the finishing touch for a true kiosk, the visitor sees nothing but the app.
Sharing links¶
The sharing feature generates links that grant access to the running session: view only, or gamepad only for players 2 through 4. A second page opened on the session's plain address joins as a full controller beside the first. The built in links are a demo of the multi user protocol, they carry no authentication of their own beyond whatever guards the page, so anyone with the address has the same power as the primary user, including that passwordless sudo terminal. Disable what you do not need:
-e SELKIES_ENABLE_SHARING=false # one page holds the session, a second takes it over
-e SELKIES_ENABLE_SHARED=false # or drop individual links
Proper shared sessions need secure mode: set SELKIES_MASTER_TOKEN and the server ignores the fragments and admits only pages presenting a provisioned token that carries its own role and gamepad slot. The token table is posted to /api/tokens with the master token, or read from a file named by SELKIES_TOKENS_FILE. SealSkin is the reference orchestrator for it; the upstream Secure Mode page has the wire details.
Pelorus and the computer use API¶
Enabling PELORUS=true starts an agent control API inside the container and forces the accessibility bridge on for all applications, which means any process in the session can read the full UI text of every app. The Pelorus API itself has no authentication and relies on the container's Nginx and network isolation. Treat a Pelorus enabled container as fully readable and controllable by anything that can reach it on its internal port, and never publish its ports directly. Details on the Pelorus page.
The pixelflux Computer Use server underneath drives the desktop with the session user's full authority. It requires a bearer token (SELKIES_COMPUTER_USE_TOKEN, or PIXELFLUX_CU_TOKEN for the standalone PIXELFLUX_CU port) and refuses to start without one, and a bare port binds loopback only, which every account inside the container can still reach.