Class: LittleGhost::Sandbox::Policy

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/sandbox/policy.rb

Overview

Normalizes requested filesystem, process, environment, and child-network controls into one immutable policy. Policy is a declaration, not proof of isolation; the selected backend reports the policy it actually enforces and rejects controls it cannot enforce.

When environment is omitted, child processes receive a scrubbed locale and path baseline without inheriting other host values. An explicit Hash or EnvironmentPolicy replaces that baseline, including an empty Hash. Workspace routing values are added separately when a process starts.

policy = LittleGhost::Sandbox::Policy.new(
files: {root: :read_write},
network: :none
)

policy.root_filesystem       # => :isolated
policy.environment.inherit?  # => false

Constant Summary collapse

COMMON_KEYS =
%i[
  files runtime_paths root_filesystem environment network
].freeze
ACCESS_MODES =

:nodoc:

%i[read_only read_write].freeze
ROOT_FILESYSTEM_MODES =

:nodoc:

%i[isolated read_only read_write].freeze
DEFAULT_ENVIRONMENT =

:nodoc:

Object.new.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(files: {root: :read_only}, runtime_paths: {}, root_filesystem: :isolated, environment: DEFAULT_ENVIRONMENT, network: nil) ⇒ Policy

:call-seq:

new(files: {root: :read_only}, runtime_paths: {}, root_filesystem: :isolated, network: nil)
new(files: {root: :read_only}, runtime_paths: {}, root_filesystem: :isolated, environment: value, network: nil)

Builds a backend-independent policy from named Workspace paths.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/little_ghost/sandbox/policy.rb', line 43

def initialize(
  files: {root: :read_only},
  runtime_paths: {},
  root_filesystem: :isolated,
  environment: DEFAULT_ENVIRONMENT,
  network: nil
)
  @files = normalize_paths(files, "files")
  @runtime_paths = normalize_paths(runtime_paths, "runtime_paths")
  @root_filesystem = enum!(root_filesystem, ROOT_FILESYSTEM_MODES, "root filesystem")
  @environment = if environment.equal?(DEFAULT_ENVIRONMENT)
    EnvironmentPolicy.default
  else
    EnvironmentPolicy.coerce(environment)
  end
  @network = NetworkPolicy.coerce(network)
  freeze
end

Instance Attribute Details

#environmentObject (readonly)

Environment inheritance and values requested for child processes.



69
70
71
# File 'lib/little_ghost/sandbox/policy.rb', line 69

def environment
  @environment
end

#filesObject (readonly)

Named Workspace paths visible to tools and child processes.



63
64
65
# File 'lib/little_ghost/sandbox/policy.rb', line 63

def files
  @files
end

#networkObject (readonly)

Network policy, or nil for the backend-specific default.



71
72
73
# File 'lib/little_ghost/sandbox/policy.rb', line 71

def network
  @network
end

#root_filesystemObject (readonly)

Requested host-root access: :isolated, :read_only, or :read_write.



67
68
69
# File 'lib/little_ghost/sandbox/policy.rb', line 67

def root_filesystem
  @root_filesystem
end

#runtime_pathsObject (readonly)

Named workspace paths visible only to sandboxed processes.



65
66
67
# File 'lib/little_ghost/sandbox/policy.rb', line 65

def runtime_paths
  @runtime_paths
end

Class Method Details

.coerce(value = nil, **options) ⇒ Object

Returns an existing policy or builds one from a Hash and keyword options.

Raises:



30
31
32
33
34
35
36
# File 'lib/little_ghost/sandbox/policy.rb', line 30

def self.coerce(value = nil, **options)
  return value if value.is_a?(self) && options.empty?
  values = value.nil? ? {} : value
  raise PolicyError, "sandbox policy must be a Hash or Sandbox::Policy" unless values.is_a?(Hash)

  new(**values.transform_keys(&:to_sym).merge(options))
end

Instance Method Details

#process_grants(workspace) ⇒ Object

Builds internal identity grants for a concrete Workspace.



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/little_ghost/sandbox/policy.rb', line 76

def process_grants(workspace) # :nodoc:
  file_mounts = files.map do |name, access|
    source = workspace_directory(workspace, name)
    Mount.new(source:, target: source, access:, protect_aliases: true)
  end
  process_mounts = runtime_paths.map do |name, access|
    source = workspace_directory(workspace, name)
    Mount.new(source:, target: source, access:, protect_aliases: true, tools: false)
  end
  (file_mounts + process_mounts).sort_by { |mount| -mount.target.length }.freeze
end

#workspace_writable?Boolean

Whether the :root entry in files requests :read_write access.

Returns:

  • (Boolean)


73
# File 'lib/little_ghost/sandbox/policy.rb', line 73

def workspace_writable? = files.fetch(:root, :read_only) == :read_write