Skip to content
zoryn/ maintainer-assistant

Sandbox

zoryn runs scripts from .gear/up.d/ and .gear/merge-up.d/ in a sandbox to protect sensitive files (~/.ssh, ~/.gnupg, ~/.config) from untrusted repository scripts.

Setup

Install bubblewrap for lightweight sandboxing:

apt-get install bubblewrap

For hybrid mode (bwrap + hasher chroot with the package's build dependencies):

apt-get install bubblewrap hasher

Runner modes

RunnerDescription
hybridbwrap + hasher chroot — uses the package's build dependencies (default when both tools are installed)
bwrapbubblewrap only — lightweight namespace isolation with host system paths
directNo isolation — full filesystem access (not recommended)

The runner is auto-detected: hybrid if both bwrap and hsh are available, bwrap if only bwrap, otherwise direct.

If the configured runner is unavailable, zoryn prompts for confirmation before running hooks without isolation. In non-interactive mode, execution is refused. Use --no-sandbox to skip the prompt and run without sandboxing.

What every sandbox mode shares

For both hybrid and bwrap (i.e. anything other than direct):

AspectBehaviour
Project directoryThe git root is bind-mounted read-write at the same path. Hooks see and can modify the repository.
Working directorychdir to the git root before exec.
Home ($HOME)Replaced with an empty tmpfs. ~/.ssh, ~/.gnupg, ~/.config, history files, agent sockets — none of these are visible.
~/.gitconfigBind-mounted read-only if it exists, so git inside the hook keeps your user identity.
EnvironmentCleared with --clearenv. Only PATH, HOME, and TAG/COMMIT/SHORTCOMMIT (merge hooks only) are re-injected.
PATH/usr/bin:/bin by default (configurable via [sandbox] path = "…").
/procMounted as a fresh procfs.
/devBind-mounted from the host (--dev-bind /dev /dev) for full device access.
Process namespace--unshare-all — fresh PID, IPC, UTS, cgroup namespaces.
NetworkAllowed (--share-net). Hooks can curl, go mod download, cargo fetch, etc.
Lifetime--die-with-parent — the sandbox dies if zoryn dies.

What differs between modes is where /usr, /lib, /bin, /sbin, /etc come from:

bwrap

System directories (/usr, /lib, /lib64, /etc, /bin, /sbin) are bind-mounted read-only from the host. The hook sees exactly the tools you have installed in your host distro.

  • Pros: zero setup cost — bwrap launch is fast (no chroot to build).
  • Cons: build dependencies declared in the spec aren't guaranteed to be there; the host's library versions are what the hook gets.

Use when hooks don't need any package beyond what's already on your machine (typical for sed/awk glue scripts).

hybrid

hsh first builds a hasher chroot populated with the package's build dependencies (parsed from BuildRequires:), then bwrap is launched with the chroot's /usr, /lib, /lib64, /etc, /bin, /sbin, /var bind-mounted read-only instead of the host's (with a writable /var/tmp tmpfs on top for scratch space). Project directory, /proc, /dev, network, and home isolation are identical to bwrap.

/etc/resolv.conf is bind-mounted from the host so DNS works inside the chroot.

  • Pros: hooks see exactly the toolchain that will be used to build the package — reproducible across machines.
  • Cons: first run pays the chroot-preparation cost (seconds to a minute, cached afterwards in ~/hasher).

Use when hooks need build tooling (go mod vendor, cargo vendor, meson subprojects download, etc.).

Additional packages on top of the spec's BuildRequires: can be listed in .gear/version-up:

[sandbox]
packages = ["go", "make", "curl"]

These are installed via hsh-install after the chroot is prepared.

To skip the package's BuildRequires: entirely, set specbr = false. The chroot is then initialised bare (hsh --initroot-only, no src.rpm build) — only the explicitly configured packages (git, plus any [sandbox.chroot] packages from ~/.zoryn and the packages listed above) are installed, not the spec's build dependencies. Useful when those dependencies are irrelevant to the hooks and the src.rpm build is just overhead:

[sandbox]
specbr = false
packages = ["go", "make", "curl"]

specbr defaults to true (build the src.rpm and use its BuildRequires:). When specbr = false, a custom [sandbox.chroot] prepare command (from ~/.zoryn) is ignored in favour of the bare init — zoryn prints a warning when this happens.

direct

No isolation at all. The hook script is executed in the current process's environment, with full access to your filesystem, ~/.ssh, the network, environment variables, and so on. Equivalent to running the hook by hand.

Only use this when you have audited the hooks and trust the repository.

Configuration

In ~/.zoryn:

[sandbox]
runner = "hybrid"
path = "/usr/bin:/bin"

[sandbox.chroot]
prepare = "gear-hsh --commit -- --mountpoints=/proc,/dev/pts,/dev/kvm --lazy-cleanup --build-srpm-only"

[sandbox.hasher]
workdir = "~/hasher"

Per-project (~/.config/zoryn/projects.d/<project>.toml):

[sandbox]
runner = "hybrid"

[sandbox.chroot]
prepare = "gear-hsh --commit -- --mountpoints=/proc,/dev/pts,/dev/kvm --lazy-cleanup --build-srpm-only"

Additional packages

In .gear/version-up:

[sandbox]
packages = ["go", "make", "curl"]

The git package is always installed. Packages listed here are installed after chroot preparation via hsh-install, on top of the spec's BuildRequires:. Add specbr = false to skip those BuildRequires: and the src.rpm build (see hybrid above).

CLI flags

zoryn up --sandbox=hybrid    # use hybrid (default)
zoryn up --sandbox=bwrap     # use bubblewrap with host paths
zoryn up --no-sandbox        # disable sandbox entirely

How hybrid works

In hybrid mode, hasher prepares a chroot populated with the package's build dependencies (parsed from the spec file), and bwrap runs scripts using paths from that chroot. This gives both filesystem isolation and access to required tools without needing setuid privileges at runtime.