Class: Silas::Sandbox::Docker

Inherits:
Object
  • Object
show all
Defined in:
lib/silas/sandbox/docker.rb

Overview

Interim adapter: shells to an ephemeral, locked-down docker run container (no network, read-only fs, dropped caps, no-new-privileges, memory/cpu/pid limits, host-side timeout). Weaker than a microVM — flag honestly. The argv is a pure function (unit-tested); the exec runner is injectable so the command building is testable without Docker present.

Constant Summary collapse

TIMEOUT_EXIT =

coreutils convention

124

Instance Method Summary collapse

Constructor Details

#initialize(image:, network: "none", memory: "512m", cpus: "1", pids: 256, workdir: "/workspace", docker_bin: "docker", runner: nil) ⇒ Docker

Returns a new instance of Docker.

Raises:



14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/silas/sandbox/docker.rb', line 14

def initialize(image:, network: "none", memory: "512m", cpus: "1", pids: 256,
               workdir: "/workspace", docker_bin: "docker", runner: nil)
  raise SandboxError, "config.sandbox_image is required for :docker" if image.to_s.strip.empty?

  @image = image
  @network = network
  @memory = memory
  @cpus = cpus
  @pids = pids
  @workdir = workdir
  @docker_bin = docker_bin
  @runner = runner || method(:shell_capture)
end

Instance Method Details

#docker_argv(command, name:) ⇒ Object

Pure: command is a single argv element to sh -c, never interpolated into a shell string — no injection surface.



43
44
45
46
47
48
49
50
# File 'lib/silas/sandbox/docker.rb', line 43

def docker_argv(command, name:)
  [ @docker_bin, "run", "--rm", "--name", name,
    "--network", @network,
    "--memory", @memory, "--cpus", @cpus.to_s, "--pids-limit", @pids.to_s,
    "--read-only", "--tmpfs", @workdir, "--workdir", @workdir,
    "--cap-drop", "ALL", "--security-opt", "no-new-privileges",
    @image, "/bin/sh", "-c", command ]
end

#enabled?Boolean

Returns:

  • (Boolean)


28
# File 'lib/silas/sandbox/docker.rb', line 28

def enabled? = true

#run(command, files: {}, timeout: 30) ⇒ Object

Raises:



30
31
32
33
34
35
36
37
38
39
# File 'lib/silas/sandbox/docker.rb', line 30

def run(command, files: {}, timeout: 30)
  if Silas::Ledger.in_transaction?
    raise SandboxError, "sandbox exec inside a ledger transaction — sandbox-backed tools must be at_most_once!, never transactional!"
  end
  raise SandboxError, "files: is not supported in v1 (deferred)" unless files.empty?

  name = "silas-sbx-#{SecureRandom.hex(6)}"
  out, err, status = @runner.call(docker_argv(command, name: name), timeout: timeout, name: name)
  Result.new(stdout: out, stderr: err, exit_status: status)
end