Skip to content

feat(sandbox): implement Linux sandbox helper binary#68

Merged
fohte merged 10 commits intomainfrom
fohte/impl-runok-init-linux-sandbox
Feb 22, 2026
Merged

feat(sandbox): implement Linux sandbox helper binary#68
fohte merged 10 commits intomainfrom
fohte/impl-runok-init-linux-sandbox

Conversation

@fohte
Copy link
Owner

@fohte fohte commented Feb 22, 2026

Why

  • Sandbox filesystem and network access for command execution on Linux
    • Adopt a defense-in-depth approach with bubblewrap (namespace isolation) + landlock (filesystem access control) + seccomp-bpf (network control)

What

  • Implement runok-linux-sandbox helper binary as a separate crate in the Cargo workspace
    • Stage 1: Set up bubblewrap namespace isolation and re-invoke itself inside the sandbox
    • Stage 2: Apply landlock (ABI V5, BestEffort compatibility) + seccomp (block non-AF_UNIX sockets), then execvp the target command
  • Add LinuxSandboxExecutor to the main crate with helper binary discovery (exe directory first, PATH fallback) and SandboxPolicy JSON serialization

Open with Devin

…ock, and seccomp

The runok project needs a Linux sandbox to restrict filesystem and
network access when executing commands. This implements the Helper
binary approach (task 12.3) as a separate workspace crate.

The helper binary operates in two stages:
- Stage 1: Sets up bubblewrap namespace isolation and re-invokes
  itself inside the sandbox
- Stage 2: Applies landlock filesystem restrictions (ABI V5,
  BestEffort compatibility) and seccomp network filtering (blocks
  non-AF_UNIX sockets), then execvp the target command

Also adds LinuxSandboxExecutor to the main crate with helper binary
discovery (exe directory first, then PATH fallback) and SandboxPolicy
serialization for JSON transport between the two binaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello @fohte, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security posture of command execution on Linux by integrating a sophisticated sandboxing mechanism. It introduces a dedicated helper binary that leverages core Linux security features like namespaces, Landlock, and seccomp to strictly control filesystem and network access for executed commands. This ensures that commands run in an isolated and restricted environment, minimizing potential security risks without requiring manual configuration from the user.

Highlights

  • Linux Sandbox Helper Binary: Implemented a new runok-linux-sandbox helper binary as a separate Rust crate to provide robust sandboxing capabilities on Linux.
  • Multi-Layered Sandboxing: The sandbox utilizes a defense-in-depth approach, combining bubblewrap for namespace isolation, Landlock for filesystem access control (ABI V5, BestEffort compatibility), and seccomp-bpf for network control (blocking non-AF_UNIX sockets).
  • Two-Stage Execution Model: The helper binary operates in two stages: first, bubblewrap sets up the isolated environment and re-invokes the helper; second, Landlock and seccomp rules are applied before executing the target command via execvp.
  • Dynamic Sandbox Executor Integration: Introduced LinuxSandboxExecutor in the main runok crate, which discovers the helper binary (first in the executable directory, then via PATH) and serializes SandboxPolicy to JSON for communication with the helper.
Changelog
  • Cargo.lock
    • Added new dependencies: enumflags2, enumflags2_derive, env_home, landlock, runok-linux-sandbox, seccompiler, which, winsafe.
  • Cargo.toml
    • Configured the workspace to include the new crates/runok-linux-sandbox crate.
    • Added which dependency to the main runok crate.
  • crates/runok-linux-sandbox/Cargo.toml
    • Created a new Cargo.toml for the runok-linux-sandbox crate.
    • Defined dependencies including anyhow, clap, landlock, libc, seccompiler, serde, serde_json, thiserror, and rstest for development.
  • crates/runok-linux-sandbox/src/bwrap.rs
    • Added a new module to construct bubblewrap command-line arguments based on the sandbox policy.
    • Included logic for read-only root, writable roots, read-only subpaths, /proc, /dev, /tmp mounts, namespace unsharing, network sharing, and re-invocation of the helper binary.
  • crates/runok-linux-sandbox/src/error.rs
    • Added custom error types for sandbox-related failures, including Landlock, seccomp, bubblewrap, and execution errors.
  • crates/runok-linux-sandbox/src/landlock_sandbox.rs
    • Added a new module to apply Landlock filesystem restrictions.
    • Implemented rules for read-only root, read-write /dev/null, common /dev paths, writable roots, and re-applying read-only to specific subpaths.
  • crates/runok-linux-sandbox/src/lib.rs
    • Created the library entry point for the runok-linux-sandbox crate, declaring its public modules.
  • crates/runok-linux-sandbox/src/main.rs
    • Implemented the main entry point for the runok-linux-sandbox helper binary.
    • Defined command-line arguments using clap for policy, current working directory, and command to execute.
    • Orchestrated the two-stage sandboxing process: stage 1 uses bubblewrap, stage 2 applies Landlock and seccomp before execvp.
  • crates/runok-linux-sandbox/src/policy.rs
    • Defined the SandboxPolicy struct for serialization and deserialization, used to pass policy details between the main runok binary and the helper.
  • crates/runok-linux-sandbox/src/seccomp_sandbox.rs
    • Added a new module to apply seccomp filters for network access control.
    • Implemented a filter to block non-AF_UNIX sockets when network access is disallowed.
  • src/exec/command_executor.rs
    • Derived serde::Serialize and serde::Deserialize for the SandboxPolicy struct to enable JSON serialization.
    • Updated the SandboxExecutor trait documentation to reflect platform-specific implementations.
    • Added LinuxSandboxExecutor for Linux, which finds and invokes the runok-linux-sandbox helper binary.
    • Implemented a helper function find_linux_sandbox_helper to locate the sandbox binary.
  • src/main.rs
    • Introduced a create_executor function to conditionally instantiate LinuxSandboxExecutor on Linux or fall back to a stub executor.
    • Modified the run_command function to use the dynamically created executor.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

devin-ai-integration[bot]

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

…e helpers

Landlock rules within a single ruleset are additive (union of
permissions), so adding a read-only rule for a subpath after a
read-write rule for its parent does not restrict write access.
The read_only_subpaths enforcement is already handled by bubblewrap's
mount ordering (--ro-bind after --bind).

Also fix tmp_in_writable check that incorrectly matched children of
/tmp as covering /tmp, and refactor add_path_rule to accept &Path
instead of &str to support non-UTF8 filesystem paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

❌ Patch coverage is 48.63014% with 150 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.59%. Comparing base (a77bcca) to head (607f1d6).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/runok-linux-sandbox/src/main.rs 22.07% 60 Missing ⚠️
crates/runok-linux-sandbox/src/landlock_sandbox.rs 16.12% 52 Missing ⚠️
src/exec/command_executor.rs 29.72% 26 Missing ⚠️
crates/runok-linux-sandbox/src/seccomp_sandbox.rs 81.39% 8 Missing ⚠️
crates/runok-linux-sandbox/src/bwrap.rs 96.77% 2 Missing ⚠️
src/main.rs 81.81% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #68      +/-   ##
==========================================
- Coverage   89.45%   87.59%   -1.87%     
==========================================
  Files          23       27       +4     
  Lines        5054     5344     +290     
==========================================
+ Hits         4521     4681     +160     
- Misses        533      663     +130     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

devin-ai-integration[bot]

This comment was marked as resolved.

fohte and others added 7 commits February 22, 2026 14:17
The existing test only verified that find_linux_sandbox_helper() does
not panic, without asserting the return value. The patch coverage was
low because the exe-directory search path was never exercised.

Add a test that creates a fake helper binary in the exe directory to
verify the discovery logic, and strengthen the existing test to assert
None is returned.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The patch coverage was low because the Exec command path in
run_command() and create_executor() were not exercised by tests.
Add tests that cover both functions to improve coverage on the
lines changed in this PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The /tmp writable-root check in bwrap.rs used exact string match
(r.as_os_str() == "/tmp") while landlock_sandbox.rs used
Path::starts_with which also matches parent paths like "/". This
inconsistency caused bwrap to add --tmpfs /tmp even when a parent
of /tmp was already in writable_roots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cargo llvm-cov was only running tests for default-members (main crate),
but Codecov counts all diff lines including runok-linux-sandbox. This
caused patch coverage to be 41% because the 811 lines in the Linux-only
crate were never tested in CI. Adding --workspace runs tests for all
crates in the workspace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`Ruleset::set_compatibility()` requires the `Compatible` trait to be
in scope. The import was accidentally dropped during the landlock
refactoring, causing a build failure on Linux CI.
The `Compatible` trait import was missing from `landlock_sandbox.rs`,
causing a build failure when `--workspace` coverage was enabled in CI.

Also added tests for `DryRunError` conversion/display, `exec_command`
error paths, `exit_code_from_status`, and `apply_seccomp` early return
to improve patch coverage.
The test used `split_whitespace` to parse `sh -c 'exit 42'`, which
split the quoted argument into `'exit` and `42'`. On Ubuntu where `sh`
is `dash`, this caused a syntax error. Pass command arguments as a
slice instead.
devin-ai-integration[bot]

This comment was marked as resolved.

When a writable root is a child of /tmp (e.g. /tmp/myproject), the
--tmpfs /tmp mount was still emitted and placed after the --bind mount,
causing bubblewrap to mount a fresh tmpfs over /tmp and hide the
writable bind. The same issue existed in landlock where /tmp would get
full write access even when only a subdirectory was intended to be
writable.

Check both directions: whether /tmp starts_with the writable root
(parent case) and whether the writable root starts_with /tmp (child
case).
@fohte fohte merged commit 545ae31 into main Feb 22, 2026
5 checks passed
@fohte fohte deleted the fohte/impl-runok-init-linux-sandbox branch February 22, 2026 08:44
@fohte-bot fohte-bot bot mentioned this pull request Feb 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant