Class: Insika::Sandbox::Docker
- Inherits:
-
Object
- Object
- Insika::Sandbox::Docker
- Defined in:
- lib/insika/sandbox/docker.rb
Overview
Isolated exec provider: runs the command inside a throwaway Docker container
(docker run --rm) with the sandbox root bind-mounted at a fixed workdir.
This is the isolation boundary the local provider is NOT — a real target
for UNTRUSTED shell execution.
"Narrowest sandbox that supports the task": only the risky part — shell exec
— is containerized. The FS tools (read/write/edit/grep) keep operating on
the host path, confined by the Boundary; the container sees the SAME bytes
through the bind mount, so a file the model writes is immediately visible to
a subsequent bash call and vice-versa.
Defaults are conservative: --network none (no egress from the container),
a memory cap and a cpu cap, and a minimal image. All are overridable via the
profile's sandbox config (config-over-code).
Constant Summary collapse
- DEFAULTS =
{ "image" => "alpine:3.20", "network" => "none", "memory" => "512m", "cpus" => "1.0", "workdir" => "/workspace", "shell" => "/bin/sh", "docker_bin" => "docker" }.freeze
Instance Method Summary collapse
-
#argv(command, root:, name:) ⇒ Object
PURE argv builder — unit-testable without Docker present.
-
#available? ⇒ Boolean
Whether the Docker daemon is reachable.
- #exec(command, root:, timeout:, max_output:) ⇒ Object
-
#initialize(config = {}) ⇒ Docker
constructor
A new instance of Docker.
- #to_s ⇒ Object
Constructor Details
#initialize(config = {}) ⇒ Docker
Returns a new instance of Docker.
33 34 35 36 37 38 39 40 41 42 |
# File 'lib/insika/sandbox/docker.rb', line 33 def initialize(config = {}) cfg = DEFAULTS.merge(config.transform_keys(&:to_s).compact) @image = cfg["image"] @network = cfg["network"] @memory = cfg["memory"] @cpus = cfg["cpus"].to_s @workdir = cfg["workdir"] @shell = cfg["shell"] @docker_bin = cfg["docker_bin"] end |
Instance Method Details
#argv(command, root:, name:) ⇒ Object
PURE argv builder — unit-testable without Docker present. --name lets
the timeout teardown docker kill the exact container (killing the client
process alone does not stop it).
47 48 49 50 51 52 |
# File 'lib/insika/sandbox/docker.rb', line 47 def argv(command, root:, name:) [@docker_bin, "run", "--rm", "--name", name, "--network", @network, "--memory", @memory, "--cpus", @cpus, "--volume", "#{root}:#{@workdir}:rw", "--workdir", @workdir, @image, @shell, "-c", command.to_s] end |
#available? ⇒ Boolean
Whether the Docker daemon is reachable. Used at boot to fail fast (or warn) instead of discovering it mid-turn.
65 66 67 68 69 |
# File 'lib/insika/sandbox/docker.rb', line 65 def available? system(@docker_bin, "version", out: File::NULL, err: File::NULL) rescue StandardError false end |
#exec(command, root:, timeout:, max_output:) ⇒ Object
54 55 56 57 58 59 60 61 |
# File 'lib/insika/sandbox/docker.rb', line 54 def exec(command, root:, timeout:, max_output:) name = "harness-sbx-#{SecureRandom.hex(8)}" Runner.run(argv(command, root: root, name: name), chdir: root, timeout: timeout, max_output: max_output, # On the wall-clock deadline, stop the container by name; the # `--rm` then removes it. Squelch output — this is teardown. kill: -> { system(@docker_bin, "kill", name, out: File::NULL, err: File::NULL) }) end |
#to_s ⇒ Object
71 |
# File 'lib/insika/sandbox/docker.rb', line 71 def to_s = "docker(#{@image}, network=#{@network})" |