Skip to content

Jail agent commands in their own namespaces - #5

Merged
ardecvz merged 8 commits into
mainfrom
jail-agent-commands
Sep 15, 2026
Merged

ardecvz merged 8 commits into
mainfrom
jail-agent-commands

Conversation

@skryukov

@skryukov skryukov commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

The sandbox agent could use the harness's secrets: a DeepSeek model read OPENROUTER_API_KEY from its environment and called OpenRouter with it for Perplexity searches, which the allowlist permits since lemans itself talks to OpenRouter.

Every agent command now runs jailed:

  • Miniswen: --jail runs every command in its own namespaces: none of the harness's environment, no network, read-only system, none of its files.
    miniswen-installed always runs jailed.
    The jail is built on stock Linux tools (unshare, nsenter, setpriv), the only semi-default pkg sandbox images need is iproute2 to bring loopback up.
  • Docker: containers start with SYS_ADMIN, NET_ADMIN and AppArmor unconfined, which the jail needs.

Also:

  • Verifier: a failed restore raises an infrastructure error instead of scoring 0. Also, to be tamper-proof, the reporter aborts the run if the agent patched Minitest so tests cannot fail.
  • Miniswen: retry model calls for ~10 min instead of ~5.

@ardecvz ardecvz changed the title Jail agent commands in a bubblewrap sandbox Jail agent commands in their own namespaces Sep 15, 2026
@ardecvz
ardecvz marked this pull request as ready for review September 15, 2026 04:13
Copilot AI lite review requested due to automatic review settings September 15, 2026 04:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Unresolved critical jail-isolation and Docker-privilege findings require changes and human review.

Pull request overview

Adds jailed Miniswen execution, Docker sandbox changes, stricter verifier failures, tamper detection, longer retries, and release metadata updates.

Changes:

  • Introduces namespace-based jail execution and enables it for miniswen-installed.
  • Adds Docker capabilities and iproute2 support.
  • Treats restore failures and Minitest tampering as run failures.
  • Extends model retry duration and updates documentation, tests, and versions.
File summaries
File Summary and review notes
test/miniswen/jail_test.rb Tests jail isolation.
test/miniswen/agent_test.rb Updates retry-budget coverage.
test/lemans/trial/verifier_test.rb Updates restore-failure expectations.
test/lemans/trial/snapshot_test.rb Tests restore failures.
test/lemans/test_eport_lemans.rb Tests tamper-detection abort behavior.
test/lemans/environments/test_docker.rb Tests Docker privilege flags.
test/lemans/agents/test_miniswen_installed.rb Tests jailed command execution.
README.md Documents the iproute2 dependency.
lib/miniswen/version.rb Updates the Miniswen version.
lib/miniswen/ruby_llm.rb Extends model-call retries to approximately 10 minutes.
lib/miniswen/local.rb Refactors process spawning.
lib/miniswen/jail.rb Implements namespace jail execution. Critical (3 votes): the unshare holder can expose provider credentials through /proc/1/environ; scrub its environment. Critical (2 votes): only selected directories are read-only while UID 0 can write other paths; make the root filesystem read-only with explicit writable mounts. Moderate (1 vote): non-root execution requires root or CAP_SYS_ADMIN. Moderate (1 vote): workdirs nested under replaced scratch mounts become inaccessible. Moderate (1 vote): direct requiring can produce missing-constant errors.
lib/miniswen/cli.rb Adds jail lifecycle handling. Moderate (2 votes): --docker cleanup unconditionally calls unavailable stop, causing a post-run NoMethodError; stop only environments that own their lifecycle or provide a no-op.
lib/lemans/version.rb Updates the Lemans version.
lib/lemans/trial/verifier/assets/lemans_minitest_reporter.rb Detects Minitest assertion tampering.
lib/lemans/trial/verifier.rb Propagates restore failures.
lib/lemans/trial/snapshot.rb Makes restore failures raise infrastructure errors.
lib/lemans/environments/docker.rb Adds jail-required Docker privileges. Critical (3 votes): CAP_SYS_ADMIN, CAP_NET_ADMIN, and AppArmor unconfined mode apply to every Docker sandbox, including runs that do not use the jail; restrict these privileges to jail-capable execution.
lib/lemans/cli/templates/bench/environment/Dockerfile Installs iproute2.
lib/lemans/agents/miniswen_installed.rb Enables jailed execution for installed Miniswen.
CHANGELOG.md Records the release changes.
Review details

Suppressed comments (4)

lib/miniswen/jail.rb:24

  • This unshare invocation creates mount and network namespaces without a user namespace, so the exposed miniswen --jail option requires root or CAP_SYS_ADMIN; a normal non-root invocation fails with EPERM before running any command. Either create and map a user namespace or make this privilege requirement explicit and validate it before starting the jail.
        "unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", SETUP, pgroup: true

lib/miniswen/jail.rb:13

  • Replacing /tmp (and similarly /root or /run) hides any configured workdir below that mount. For example, Jail.new(workdir: "/tmp/project") starts with an empty /tmp, then every exec fails when nsenter --wd=/tmp/project cannot change directory. Preserve the workdir before overlaying scratch paths or reject/handle workdirs nested under them.
      for dir in /tmp /run /root; do mkdir -p "/var/lib/miniswen$dir" && mount --bind "/var/lib/miniswen$dir" "$dir"; done

lib/miniswen/jail.rb:3

  • This file only requires miniswen/local, but container_variables references Agent::EXEC_ENV and startup errors reference InfrastructureError. A consumer that does require "miniswen/jail" directly therefore gets a NameError on the first command (or on a startup failure); the test helper masks this by loading the full miniswen entrypoint first. Load the package entrypoint here before using those constants.
require "miniswen/local"

lib/miniswen/jail.rb:39

  • Using the outer /proc/1/environ as the allowlist defeats the isolation when a credential was supplied to the container at startup: that variable name is included and its value from ENV is forwarded into the jailed shell. Keep an explicit allowlist of non-secret runtime variables instead of deriving it from PID 1.
      [ ENV.to_h.merge(env.to_h).slice(*container_variables),
        "nsenter", "--target", @holder.pid.to_s, "--net", "--mount", "--pid=/proc/#{@holder.pid}/ns/pid_for_children", "--wd=#{@workdir}",
  • Files reviewed: 21/21 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/lemans/environments/docker.rb
Comment thread lib/miniswen/jail.rb
Comment thread lib/miniswen/jail.rb
Comment thread lib/miniswen/cli.rb
@ardecvz
ardecvz merged commit 39fedd5 into main Sep 15, 2026
3 checks passed
@ardecvz
ardecvz deleted the jail-agent-commands branch September 15, 2026 04:46
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.

3 participants