This guide explains what box-rpg protects against and where its limits are. Read it once before granting extra permissions. Daily steps are in the manual.
box-rpg runs games and runtimes as your own user. It confines what it can, but it cannot make hostile software safe.
- Games and downloaded runtimes are untrusted. Never run the launcher with elevated privileges. It needs none.
- A game is only launched below an allowed game root (
allowed_game_rootsinconfig.toml). Keep these roots limited to folders you control. To authorize a root, see Configuration; to authorize one folder, see Launch a game. - Game paths are resolved, including links, before use. Path traversal and escaping links are rejected.
- Keep the game directory in place during authorization and launch. The launcher rejects observed location changes.
- An isolated session separates launcher files and profiles. Only the Bubblewrap sandbox confines running processes.
- One session per game entry is firm: a second launch while one runs fails with "game already running".
- Packed single-executable directories unpack into the source-keyed game profile at
$XDG_CACHE_HOME/box-rpg/profiles/<game-id>/game/before detection. For the launch steps, see Packed single-executable games. Consent covers the source folder you pointed at; the session identifier, saves, and profiles follow the source too. Only trees detected as a supported engine are accepted. A changed source fingerprint (content hash, size, modification time) re-unpacks over the same tree with save folders carried forward, and removing the profile deletes the unpacked tree and its saves with it, except EasyRPG source saves which live in the source folder and survive. Inspection unpacks on first use, so it writes to the profile cache: same trust class as launch unpacking, no extra privileges.
Games and runtime version probes always run inside a mandatory Bubblewrap sandbox at /usr/bin/bwrap with user namespaces. There is no fallback without it. --die-with-parent is kept: the launcher double-forks a detached supervisor (setsid, orphaned to init) that becomes the Bubblewrap parent, so the guarantee moves to the supervisor instead of disappearing when the launcher exits. Only the pass_fds allowlist crosses into Bubblewrap; supervisor descriptors use O_CLOEXEC, there is no shell, and extra descriptors are closed.
The sandbox gives the game read-only system libraries and selected game and runtime files. Process, IPC, UTS, and /tmp namespaces are private. Your home directory and SSH agent are never shared. D-Bus is never shared except for the opt-in filtered GameMode proxy described below. Only saves, the selected profile, and private temporary storage are writable.
Supervised sessions use sessions_root/<game_id>/<session> depth-2 directories with 0700 permissions and lower-case names under launcher containment. Liveness is an exclusive non-blocking flock on session.lock (PID-reuse safe, never a bare PID). status.json is launcher-owned, atomically written (mkstemp/fsync/chmod 0600/os.replace), never writable from the sandbox, and read with bounded typed checks (None while running, exit code when exited).
Validation is not an immutable snapshot. It cannot defend against other local processes with the same permissions. Game code can still damage its own writable saves or profile, exhaust resources, or attack shared interfaces. diagnose runs the runtime too, but always inside the sandbox.
Extra access is always opt-in for one launch. For how to request it, see Permissions per launch.
| Access | Flag / Socket | Risk in one line |
|---|---|---|
| Host network | --allow-network |
shares host network, including internet, local network, and shared local networking such as loopback |
| Graphics acceleration | /dev/dri and read-only /sys |
enables hardware fingerprinting and driver attack surface |
| Sound | PipeWire and PulseAudio sockets | audio socket does not separate speakers from microphone input |
| X11 display | --x11 |
X11 programs can observe input and screen contents, so Wayland stays better isolated |
| GameMode boost | --gamemode via /usr/bin/busctl and /run/user/gamemode-proxy |
shares only com.feralinteractive.GameMode over a filtered xdg-dbus-proxy socket, failure fails the launch |
| Game self-writes | --allow-game-writes |
mounts the game directory writable so self-updating games can patch themselves, never combine with untrusted games |
--gamemoderegisters the host Bubblewrap PID withcom.feralinteractive.GameModevia/usr/bin/busctl(host, timeout-bounded) and shares only that bus name over a filteredxdg-dbus-proxysocket at/run/user/gamemode-proxy(no see/own/broadcasts). Host registration is authoritative because the PID namespace hides in-sandbox PIDs; the in-sandboxgamemoderunprefix stays as fallback. A missing bus client, proxy, bus address, socket, or failed host registration fails the launch instead of running unboosted.--allow-game-writesmounts the game directory writable so self-updating games can patch themselves. Never combine it with untrusted games. For NW.js without the flag only the save directory is writable; with the flag the whole game tree is writable and writes persist, except the launcher-controlledpackage.json. For EasyRPG, writes persist. Combined with--ci-mount, reads stay case-insensitive while writes persist through the mount policy: writes to an existing name keep the on-disk case and new names are created literally. Sustained writes through the mount are throttled to 100 MiB/s with a 1 s burst; the cap limits speed, not total space. Only use the combination with games you trust, as with the flag alone.- EasyRPG saves are bound writable from the consented source folder's
save/(0700, user-owned, no group or other write, no symlinks). The directory is opened withO_NOFOLLOW, pinned by device and inode plusreadlinkchecks, and validated withvalidate_tree; the unpacked game tree stays read-only unless--allow-game-writes. First-run migration best-effort moves only regular files and directories from the old unpackedsave/into a user-owned sourcesave/without group or other write permission that does not exist or is empty (at most 5000 entries, 16 MiB per file); symlinks, FIFOs, sockets, and other specials never travel, existing destinations are never truncated (O_EXCL), and over-budget or unreadable files stay behind without failing the launch. Cleanup never deletes outside the managed cache, so source saves survive profile removal. For save locations, see Game files and saves. - The runtime receives a small explicit environment. Host secrets and loader injection variables are not passed. Terminal output escapes control characters from game metadata. Only the local display socket and its authority cookie are shared for X11. This covers X11 sessions and XWayland for runtimes without Wayland support, such as the EasyRPG build.
For how to install and pin a runtime, see Install a runtime.
- Runtime requests validate HTTPS origins on every redirect. Each download has a 1 GiB transfer budget across retries. Failed
.partfiles are never resumed. - Extraction allows at most 4 GiB of expanded data, 1 GiB per member, 50,000 entries, and depth 32. Sparse members, archive hard links, and links escaping the runtime directory are rejected.
- Packed-executable unpacking enforces the same budgets (4 GiB total, 1 GiB per file, 50,000 entries, depth 32) plus a 1 GiB source size cap. Every run shares a cooperative 15-minute deadline, checked between files and decompression chunks. Over-budget runs fail naming the budget.
- Unpacking confines every write to its output directory: absolute paths and parent escapes are refused, only regular files and directories are created, and symbolic links are never created. Publication has two levels: per-attempt trial writes stage into a private temporary directory below the output and only the winning profile is moved into place, while profile game trees stage into a separate private directory inside the profile and publish under the cooperative per-profile cache lock by renaming the previous tree to a sibling backup, moving staging into place with
os.replace, then removing the backup (restoring the backup when the final name is missing after a failure); a busy entry fails fast so the caller can retry. Save folders are carried forward across re-unpacks with only regular files and directories; symlinks, FIFOs, and other specials are never carried. For EasyRPG the unpackedsave/simply goes stale after source migration (it is still carried, but never bound). A denied game still leaves its published profile game tree; removing the profile deletes it with its saves, except EasyRPG source saves which live outside the cache. - Download and extraction each have a cooperative 15-minute deadline, checked between reads. It does not kill a stuck system call.
- Concurrent operations on the same runtime fail with a busy error. Retry after the other operation finishes.
- NW.js installs verify
SHASUMS256.txtwith its official detached GPG signature and bundled key fingerprint1E8BEE8D5B0C4CBCD6D19E2678680FA9E21BB40A, then hash the same archive used for extraction. Cached archives are checked again. Only a missing signature file (HTTP 404) permits an HTTPS-only install with aNOT VERIFIEDwarning. Other failures reject the install. This needs/usr/bin/gpg. Installed runtimes are not re-verified on every launch. - The distributed EasyRPG archives have no official detached signatures, so they use the official HTTPS sources without a checksum catalog. Neither transport nor signatures prove that software is benign.
All enforced numbers in one place. Rationale stays in the sections above.
| Area | Limit |
|---|---|
| Runtime download transfer | 1 GiB transfer budget across retries |
| Runtime extraction expanded data | at most 4 GiB of expanded data |
| Runtime extraction single member | 1 GiB per member |
| Runtime extraction entries | 50,000 entries |
| Runtime extraction depth | depth 32 |
| Packed unpack total | 4 GiB total |
| Packed unpack single file | 1 GiB per file |
| Packed unpack entries | 50,000 entries |
| Packed unpack depth | depth 32 |
| Packed source size | 1 GiB source size cap |
| Download deadline | cooperative 15-minute deadline, checked between reads |
| Extraction deadline | cooperative 15-minute deadline, checked between reads |
| Unpack deadline | cooperative 15-minute deadline, checked between files and decompression chunks |
| Configuration file | at most 1 MiB |
| Inspection metadata | at most 16 MiB per metadata file |
| Launch manifest | limited to 1 MiB |
| Diagnostic core | limited to 4 MiB |
| Version probe timeout | 10-second timeout |
| Version probe output | 64 KiB output limit |
| Save migration entries | at most 5000 entries |
| Save migration file size | 16 MiB per file |
| Session directories | sessions_root/<game_id>/<session> depth-2 directories with 0700 permissions |
| EasyRPG save directory | 0700, user-owned, no group or other write, no symlinks |
| Status file | atomically written (mkstemp/fsync/chmod 0600/os.replace) |
| Configuration ownership | regular user-owned file without group or other write permission |
- Configuration must be a regular user-owned file without group or other write permission, at most 1 MiB. Empty or relative XDG variables fall back to
$HOME/.configand$HOME/.cache. - Managed cache paths use lower-case components. Runtime removal only affects runtimes inside the configured cache.
- Inspection reads at most 16 MiB per metadata file. Launch manifests are limited to 1 MiB and diagnostic core files to 4 MiB. Version probes have a 10-second timeout and a 64 KiB output limit.
- The game manifest never forwards
chromium-argsorjs-flags. Only supported, correctly typed window settings are copied. - Cleanup only removes launcher-owned data under
$XDG_CACHE_HOME/box-rpg. It never deletes game files or saves, and it leavesconfig.toml, sessions, and reports intact. For the cleanup steps, see Manage stored data.
Wayland sessions are recommended. Graphical compatibility has not been validated against real games, so some exports may fail. The managed EasyRPG Player build has no Wayland video driver and runs through XWayland.