Class: LittleGhost::Sandboxes::Unrestricted
- Inherits:
-
LittleGhost::Sandbox
- Object
- LittleGhost::Sandbox
- LittleGhost::Sandboxes::Unrestricted
- Defined in:
- lib/little_ghost/sandboxes/unrestricted.rb
Overview
A convenient host-backed sandbox for trusted local work. It offers bounded text-file operations and command execution using only Ruby's standard library.
workspace = LittleGhost::Workspace.new(root: Dir.pwd)
sandbox = LittleGhost::Sandboxes::Unrestricted.new(workspace:)
sandbox.read("README.md").lines.first # => "# LittleGhost\n"
Reads return valid UTF-8 text. Writes preserve the supplied String bytes. Paths may be relative to the workspace or absolute within a declared virtual mount. Traversal components are rejected, and every path is checked against its configured mount root.
Security and trust
This sandbox is not a security boundary. Commands run directly on the host with the Ruby process's permissions, and filesystem containment cannot defend against concurrent adversarial mutation. Use an isolated Sandbox implementation for untrusted work.
Instance Attribute Summary collapse
-
#effective_policy ⇒ Object
readonly
Reports the host permissions this backend actually uses.
Attributes inherited from LittleGhost::Sandbox
Instance Method Summary collapse
-
#capabilities ⇒ Object
Reports host execution and the bounded filesystem operations exposed by this instance.
-
#execute_program(command, timeout:, context: nil, max_output_bytes: nil, environment: {}, inherit_environment: false, scope: nil) ⇒ Object
Executes an argument vector on the host from the workspace root.
-
#initialize(workspace:, policy: nil, profiles: {}, limits: {}) ⇒ Unrestricted
constructor
Configures a host sandbox with an explicit policy and resource limits.
-
#list(path = ".", context: nil) ⇒ Object
Produces a newline-delimited, sorted directory listing.
-
#open(run: nil) ⇒ Object
Opens the sandbox and verifies that the workspace root has not changed.
-
#read(path, context: nil) ⇒ Object
Reads a bounded UTF-8 file within the workspace.
-
#replace(path, old_text, new_text, context: nil) ⇒ Object
Replaces exactly one occurrence of
old_textin a writable file. -
#start_program(command, context: nil, environment: {}, inherit_environment: false, scope: nil, cwd: nil, output_bytes: nil, memory_bytes: nil, cpu_seconds: nil, file_bytes: nil, allow_subprocesses: true) ⇒ Object
Starts a bounded host process.
-
#writable? ⇒ Boolean
Indicates whether this sandbox accepts filesystem mutations.
-
#write(path, content, context: nil) ⇒ Object
Writes a bounded String without following a symbolic-link target.
Methods inherited from LittleGhost::Sandbox
#allows?, #close, #execute, probe, providers, register_provider, resolve_provider, #scope, #supports?
Constructor Details
#initialize(workspace:, policy: nil, profiles: {}, limits: {}) ⇒ Unrestricted
Configures a host sandbox with an explicit policy and resource limits.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 29 def initialize(workspace:, policy: nil, profiles: {}, limits: {}) configured_policy = policy || Sandbox::Policy.new(files: {root: :read_only}, root_filesystem: :read_write, network: :inherit) super(workspace:, policy: configured_policy, profiles:, limits:) policy = self.policy unless policy.network.nil? || policy.network.inherit? raise CapabilityError, "the unrestricted sandbox cannot enforce network mode :#{policy.network.mode}" end @effective_policy = Sandbox::Policy.new( files: policy.files, runtime_paths: policy.runtime_paths, root_filesystem: :read_write, environment: policy.environment, network: :inherit ) @writable = effective_policy.process_grants(workspace).any?(&:writable?) @root = File.(workspace.root) capture_root_identity if File.exist?(@root) end |
Instance Attribute Details
#effective_policy ⇒ Object (readonly)
Reports the host permissions this backend actually uses. In particular, unrestricted execution cannot make the host root filesystem read-only.
64 65 66 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 64 def effective_policy @effective_policy end |
Instance Method Details
#capabilities ⇒ Object
Reports host execution and the bounded filesystem operations exposed by
this instance. isolation: :none is deliberate: unrestricted execution
is not a security boundary.
69 70 71 72 73 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 69 def capabilities features = %i[filesystem_read filesystem_list process_execute process_spawn] features.concat(%i[filesystem_write filesystem_replace]) if writable? Sandbox::Capabilities.new(features:, network_modes: [:inherit], isolation: :none) end |
#execute_program(command, timeout:, context: nil, max_output_bytes: nil, environment: {}, inherit_environment: false, scope: nil) ⇒ Object
Executes an argument vector on the host from the workspace root.
Shell syntax is not interpreted. The child starts with an empty environment
unless inherit_environment is true, is terminated when the context is
cancelled or the timeout expires, and has each output stream truncated to
max_output_bytes.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 94 def execute_program( command, timeout:, context: nil, max_output_bytes: nil, environment: {}, inherit_environment: false, scope: nil ) argv = Array(command).map(&:to_s) raise ToolError, "Command must contain an executable" if argv.empty? || argv.first.empty? timeout = Float(timeout) max_output_bytes = Integer(max_output_bytes || limits.output_bytes) raise ArgumentError, "timeout must be positive" unless timeout.positive? raise ArgumentError, "max_output_bytes must be positive" unless max_output_bytes.positive? stdout, stderr, status = capture( argv, timeout:, context:, max_output_bytes:, environment: workspace.environment.merge(effective_policy.environment.values).merge(environment), inherit_environment: effective_policy.environment.inherit? && inherit_environment ) Sandbox::Execution.new(stdout:, stderr:, exit_code: status.exitstatus) end |
#list(path = ".", context: nil) ⇒ Object
Produces a newline-delimited, sorted directory listing. Directories end in
/.
80 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 80 def list(path = ".", context: nil) = default_scope.list(path, context:) |
#open(run: nil) ⇒ Object
Opens the sandbox and verifies that the workspace root has not changed.
49 50 51 52 53 54 55 56 57 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 49 def open(run: nil) if @root_identity validate_root! else @root = File.realpath(workspace.root) capture_root_identity end self end |
#read(path, context: nil) ⇒ Object
Reads a bounded UTF-8 file within the workspace.
76 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 76 def read(path, context: nil) = default_scope.read(path, context:) |
#replace(path, old_text, new_text, context: nil) ⇒ Object
Replaces exactly one occurrence of old_text in a writable file.
86 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 86 def replace(path, old_text, new_text, context: nil) = default_scope.replace(path, old_text, new_text, context:) |
#start_program(command, context: nil, environment: {}, inherit_environment: false, scope: nil, cwd: nil, output_bytes: nil, memory_bytes: nil, cpu_seconds: nil, file_bytes: nil, allow_subprocesses: true) ⇒ Object
Starts a bounded host process. This remains unrestricted host execution, not a containment boundary.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 124 def start_program(command, context: nil, environment: {}, inherit_environment: false, scope: nil, cwd: nil, output_bytes: nil, memory_bytes: nil, cpu_seconds: nil, file_bytes: nil, allow_subprocesses: true) unless allow_subprocesses raise CapabilityError, "the unrestricted sandbox cannot prohibit subprocess creation" end Sandbox::ProcessSession.new( command:, environment: workspace.environment.merge(effective_policy.environment.values).merge(environment), inherit_environment: effective_policy.environment.inherit? && inherit_environment, chdir: cwd ? workspace.resolve(cwd) : workspace.root, output_bytes: output_bytes || limits.output_bytes, memory_bytes:, cpu_seconds:, file_bytes: ) end |
#writable? ⇒ Boolean
Indicates whether this sandbox accepts filesystem mutations.
60 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 60 def writable? = @writable |
#write(path, content, context: nil) ⇒ Object
Writes a bounded String without following a symbolic-link target.
83 |
# File 'lib/little_ghost/sandboxes/unrestricted.rb', line 83 def write(path, content, context: nil) = default_scope.write(path, content, context:) |