class LittleGhost::Sandbox
A Sandbox governs filesystem operations and child processes that explicitly pass through it. Built-in filesystem and shell Tools use their bound Sandbox. A custom Ruby Tool remains trusted application code unless it delegates work to that Sandbox or one of its Scopes.
LittleGhost.configure do |config| config.sandbox = { provider: :native, files: {root: :read_write, source: :read_only}, runtime_paths: {home: :read_write}, network: :none } end
A backend reports the policy and capabilities it actually enforces. File operations stay within declared Workspace paths. Process operations honor cancellation and configured limits, then return an Execution.
A backend’s isolation mechanism still relies on its outer host, kernel or VM, dependencies, trusted configuration, and deliberately exposed paths. Sandbox policy does not apply to provider requests or arbitrary Ruby code in the application process.
See the Workspaces and Sandboxes guide for the path model, built-in backends, Scopes, process ownership, and networking boundaries.
Attributes
File and process output bounds enforced by this Sandbox.
Normalized policy requested by trusted application configuration.
Workspace whose files and processes this sandbox governs.
Public Class Methods
# File lib/little_ghost/sandbox.rb, line 119 def initialize(workspace:, policy: nil, profiles: {}, limits: {}) @workspace = workspace @policy = Policy.coerce(policy) @limits = Limits.coerce(limits) configure_profiles!(profiles) end
Binds the sandbox to workspace.
Source
# File lib/little_ghost/sandbox.rb, line 53 def probe(name, **options) implementation = resolve_provider(name) provider_probe = implementation.method(:probe) return provider_probe.call(**options) unless provider_probe.owner == Sandbox.singleton_class unless options.empty? raise ArgumentError, "sandbox provider :#{name} does not accept probe options" end { available: true, reason: nil, capabilities: Capabilities.new(features: [], network_modes: []) } rescue DependencyError => error {available: false, reason: error.message, capabilities: Capabilities.new(features: [], network_modes: [])} end
Reports whether a registered backend can start in the current environment without creating a Run-owned sandbox.
# File lib/little_ghost/sandbox.rb, line 36 def register_provider(name, implementation) unless implementation.is_a?(Class) && implementation <= Sandbox raise ArgumentError, "sandbox provider must be a Sandbox class" end Sandbox.providers[name.to_sym] = implementation end
Registers a trusted backend class under a configuration symbol.
Source
# File lib/little_ghost/sandbox.rb, line 45 def resolve_provider(name) Sandbox.providers.fetch(name.to_sym) do raise DependencyError, "sandbox provider :#{name} is not available" end end
Resolves a registered backend without silently falling back.
Public Instance Methods
# File lib/little_ghost/sandbox.rb, line 149 def allows?(operation, path = nil) return supports?(operation) unless path scope.allows?(operation, path) end
Indicates whether an operation is allowed by this sandbox and optional virtual path.
Source
# File lib/little_ghost/sandbox.rb, line 140 def capabilities Capabilities.new(features: [], network_modes: []) end
Operations and network modes implemented by this backend.
Source
# File lib/little_ghost/sandbox.rb, line 241 def close nil end
Releases sandbox resources. Runs close the sandbox before its workspace.
Source
# File lib/little_ghost/sandbox.rb, line 137 def effective_policy = policy
Policy the backend enforces. Backends may fill a documented default or report an unavoidable effective value, but reject unsupported requested rules instead of silently claiming enforcement.
# File lib/little_ghost/sandbox.rb, line 215 def execute(command, timeout:, context: nil, max_output_bytes: nil, **options) execute_program( ["/bin/sh", "-c", String(command)], timeout:, context:, max_output_bytes:, **options ) end
Executes command through /bin/sh.
Prefer execute_program for model-controlled arguments so shell syntax is not interpreted.
# File lib/little_ghost/sandbox.rb, line 230 def execute_program(command, timeout:, context: nil, max_output_bytes: nil, environment: {}, inherit_environment: false, **options) raise AbstractMethodError, "#{self.class} does not support program execution" end
Executes an argument vector without shell interpretation.
Implementations must enforce timeout and max_output_bytes. Environment inheritance is disabled by default to avoid leaking process credentials; both policy and the individual call must opt in before a backend may inherit.
# File lib/little_ghost/sandbox.rb, line 196 def list(path = ".", context: nil) raise AbstractMethodError, "#{self.class} does not support filesystem listings" end
Lists entries at a workspace-relative or absolute virtual directory path.
Source
# File lib/little_ghost/sandbox.rb, line 183 def open(run: nil) self end
Opens any run-scoped resources and makes the sandbox ready for tools.
Source
# File lib/little_ghost/sandbox.rb, line 191 def read(path, context: nil) raise AbstractMethodError, "#{self.class} does not support filesystem reads" end
Reads UTF-8 text at a workspace-relative or absolute virtual path.
# File lib/little_ghost/sandbox.rb, line 207 def replace(path, old_text, new_text, context: nil) raise AbstractMethodError, "#{self.class} does not support filesystem edits" end
Replaces one exact old_text occurrence at a workspace-relative or absolute virtual path with new_text.
# File lib/little_ghost/sandbox.rb, line 158 def scope(profile = nil, files: nil, runtime_paths: nil, capabilities: nil, network: nil) if profile if !files.nil? || !runtime_paths.nil? || !capabilities.nil? || !network.nil? raise ArgumentError, "scope profile cannot be combined with explicit options" end declaration = @profiles.fetch(profile.to_sym) do raise PolicyError, "unknown sandbox scope profile: #{profile.inspect}" end declaration = declaration.call(workspace:, policy: effective_policy) if declaration.respond_to?(:call) unless declaration.respond_to?(:transform_keys) raise PolicyError, "sandbox scope profile must be a Hash" end values = declaration.transform_keys(&:to_sym) unknown = values.keys - %i[files runtime_paths capabilities network] raise PolicyError, "unknown sandbox scope profile options: #{unknown.join(", ")}" unless unknown.empty? files = values[:files] runtime_paths = values[:runtime_paths] capabilities = values[:capabilities] network = values[:network] end Scope.new(sandbox: self, files:, runtime_paths:, capabilities:, network:) end
Source
# File lib/little_ghost/sandbox.rb, line 236 def start_program(command, context: nil, environment: {}, inherit_environment: false, **options) raise AbstractMethodError, "#{self.class} does not support program sessions" end
Starts an owned, duplex child process for framed protocols and other interactive programs. The returned session owns the child process group.
# File lib/little_ghost/sandbox.rb, line 145 def supports?(feature, value = nil) = capabilities.supports?(feature, value)
Indicates whether the backend implements feature.
Source
# File lib/little_ghost/sandbox.rb, line 188 def writable? = supports?(:filesystem_write)
Indicates whether filesystem mutation is allowed.
# File lib/little_ghost/sandbox.rb, line 201 def write(path, content, context: nil) raise AbstractMethodError, "#{self.class} does not support filesystem writes" end
Writes content to a workspace-relative or absolute virtual path.