--- name: run-confined description: Run confined / sandbox an untrusted command — use for `curl ... | bash` installers, npm/npx/bunx of unvetted packages, or any third-party script that should not see ~/.ssh, ~/.config/gh, tokens, or the rest of $HOME; when someone says "run it confined", "sandbox this", or "don't pipe that into my shell". Runs the command in a transient systemd user unit whose only writable place is ~/.local. --- # Run confined: untrusted code in a one-shot systemd user unit [run-confined.sh](run-confined.sh) runs a command in a transient `systemd-run --user` service with kernel-enforced sandboxing: - `$HOME` is an unreadable tmpfs (`ProtectHome=yes`): no `~/.ssh`, `~/.config/gh`, `.env` files, or other secrets. - The only writable place is `/tmp/confined`, a bind mount of `~/.local`; inside the unit `$HOME` points there. Whatever gets installed lands in `~/.local` on the host. - System dirs are read-only; `/tmp` and `/dev` are private. - The calling shell's environment is not inherited (no `GH_TOKEN`, `SSH_AUTH_SOCK`, ...). Network is allowed. - Node (newest nvm v22, bound read-only) is on `PATH` when present. Before running anything, the script does a wet test: if `~/.ssh` is still visible from inside a probe unit, it aborts with exit 3 and runs nothing. ## Usage ```sh .agents/skills/run-confined/run-confined.sh -- cmd [args...] ``` Arguments naming paths under `$HOME` or `~/.local` are rewritten to the `/tmp/confined` equivalents; add `--preserve-paths` to opt out. stdin, stdout, and stderr are piped through (`systemd-run -P`), so a download-and-run installer works as a pipe. Fetch with `curl` on the host (read it first), then run the interpreter confined. Pass environment explicitly with `env`, since the unit doesn't inherit any: ```sh curl -fsSL https://bun.com/install -o /tmp/bun-install.sh less /tmp/bun-install.sh .agents/skills/run-confined/run-confined.sh -- \ env BUN_INSTALL=/tmp/confined bash < /tmp/bun-install.sh # -> ~/.local/bin/bun on the host ``` Point installers at `/tmp/confined` (e.g. `BUN_INSTALL`, `npm_config_prefix`, which the script already sets), not `$HOME/.foo`. That way binaries land in `~/.local/bin`, which is already on the host `PATH`. Rc-file edits an installer makes go to `~/.local/.bashrc` and similar, where they have no effect; check for them and delete them. ## When the wet test aborts On Ubuntu 24.04, `kernel.apparmor_restrict_unprivileged_userns=1` (the default) stops the systemd user manager from creating namespaces, and systemd *silently* skips the sandbox. The script refuses rather than run unconfined. The operator decides whether to lift it: ```sh sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0 # until reboot ``` Don't run the command unconfined as a fallback. Report the abort and let the operator choose. The script header lists the full symptom set. The canonical copy of this script is `~/projects/office-admin/tools/run-confined.sh`.