Module: KairosMcp::Daemon::RestrictedShell::SandboxFactory

Defined in:
lib/kairos_mcp/daemon/restricted_shell/sandbox_factory.rb

Overview

SandboxFactory — generates platform-appropriate sandbox wrapping.

Class Method Summary collapse

Class Method Details

.macos?Boolean

Returns:

  • (Boolean)


23
24
25
# File 'lib/kairos_mcp/daemon/restricted_shell/sandbox_factory.rb', line 23

def self.macos?
  RUBY_PLATFORM.include?('darwin') && File.executable?('/usr/bin/sandbox-exec')
end

.render_sbpl(cwd:, allowed_paths:, network:) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/kairos_mcp/daemon/restricted_shell/sandbox_factory.rb', line 41

def self.render_sbpl(cwd:, allowed_paths:, network:)
  # F2 fix: validate + escape paths for SBPL injection prevention
  all_paths = [cwd, *allowed_paths]
  all_paths.each do |p|
    if p.match?(/["\\()\n\r]/)
      raise SandboxError, "path contains SBPL-unsafe characters: #{p.inspect}"
    end
  end
  allowed_read = allowed_paths.map { |p| "(subpath \"#{p}\")" }.join("\n  ")
  network_clause = network == :allow ? '(allow network-outbound)' : ''

  <<~SBPL
    (version 1)
    (deny default)
    (allow process-fork process-exec
      (subpath "/usr/bin")
      (subpath "/usr/local/bin")
      (subpath "/opt/homebrew/bin")
      (subpath "/opt/homebrew/Cellar")
      (subpath "/Library/TeX/texbin"))
    (allow file-read*
      (subpath "/usr/lib")
      (subpath "/usr/share")
      (subpath "/System/Library")
      (subpath "/Library/Fonts")
      (subpath "/opt/homebrew/lib")
      (subpath "/opt/homebrew/share")
      (subpath "/opt/homebrew/Cellar")
      #{allowed_read})
    (allow file-write*
      (subpath "#{cwd}"))
    (allow file-read-metadata
      (subpath "/usr")
      (subpath "/System")
      (subpath "/Library")
      (subpath "/opt/homebrew")
      #{allowed_read})
    (allow file-read* (literal "/dev/random") (literal "/dev/urandom") (literal "/dev/null"))
    (allow file-write-data (literal "/dev/null"))
    #{network_clause}
    (allow mach-lookup
      (global-name "com.apple.system.logger")
      (global-name "com.apple.system.notification_center"))
  SBPL
end

.wrap(bin_path:, argv:, cwd:, allowed_paths:, network:) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
# File 'lib/kairos_mcp/daemon/restricted_shell/sandbox_factory.rb', line 11

def self.wrap(bin_path:, argv:, cwd:, allowed_paths:, network:)
  if macos?
    wrap_macos(bin_path, argv, cwd, allowed_paths, network)
  else
    # No sandbox available — require explicit opt-in
    unless ENV['KAIROS_SANDBOX_FALLBACK'] == 'unsafe_ok_i_know'
      raise SandboxError, 'no sandbox driver; set KAIROS_SANDBOX_FALLBACK=unsafe_ok_i_know'
    end
    SandboxContext.new(cmd: [bin_path, *argv], driver: :none_exec_only)
  end
end

.wrap_macos(bin_path, argv, cwd, allowed_paths, network) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/kairos_mcp/daemon/restricted_shell/sandbox_factory.rb', line 27

def self.wrap_macos(bin_path, argv, cwd, allowed_paths, network)
  tmpdir = Dir.mktmpdir('kairos_sbpl')
  begin
    profile = render_sbpl(cwd: cwd, allowed_paths: allowed_paths, network: network)
    profile_path = File.join(tmpdir, 'profile.sb')
    File.write(profile_path, profile)
    cmd = ['/usr/bin/sandbox-exec', '-f', profile_path, bin_path, *argv]
    SandboxContext.new(cmd: cmd, driver: :sandbox_exec, tmpdir: tmpdir)
  rescue StandardError => e
    FileUtils.rm_rf(tmpdir) rescue nil
    raise SandboxError, "SBPL setup failed: #{e.message}"
  end
end