From 46ec93c9b9d3d55788e93d32b784ced79b82b1ce Mon Sep 17 00:00:00 2001 From: Artur Petrov Date: Tue, 15 Sep 2026 05:33:30 +0000 Subject: [PATCH 1/4] - docker: --docker no longer fails at exit --- CHANGELOG.md | 2 ++ lib/miniswen/environment.rb | 2 ++ lib/miniswen/local.rb | 2 -- test/miniswen/cli_test.rb | 8 ++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03c1901..f9ba91a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [Unreleased] +- Docker: `--docker` no longer fails at exit. + ## [1.3.4] - 2026-09-15 - 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. diff --git a/lib/miniswen/environment.rb b/lib/miniswen/environment.rb index 655f4e6..3639e8b 100644 --- a/lib/miniswen/environment.rb +++ b/lib/miniswen/environment.rb @@ -10,5 +10,7 @@ def success? = exit_code.zero? # Execute a shell command def exec(cmd, timeout: nil, env: nil) = raise NotImplementedError + + def stop = nil end end diff --git a/lib/miniswen/local.rb b/lib/miniswen/local.rb index 18b53a8..fdb748e 100644 --- a/lib/miniswen/local.rb +++ b/lib/miniswen/local.rb @@ -27,8 +27,6 @@ def exec(command, timeout: nil, env: nil) end end - def stop = nil - private def spawn_arguments(command, env) = [ env || {}, "sh", "-c", command ] diff --git a/test/miniswen/cli_test.rb b/test/miniswen/cli_test.rb index 54201d8..d60b872 100644 --- a/test/miniswen/cli_test.rb +++ b/test/miniswen/cli_test.rb @@ -188,6 +188,14 @@ def run_cli(*argv) ARGV.replace(original) end + def test_a_docker_run_exits_cleanly + Dir.mktmpdir do |dir| + assert_raises(RubyLLM::Test::Errors::NoResponseProvidedError) do + run_cli("-q", "--no-refresh-registry", "--docker", "c", "--results-path=#{dir}/result.json", "-m", "test", "-p", "task") + end + end + end + def test_a_crashed_run_still_writes_the_results_and_trajectory_files Dir.mktmpdir do |dir| results = File.join(dir, "result.json") From 02b7abf74fa145f29658f6087cf923a0e9d584f8 Mon Sep 17 00:00:00 2001 From: Artur Petrov Date: Tue, 15 Sep 2026 05:33:30 +0000 Subject: [PATCH 2/4] - miniswen: jail makes the whole system read-only --- CHANGELOG.md | 1 + lib/miniswen/jail.rb | 27 +++++++++++++++++---------- test/miniswen/jail_test.rb | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9ba91a..270ecb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [Unreleased] - Docker: `--docker` no longer fails at exit. +- Miniswen: the jail makes the whole system read-only. ## [1.3.4] - 2026-09-15 diff --git a/lib/miniswen/jail.rb b/lib/miniswen/jail.rb index 57c91d1..0752f95 100644 --- a/lib/miniswen/jail.rb +++ b/lib/miniswen/jail.rb @@ -1,27 +1,20 @@ # frozen_string_literal: true +require "shellwords" + require "miniswen/local" module Miniswen # Runs every command in its own namespaces: none of the harness's environment, # no network, read-only system, none of its files. class Jail < Local - SETUP = <<~SH - set -eu - ip link set lo up - for dir in /usr /etc /opt; do mount -o bind,ro "$dir" "$dir"; done - for dir in /tmp /run /root; do mkdir -p "/var/lib/miniswen$dir" && mount --bind "/var/lib/miniswen$dir" "$dir"; done - echo ready - exec sleep infinity - SH - def initialize(workdir: Dir.pwd) @workdir = workdir end def start _, @stdout, @stderr, @holder = Open3.popen3( - "unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", SETUP, pgroup: true + "unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", setup, pgroup: true ) return self if @stdout.gets == "ready\n" @@ -34,6 +27,20 @@ def stop private + def setup = <<~SH + set -eu + # Bring loopback up + ip link set lo up + # Make the workdir its own mount + mount --bind #{Shellwords.escape(@workdir)} #{Shellwords.escape(@workdir)} + # Make private temp dirs their own mounts + for dir in /tmp /run /root; do mkdir -p "/var/lib/miniswen$dir" && mount --bind "/var/lib/miniswen$dir" "$dir"; done + # Make everything without its own mount read-only + mount -o remount,bind,ro / + echo ready + exec sleep infinity + SH + def spawn_arguments(command, env) [ 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}", diff --git a/test/miniswen/jail_test.rb b/test/miniswen/jail_test.rb index f08d6a1..9d1b8bf 100644 --- a/test/miniswen/jail_test.rb +++ b/test/miniswen/jail_test.rb @@ -22,7 +22,7 @@ def test_commands_have_no_network_but_loopback end def test_commands_cannot_touch_the_system_or_see_the_harness_files - assert_equal "read-only\n", @jail.exec("(touch /usr/x 2>/dev/null && echo writable) || echo read-only").output + assert_equal "read-only\n", @jail.exec("(touch /x 2>/dev/null && echo writable) || echo read-only").output assert_equal "0\n0\n", @jail.exec("ls -A /root | wc -l; ls -A /tmp | wc -l").output end end From 15c7e2034785e99dab0898772883641afd3aea53 Mon Sep 17 00:00:00 2001 From: Artur Petrov Date: Tue, 15 Sep 2026 05:33:30 +0000 Subject: [PATCH 3/4] - miniswen: jail does not leak env vars, which were already inaccessible --- CHANGELOG.md | 2 +- lib/miniswen/jail.rb | 4 +++- test/miniswen/jail_test.rb | 13 +++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 270ecb3..8d9fc8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## [Unreleased] - Docker: `--docker` no longer fails at exit. -- Miniswen: the jail makes the whole system read-only. +- Miniswen: the jail makes the whole system read-only and does not leak env vars, which were already inaccessible. ## [1.3.4] - 2026-09-15 diff --git a/lib/miniswen/jail.rb b/lib/miniswen/jail.rb index 0752f95..a772a4b 100644 --- a/lib/miniswen/jail.rb +++ b/lib/miniswen/jail.rb @@ -14,7 +14,9 @@ def initialize(workdir: Dir.pwd) def start _, @stdout, @stderr, @holder = Open3.popen3( - "unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", setup, pgroup: true + ENV.to_h.slice(*container_variables), + "unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", setup, + pgroup: true, unsetenv_others: true ) return self if @stdout.gets == "ready\n" diff --git a/test/miniswen/jail_test.rb b/test/miniswen/jail_test.rb index 9d1b8bf..7d4723f 100644 --- a/test/miniswen/jail_test.rb +++ b/test/miniswen/jail_test.rb @@ -5,15 +5,24 @@ class MiniswenJailTest < Minitest::Test def setup + @key = ENV.fetch("OPENROUTER_API_KEY", nil) + skip "needs root" unless Process.uid.zero? + ENV["OPENROUTER_API_KEY"] = "sk" @jail = Miniswen::Jail.new(workdir: Dir.tmpdir).start end - def teardown = @jail&.stop + def teardown + ENV["OPENROUTER_API_KEY"] = @key + @jail&.stop + end def test_commands_do_not_get_the_harness_environment - assert_equal "0\n", @jail.exec("env | grep -c OPENROUTER", env: { "OPENROUTER_API_KEY" => "sk" }).output + holder = @jail.instance_variable_get(:@holder).pid + + assert_equal "0\n", @jail.exec("env | grep -c OPENROUTER").output + refute_includes File.read("/proc/#{holder}/environ"), "OPENROUTER" end def test_commands_have_no_network_but_loopback From 2d0b7fb8d3b0077574dcf7b70fd4e32c8f9d6e76 Mon Sep 17 00:00:00 2001 From: Artur Petrov Date: Tue, 15 Sep 2026 06:17:21 +0000 Subject: [PATCH 4/4] Bump 1.3.5 --- CHANGELOG.md | 2 ++ lib/lemans/version.rb | 2 +- lib/miniswen/version.rb | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d9fc8c..c362dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [Unreleased] +## [1.3.5] - 2026-09-15 + - Docker: `--docker` no longer fails at exit. - Miniswen: the jail makes the whole system read-only and does not leak env vars, which were already inaccessible. diff --git a/lib/lemans/version.rb b/lib/lemans/version.rb index b89a690..82f5b96 100644 --- a/lib/lemans/version.rb +++ b/lib/lemans/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Lemans - VERSION = "1.3.4" + VERSION = "1.3.5" end diff --git a/lib/miniswen/version.rb b/lib/miniswen/version.rb index d022cb2..79e1e0a 100644 --- a/lib/miniswen/version.rb +++ b/lib/miniswen/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Miniswen - VERSION = "1.3.4" + VERSION = "1.3.5" end