Landrun: binaries are not trustworthy

Landrun: binaries are not trustworthy

If you are allowed to run a program, the program is usually allowed to see almost everything you can.
Run a build tool and it can probably read your SSH keys, tokens.
Run an AI coding agent and it can probably inspect your browser profile, cloud credentials, source code, spaghetti monster knows what.
Found a great tool online that promises great things? chances are that it'll HTTP post your stuff to a random Webserver.

That can even happen with tools that you have used and trusted for years if they've been subject to a supply chain attack.

This is not because the software needs that access, it has that access because removing it historically been annoying, the result is that we routinely execute enormous piles of code with the authority of a trusted human user, then hope nothing inside the pile has been compromised. that's just too optimistic.

The authority gap

Most CLI programs need surprisingly little, a compiler might needs to read a source directory, a static-site generator needs to reads content and templates and then write to a specific directory, an image converter needs one input file and somewhere to put the results, nothing more.

The difference between the authority a process receives and the authority it actually needs is an attack surface. on most developer machines and CI runners, that difference is enormous.

This becomes particularly ugly with modern development tooling.

We now routinely execute third-party packages during installation, build scripts pulled from public registries, extensions updated automatically, containers assembled from remote layers and AI agents specifically designed to explore the machine and take actions on our behalf.

The old answer was: only run software you trust.
That answer has expired.

Containers are not always the answer

The obvious answer is to put everything in a container.

Sometimes that is exactly right.

Containers are excellent deployment units. They are useful for reproducibility, packaging and separating services. But they are also frequently an absurd response to a small local problem.

I should not need an image, registry, namespace configuration, volume mappings and a miniature operating system because I want to stop a PDF converter from reading ~/.ssh.

Firejail, Bubblewrap, AppArmor, SELinux and systemd sandboxing can all solve variations of this problem. They are powerful tools, and each comes with its own configuration model, operational assumptions and collection of exciting ways to lose an afternoon.

For many commands, the requirement is much smaller:

  • allow these paths to be read;
  • allow this directory to be written;
  • allow these binaries to execute;
  • allow connections to these TCP ports;
  • deny everything else.

Linux has a kernel feature designed for almost exactly this.
It is called Landlock and it changes the direction of control, Traditional access-control systems are generally configured by an administrator and applied to a process from the outside.
Landlock allows an unprivileged process to restrict itself and its future children and the kernel becomes the enforcement layer.

I liked this model, but using it directly was still too low-level for the sort of everyday sandboxing I wanted, so I built Landrun.

A sandbox should be boring to use

The basic model remains explicit:

go install github.com/zouuup/landrun/cmd/landrun@latest

landrun \
  --rox /usr \
  --ro ./input \
  --rw ./output \
  my-command

The command can read and execute files beneath /usr, read from ./input, write to ./output and do very little else.

No YAML document large enough to develop gravity.

The original weakness in this model was usability. Dynamically linked executables require access to their loader and shared libraries. Finding and listing every dependency manually produces a secure configuration, but also makes people reconsider whether security is really worth having.

Landrun now has two options to remove most of that friction:

landrun --add-exec --ldd /usr/bin/true

--add-exec automatically permits the target executable.
--ldd discovers its ELF dependencies recursively and permits the required libraries. The implementation reads ELF metadata directly, handles architecture-specific library resolution and avoids simply shelling out to ldd, which would be a rather creative thing for a sandboxing tool to trust.

This makes the smallest useful sandbox much easier to create:

landrun \
  --best-effort \
  --add-exec \
  --ldd \
  --ro ./untrusted-input \
  --rw ./result \
  converter ./untrusted-input/file.pdf

The converter receives the input, the output directory, its executable and required libraries.
It does not receive your home directory merely because Linux traditionally considers that convenient.

Files are only half the problem

A process that cannot read your SSH key may still be able to connect to a credential service, database, internal API or local UNIX socket.

Landrun can restrict TCP connections and listening ports:

landrun \
  --add-exec \
  --ldd \
  --connect-tcp 443 \
  curl https://example.com

For services, binding can be restricted separately:

landrun \
  --bind-tcp 8080 \
  --connect-tcp 5432 \
  ./my-service

Newer Landlock versions also understand more than files and TCP.

Landrun restricts communication through abstract UNIX sockets and signals across sandbox boundaries by default on supported kernels. With ABI v9, specific pathname UNIX sockets can be permitted explicitly:

landrun \
  --best-effort \
  --add-exec \
  --ldd \
  --unix /run/postgresql/.s.PGSQL.5432 \
  psql

This is important because on a modern Linux system, authority is often exposed through sockets rather than files. D-Bus, database connections, credential helpers and system services can all turn a supposedly isolated process into something considerably less isolated.

A filesystem sandbox that ignores IPC is only reading half the threat model.

Where this is actually useful

Landrun is not intended to replace containers, virtual machines or mandatory access-control systems.

It is useful where those things are too heavy, unavailable or simply the wrong abstraction.

Developer tools are an obvious case.

An AI coding agent can be given access to one repository instead of your entire account. It can write to the project and perhaps connect to an approved API, while being unable to inspect unrelated source code, personal files or credential directories.

CI jobs are another.

A test process can receive the checked-out repository, temporary storage and access to a test database without inheriting every secret and socket available to the runner.

Build steps, package hooks, document converters, media tools, static-site generators, plugins and unfamiliar binaries are all good candidates.

Systemd services can also be wrapped with Landrun, adding an unprivileged Landlock layer alongside systemd’s existing hardening controls.

The point is not to sandbox everything perfectly.

The point is to stop granting every process the authority of the person who launched it.

Make least privilege cheap

Least privilege is one of those principles every engineering organisation claims to believe in.

In practice, it is often applied only to production services and human IAM accounts because applying it to ordinary processes is considered too expensive.

That is backwards.
The cheapest place to reduce authority is before a process starts.

Not every process needs a container. Not every workstation needs a new mandatory access-control regime. Not every CI job needs a security platform with an annual contract and a dashboard showing twelve shades of red.

Sometimes you only need to say:

This command may read here, write there, connect to this port and touch nothing else.
GitHub - Zouuup/landrun: Run any Linux process in a secure, unprivileged sandbox using Landlock. Think firejail, but lightweight, user-friendly, and baked into the kernel.
Run any Linux process in a secure, unprivileged sandbox using Landlock. Think firejail, but lightweight, user-friendly, and baked into the kernel. - Zouuup/landrun