Class: LittleGhost::Sandbox::Scope
- Inherits:
-
Object
- Object
- LittleGhost::Sandbox::Scope
- Defined in:
- lib/little_ghost/sandbox/scope.rb
Overview
A non-owning, capability-reduced view of a Sandbox for one agent or Tool set. Scopes never open or close their parent and cannot widen it. They constrain only callers that receive and use the Scope; code retaining the parent Sandbox retains its broader authority.
Instance Attribute Summary collapse
-
#capabilities ⇒ Object
readonly
Operations exposed through this scope.
-
#network ⇒ Object
readonly
Outbound connectivity available to processes launched through this scope.
-
#sandbox ⇒ Object
readonly
Sandbox that enforces process execution.
Instance Method Summary collapse
-
#allows?(operation, path = nil) ⇒ Boolean
Indicates whether
operationis available at optional virtualpath. -
#close ⇒ Object
Scopes own no resources, so closing has no effect.
-
#execute(command, **options) ⇒ Object
Executes a shell command through the parent sandbox and this scope.
-
#execute_program(command, **options) ⇒ Object
Executes an argument vector through the parent sandbox and this scope.
-
#initialize(sandbox:, files: nil, runtime_paths: nil, capabilities: nil, network: nil, parent_scope: nil) ⇒ Scope
constructor
Creates a view of
sandbox. -
#list(path = ".", context: nil) ⇒ Object
Lists one directory through the scoped filesystem.
-
#open(run: nil) ⇒ Object
Scopes own no resources; opening returns the same object.
-
#policy ⇒ Object
(also: #effective_policy)
Effective policy enforced by the parent sandbox.
-
#process_grants ⇒ Object
:nodoc:.
-
#read(path, context: nil) ⇒ Object
Reads bounded UTF-8 text through the scoped filesystem.
-
#replace(path, old_text, new_text, context: nil) ⇒ Object
Replaces one unique text occurrence through a writable scoped mount.
-
#scope(**options) ⇒ Object
Produces another view that can only narrow this scope.
-
#supports?(feature, value = nil) ⇒ Boolean
Indicates whether this scope exposes
feature. -
#validate! ⇒ Object
Fails closed if a selected host mount was replaced after scope creation.
-
#workspace ⇒ Object
Workspace owned by the parent sandbox.
-
#writable? ⇒ Boolean
Indicates whether any visible mount accepts writes.
-
#write(path, content, context: nil) ⇒ Object
Writes bounded content through a writable scoped mount.
Constructor Details
#initialize(sandbox:, files: nil, runtime_paths: nil, capabilities: nil, network: nil, parent_scope: nil) ⇒ Scope
Creates a view of sandbox. mounts and capabilities may only narrow
the parent scope or sandbox.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/little_ghost/sandbox/scope.rb', line 12 def initialize(sandbox:, files: nil, runtime_paths: nil, capabilities: nil, network: nil, parent_scope: nil) @sandbox = sandbox @parent_mounts = parent_scope&.process_grants || sandbox.effective_policy.process_grants(sandbox.workspace) @parent_tool_denies = if parent_scope parent_scope.send(:tool_deny_mounts) else @parent_mounts.reject(&:tool_visible?) end requested = scoped_mounts(files, runtime_paths) requested_mounts = requested ? (normalize_mounts(requested)) : @parent_mounts @mounts = validate_mounts(requested_mounts).sort_by { |mount| -mount.target.length }.freeze @tool_deny_mounts = relevant_tool_denies(@mounts).freeze parent_capabilities = parent_scope&.capabilities || sandbox.capabilities requested_capabilities = capabilities || parent_capabilities requested_capabilities = Capabilities.new(features: requested_capabilities) unless requested_capabilities.is_a?(Capabilities) @capabilities = parent_capabilities.intersect(requested_capabilities) @network = narrow_network(parent_scope&.network || sandbox.effective_policy.network, network) if @network unless parent_capabilities.supports?(:network, @network.mode) raise CapabilityError, "sandbox backend cannot enforce scoped network mode :#{@network.mode}" end @capabilities = @capabilities.intersect(Capabilities.new( features: @capabilities.features, network_modes: [@network.mode], isolation: @capabilities.isolation )) end @filesystem = Filesystem.new( mounts: (@mounts + @tool_deny_mounts).uniq, relative_root: sandbox.workspace.root, max_read_bytes: sandbox.limits.read_bytes, max_write_bytes: sandbox.limits.write_bytes, max_list_entries: sandbox.limits.list_entries ) @mount_identities = mount_identities end |
Instance Attribute Details
#capabilities ⇒ Object (readonly)
Operations exposed through this scope.
52 53 54 |
# File 'lib/little_ghost/sandbox/scope.rb', line 52 def capabilities @capabilities end |
#network ⇒ Object (readonly)
Outbound connectivity available to processes launched through this scope.
54 55 56 |
# File 'lib/little_ghost/sandbox/scope.rb', line 54 def network @network end |
#sandbox ⇒ Object (readonly)
Sandbox that enforces process execution.
50 51 52 |
# File 'lib/little_ghost/sandbox/scope.rb', line 50 def sandbox @sandbox end |
Instance Method Details
#allows?(operation, path = nil) ⇒ Boolean
Indicates whether operation is available at optional virtual path.
71 72 73 74 75 76 77 78 |
# File 'lib/little_ghost/sandbox/scope.rb', line 71 def allows?(operation, path = nil) operation = Capabilities.normalize(operation) return supports?(operation) unless path @filesystem.allows?(operation, absolute_path(path)) rescue PolicyError, ToolError false end |
#close ⇒ Object
Scopes own no resources, so closing has no effect.
121 |
# File 'lib/little_ghost/sandbox/scope.rb', line 121 def close = nil |
#execute(command, **options) ⇒ Object
Executes a shell command through the parent sandbox and this scope.
105 106 107 108 |
# File 'lib/little_ghost/sandbox/scope.rb', line 105 def execute(command, **) require_capability!(:process_execute) sandbox.execute(command, scope: self, **) end |
#execute_program(command, **options) ⇒ Object
Executes an argument vector through the parent sandbox and this scope.
111 112 113 114 |
# File 'lib/little_ghost/sandbox/scope.rb', line 111 def execute_program(command, **) require_capability!(:process_execute) sandbox.execute_program(command, scope: self, **) end |
#list(path = ".", context: nil) ⇒ Object
Lists one directory through the scoped filesystem.
87 88 89 90 |
# File 'lib/little_ghost/sandbox/scope.rb', line 87 def list(path = ".", context: nil) require_capability!(:filesystem_list) @filesystem.list(logical_path(path), context:) end |
#open(run: nil) ⇒ Object
Scopes own no resources; opening returns the same object.
119 120 |
# File 'lib/little_ghost/sandbox/scope.rb', line 119 def open(run: nil) = self # Scopes own no resources, so closing has no effect. |
#policy ⇒ Object Also known as: effective_policy
Effective policy enforced by the parent sandbox.
59 60 |
# File 'lib/little_ghost/sandbox/scope.rb', line 59 def policy = sandbox.effective_policy # Effective policy enforced by the parent sandbox. |
#process_grants ⇒ Object
:nodoc:
68 |
# File 'lib/little_ghost/sandbox/scope.rb', line 68 def process_grants = @mounts # :nodoc: |
#read(path, context: nil) ⇒ Object
Reads bounded UTF-8 text through the scoped filesystem.
81 82 83 84 |
# File 'lib/little_ghost/sandbox/scope.rb', line 81 def read(path, context: nil) require_capability!(:filesystem_read) @filesystem.read(logical_path(path), context:) end |
#replace(path, old_text, new_text, context: nil) ⇒ Object
Replaces one unique text occurrence through a writable scoped mount.
99 100 101 102 |
# File 'lib/little_ghost/sandbox/scope.rb', line 99 def replace(path, old_text, new_text, context: nil) require_capability!(:filesystem_replace) @filesystem.replace(logical_path(path), old_text, new_text, context:) end |
#scope(**options) ⇒ Object
Produces another view that can only narrow this scope.
117 118 |
# File 'lib/little_ghost/sandbox/scope.rb', line 117 def scope(**) = self.class.new(sandbox:, parent_scope: self, **) # Scopes own no resources; opening returns the same object. |
#supports?(feature, value = nil) ⇒ Boolean
Indicates whether this scope exposes feature.
64 65 |
# File 'lib/little_ghost/sandbox/scope.rb', line 64 def supports?(feature, value = nil) = capabilities.supports?(feature, value) # Indicates whether any visible mount accepts writes. |
#validate! ⇒ Object
Fails closed if a selected host mount was replaced after scope creation.
124 125 126 127 128 129 130 131 |
# File 'lib/little_ghost/sandbox/scope.rb', line 124 def validate! current = mount_identities unless current == @mount_identities raise CapabilityError, "sandbox scope mount source changed after initialization" end self end |
#workspace ⇒ Object
Workspace owned by the parent sandbox.
57 58 |
# File 'lib/little_ghost/sandbox/scope.rb', line 57 def workspace = sandbox.workspace # Effective policy enforced by the parent sandbox. |
#writable? ⇒ Boolean
Indicates whether any visible mount accepts writes.
66 |
# File 'lib/little_ghost/sandbox/scope.rb', line 66 def writable? = supports?(:filesystem_write) && @mounts.any? { |mount| mount.tool_visible? && mount.writable? } |
#write(path, content, context: nil) ⇒ Object
Writes bounded content through a writable scoped mount.
93 94 95 96 |
# File 'lib/little_ghost/sandbox/scope.rb', line 93 def write(path, content, context: nil) require_capability!(:filesystem_write) @filesystem.write(logical_path(path), content, context:) end |