Class: LittleGhost::Sandbox
- Inherits:
-
Object
- Object
- LittleGhost::Sandbox
- Defined in:
- lib/little_ghost/sandbox.rb,
lib/little_ghost/sandbox/mount.rb,
lib/little_ghost/sandbox/scope.rb,
lib/little_ghost/sandbox/limits.rb,
lib/little_ghost/sandbox/policy.rb,
lib/little_ghost/sandbox/filesystem.rb,
lib/little_ghost/sandbox/capabilities.rb,
lib/little_ghost/sandbox/network_policy.rb,
lib/little_ghost/sandbox/process_runner.rb,
lib/little_ghost/sandbox/process_session.rb,
lib/little_ghost/sandbox/isolated_backend.rb,
lib/little_ghost/sandbox/environment_policy.rb
Overview
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.
Direct Known Subclasses
LittleGhost::Sandboxes::Native, LittleGhost::Sandboxes::Unrestricted
Defined Under Namespace
Classes: Capabilities, EnvironmentPolicy, Execution, Filesystem, IsolatedBackend, Limits, Mount, NetworkPolicy, Policy, ProcessRunner, ProcessSession, Scope
Instance Attribute Summary collapse
-
#limits ⇒ Object
readonly
File and process output bounds enforced by this Sandbox.
-
#policy ⇒ Object
readonly
Normalized policy requested by trusted application configuration.
-
#workspace ⇒ Object
readonly
Workspace whose files and processes this sandbox governs.
Class Method Summary collapse
-
.probe(name, **options) ⇒ Object
Reports whether a registered backend can start in the current environment without creating a Run-owned sandbox.
-
.providers ⇒ Object
:nodoc:.
-
.register_provider(name, implementation) ⇒ Object
Registers a trusted backend class under a configuration symbol.
-
.resolve_provider(name) ⇒ Object
Resolves a registered backend without silently falling back.
Instance Method Summary collapse
-
#allows?(operation, path = nil) ⇒ Boolean
Indicates whether an operation is allowed by this sandbox and optional virtual
path. -
#capabilities ⇒ Object
Operations and network modes implemented by this backend.
-
#close ⇒ Object
Releases sandbox resources.
-
#effective_policy ⇒ Object
Policy the backend enforces.
-
#execute(command, timeout:, context: nil, max_output_bytes: nil, **options) ⇒ Object
Executes
commandthrough/bin/sh. -
#execute_program(command, timeout:, context: nil, max_output_bytes: nil, environment: {}, inherit_environment: false, **options) ⇒ Object
Executes an argument vector without shell interpretation.
-
#initialize(workspace:, policy: nil, profiles: {}, limits: {}) ⇒ Sandbox
constructor
Binds the sandbox to
workspace. -
#list(path = ".", context: nil) ⇒ Object
Lists entries at a workspace-relative or absolute virtual directory
path. -
#open(run: nil) ⇒ Object
Opens any run-scoped resources and makes the sandbox ready for tools.
-
#read(path, context: nil) ⇒ Object
Reads UTF-8 text at a workspace-relative or absolute virtual
path. -
#replace(path, old_text, new_text, context: nil) ⇒ Object
Replaces one exact
old_textoccurrence at a workspace-relative or absolute virtualpathwithnew_text. -
#scope(profile = nil, files: nil, runtime_paths: nil, capabilities: nil, network: nil) ⇒ Object
Produces a non-owning capability-reduced view for tools or child agents.
-
#start_program(command, context: nil, environment: {}, inherit_environment: false, **options) ⇒ Object
Starts an owned, duplex child process for framed protocols and other interactive programs.
-
#supports?(feature, value = nil) ⇒ Boolean
Indicates whether the backend implements
feature. -
#writable? ⇒ Boolean
Indicates whether filesystem mutation is allowed.
-
#write(path, content, context: nil) ⇒ Object
Writes
contentto a workspace-relative or absolute virtualpath.
Constructor Details
#initialize(workspace:, policy: nil, profiles: {}, limits: {}) ⇒ Sandbox
Binds the sandbox to workspace.
119 120 121 122 123 124 |
# 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 |
Instance Attribute Details
#limits ⇒ Object (readonly)
File and process output bounds enforced by this Sandbox.
132 133 134 |
# File 'lib/little_ghost/sandbox.rb', line 132 def limits @limits end |
#policy ⇒ Object (readonly)
Normalized policy requested by trusted application configuration.
130 131 132 |
# File 'lib/little_ghost/sandbox.rb', line 130 def policy @policy end |
#workspace ⇒ Object (readonly)
Workspace whose files and processes this sandbox governs.
127 128 129 |
# File 'lib/little_ghost/sandbox.rb', line 127 def workspace @workspace end |
Class Method Details
.probe(name, **options) ⇒ Object
Reports whether a registered backend can start in the current environment without creating a Run-owned sandbox.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/little_ghost/sandbox.rb', line 53 def probe(name, **) implementation = resolve_provider(name) provider_probe = implementation.method(:probe) return provider_probe.call(**) unless provider_probe.owner == Sandbox.singleton_class unless .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., capabilities: Capabilities.new(features: [], network_modes: [])} end |
.providers ⇒ Object
:nodoc:
70 71 72 |
# File 'lib/little_ghost/sandbox.rb', line 70 def providers # :nodoc: @providers ||= {} end |
.register_provider(name, implementation) ⇒ Object
Registers a trusted backend class under a configuration symbol.
36 37 38 39 40 41 42 |
# 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 |
.resolve_provider(name) ⇒ Object
Resolves a registered backend without silently falling back.
45 46 47 48 49 |
# 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 |
Instance Method Details
#allows?(operation, path = nil) ⇒ Boolean
Indicates whether an operation is allowed by this sandbox and optional
virtual path.
149 150 151 152 153 |
# File 'lib/little_ghost/sandbox.rb', line 149 def allows?(operation, path = nil) return supports?(operation) unless path scope.allows?(operation, path) end |
#capabilities ⇒ Object
Operations and network modes implemented by this backend.
140 141 142 |
# File 'lib/little_ghost/sandbox.rb', line 140 def capabilities Capabilities.new(features: [], network_modes: []) end |
#close ⇒ Object
Releases sandbox resources. Runs close the sandbox before its workspace.
241 242 243 |
# File 'lib/little_ghost/sandbox.rb', line 241 def close nil end |
#effective_policy ⇒ Object
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.
137 |
# File 'lib/little_ghost/sandbox.rb', line 137 def effective_policy = policy |
#execute(command, timeout:, context: nil, max_output_bytes: nil, **options) ⇒ Object
Executes command through /bin/sh.
Prefer #execute_program for model-controlled arguments so shell syntax is not interpreted.
215 216 217 218 219 220 221 222 223 |
# File 'lib/little_ghost/sandbox.rb', line 215 def execute(command, timeout:, context: nil, max_output_bytes: nil, **) execute_program( ["/bin/sh", "-c", String(command)], timeout:, context:, max_output_bytes:, ** ) end |
#execute_program(command, timeout:, context: nil, max_output_bytes: nil, environment: {}, inherit_environment: false, **options) ⇒ Object
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.
230 231 232 |
# File 'lib/little_ghost/sandbox.rb', line 230 def execute_program(command, timeout:, context: nil, max_output_bytes: nil, environment: {}, inherit_environment: false, **) raise AbstractMethodError, "#{self.class} does not support program execution" end |
#list(path = ".", context: nil) ⇒ Object
Lists entries at a workspace-relative or absolute virtual directory path.
196 197 198 |
# File 'lib/little_ghost/sandbox.rb', line 196 def list(path = ".", context: nil) raise AbstractMethodError, "#{self.class} does not support filesystem listings" end |
#open(run: nil) ⇒ Object
Opens any run-scoped resources and makes the sandbox ready for tools.
183 184 185 |
# File 'lib/little_ghost/sandbox.rb', line 183 def open(run: nil) self end |
#read(path, context: nil) ⇒ Object
Reads UTF-8 text at a workspace-relative or absolute virtual path.
191 192 193 |
# File 'lib/little_ghost/sandbox.rb', line 191 def read(path, context: nil) raise AbstractMethodError, "#{self.class} does not support filesystem reads" end |
#replace(path, old_text, new_text, context: nil) ⇒ Object
Replaces one exact old_text occurrence at a workspace-relative or
absolute virtual path with new_text.
207 208 209 |
# 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 |
#scope(profile = nil, files: nil, runtime_paths: nil, capabilities: nil, network: nil) ⇒ Object
Produces a non-owning capability-reduced view for tools or child agents. The caller must pass and use that Scope; retaining this parent Sandbox retains its broader authority.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# 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 |
#start_program(command, context: nil, environment: {}, inherit_environment: false, **options) ⇒ Object
Starts an owned, duplex child process for framed protocols and other interactive programs. The returned session owns the child process group.
236 237 238 |
# File 'lib/little_ghost/sandbox.rb', line 236 def start_program(command, context: nil, environment: {}, inherit_environment: false, **) raise AbstractMethodError, "#{self.class} does not support program sessions" end |
#supports?(feature, value = nil) ⇒ Boolean
Indicates whether the backend implements feature.
145 |
# File 'lib/little_ghost/sandbox.rb', line 145 def supports?(feature, value = nil) = capabilities.supports?(feature, value) |
#writable? ⇒ Boolean
Indicates whether filesystem mutation is allowed.
188 |
# File 'lib/little_ghost/sandbox.rb', line 188 def writable? = supports?(:filesystem_write) |
#write(path, content, context: nil) ⇒ Object
Writes content to a workspace-relative or absolute virtual path.
201 202 203 |
# File 'lib/little_ghost/sandbox.rb', line 201 def write(path, content, context: nil) raise AbstractMethodError, "#{self.class} does not support filesystem writes" end |