Class: Kobako::SandboxOptions

Inherits:
Data
  • Object
show all
Defined in:
lib/kobako/sandbox_options.rb,
sig/kobako/sandbox_options.rbs

Overview

Kobako::SandboxOptions — immutable Value Object holding the four per-Sandbox configuration caps and the requested isolation profile. Built on the class X < Data.define(...) subclass form (the Steep-friendly shape — see lib/kobako/outcome/panic.rb).

The initialize normalises every option before delegating to Data's super: timeout to Float seconds, memory_limit / stdout_limit / stderr_limit to positive Integer bytes. Each cap is +nil+-disablable (an absent argument takes its DEFAULT; an explicit nil leaves the bound off), so all four behave uniformly. profile is the one non-cap: a Symbol on the PROFILES ladder naming the posture the runtime builds, which is also the weakest the Host App accepts — nil is rejected because the weakest posture is requested as an explicit :permissive. Anything that survives SandboxOptions.new is a wire-ready bundle the Kobako::Runtime constructor consumes as-is. The options also own the ladder comparison (+#enforce_floor!+) that Kobako::Sandbox delegates its construction floor check to.

Constant Summary collapse

DEFAULT_TIMEOUT_SECONDS =

Default wall-clock timeout for a single invocation: 60 seconds.

Returns:

  • (Float)
60.0
DEFAULT_MEMORY_LIMIT =

Default cap on the per-invocation guest linear-memory delta: 1 MiB. The mruby image's initial allocation and prior invocations' watermark sit outside this budget.

Returns:

  • (Integer)
1 << 20
DEFAULT_OUTPUT_LIMIT =

Default per-channel capture ceiling: 1 MiB.

Returns:

  • (Integer)
1 << 20
PROFILES =

The isolation ladder, weakest first — index order is rank order, so a floor check is an index comparison.

Returns:

  • (Array[Symbol])
%i[permissive hermetic].freeze
DEFAULT_PROFILE =

Default isolation profile: the strictest rung — opting down to :permissive is the Host App's explicit trade.

Returns:

  • (Symbol)
:hermetic

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(timeout: DEFAULT_TIMEOUT_SECONDS, memory_limit: DEFAULT_MEMORY_LIMIT, stdout_limit: DEFAULT_OUTPUT_LIMIT, stderr_limit: DEFAULT_OUTPUT_LIMIT, profile: DEFAULT_PROFILE) ⇒ SandboxOptions

Returns a new instance of SandboxOptions.



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/kobako/sandbox_options.rb', line 44

def initialize(timeout: DEFAULT_TIMEOUT_SECONDS,
               memory_limit: DEFAULT_MEMORY_LIMIT,
               stdout_limit: DEFAULT_OUTPUT_LIMIT,
               stderr_limit: DEFAULT_OUTPUT_LIMIT,
               profile: DEFAULT_PROFILE)
  timeout = normalize_timeout(timeout)
  memory_limit = normalize_memory_limit(memory_limit)
  stdout_limit = normalize_output_limit(stdout_limit, "stdout_limit")
  stderr_limit = normalize_output_limit(stderr_limit, "stderr_limit")
  profile = normalize_profile(profile)
  super
end

Instance Attribute Details

#memory_limitInteger? (readonly)

Returns the value of attribute memory_limit.

Returns:

  • (Integer, nil)


10
11
12
# File 'sig/kobako/sandbox_options.rbs', line 10

def memory_limit
  @memory_limit
end

#profileSymbol (readonly)

Returns the value of attribute profile.

Returns:

  • (Symbol)


13
14
15
# File 'sig/kobako/sandbox_options.rbs', line 13

def profile
  @profile
end

#stderr_limitInteger? (readonly)

Returns the value of attribute stderr_limit.

Returns:

  • (Integer, nil)


12
13
14
# File 'sig/kobako/sandbox_options.rbs', line 12

def stderr_limit
  @stderr_limit
end

#stdout_limitInteger? (readonly)

Returns the value of attribute stdout_limit.

Returns:

  • (Integer, nil)


11
12
13
# File 'sig/kobako/sandbox_options.rbs', line 11

def stdout_limit
  @stdout_limit
end

#timeoutFloat? (readonly)

Returns the value of attribute timeout.

Returns:

  • (Float, nil)


9
10
11
# File 'sig/kobako/sandbox_options.rbs', line 9

def timeout
  @timeout
end

Class Method Details

.newObject



15
# File 'sig/kobako/sandbox_options.rbs', line 15

def self.new: (

Instance Method Details

#enforce_floor!(declared) ⇒ void

This method returns an undefined value.

Enforce the requested profile as the floor against declared — the posture a runtime reports having built — so a runtime that cannot honor the request fails construction with Kobako::SetupError instead of weakening the posture silently. Both fallbacks fail closed: a declaration off the PROFILES ladder ranks below every request, and a request off the ladder (unreachable past initialize) refuses every declaration.

Parameters:

  • declared (Symbol)

Raises:



64
65
66
67
68
69
# File 'lib/kobako/sandbox_options.rb', line 64

def enforce_floor!(declared)
  return if (PROFILES.index(declared) || -1) >= (PROFILES.index(profile) || PROFILES.size)

  raise Kobako::SetupError, "runtime declares isolation profile #{declared.inspect}, " \
                            "below the requested floor #{profile.inspect}"
end

#normalize_memory_limit(memory_limit) ⇒ Integer?

Coerce memory_limit into the byte cap the ext expects, or nil to mean unbounded. Must be a positive Integer when set; Float or zero/negative values are rejected.

Parameters:

  • memory_limit (Integer, nil)

Returns:

  • (Integer, nil)


91
92
93
94
95
96
97
98
# File 'lib/kobako/sandbox_options.rb', line 91

def normalize_memory_limit(memory_limit)
  return nil if memory_limit.nil?
  unless memory_limit.is_a?(Integer) && memory_limit.positive?
    raise ArgumentError, "memory_limit must be a positive Integer or nil, got #{memory_limit.inspect}"
  end

  memory_limit
end

#normalize_output_limit(limit, name) ⇒ Integer?

Coerce a per-channel output cap (+stdout_limit+ / stderr_limit) into the byte cap the ext expects, or nil to leave the channel uncapped. Same shape as normalize_memory_limit: a positive Integer when set, Float / zero / negative rejected. name tags the ArgumentError with the offending keyword.

Parameters:

  • limit (Integer, nil)
  • name (String)

Returns:

  • (Integer, nil)


105
106
107
108
109
110
111
112
# File 'lib/kobako/sandbox_options.rb', line 105

def normalize_output_limit(limit, name)
  return nil if limit.nil?
  unless limit.is_a?(Integer) && limit.positive?
    raise ArgumentError, "#{name} must be a positive Integer or nil, got #{limit.inspect}"
  end

  limit
end

#normalize_profile(profile) ⇒ Symbol

Validate profile against the PROFILES ladder. Unlike the caps there is no nil form: the weakest posture is requested as an explicit :permissive, so anything off the ladder — nil included — is rejected.

Parameters:

  • profile (Object)

Returns:

  • (Symbol)

Raises:

  • (ArgumentError)


118
119
120
121
122
# File 'lib/kobako/sandbox_options.rb', line 118

def normalize_profile(profile)
  return profile if PROFILES.include?(profile)

  raise ArgumentError, "profile must be one of #{PROFILES.map(&:inspect).join(", ")}, got #{profile.inspect}"
end

#normalize_timeout(timeout) ⇒ Float?

Coerce timeout into the Float seconds the ext expects, or nil to mean the cap is disabled. Any finite non-positive value is rejected — a zero or negative timeout would either fire instantly or never, both of which would surprise callers more than an early ArgumentError.

Parameters:

  • timeout (Float, Integer, nil)

Returns:

  • (Float, nil)

Raises:

  • (ArgumentError)


78
79
80
81
82
83
84
85
86
# File 'lib/kobako/sandbox_options.rb', line 78

def normalize_timeout(timeout)
  return nil if timeout.nil?
  raise ArgumentError, "timeout must be Numeric or nil, got #{timeout.class}" unless timeout.is_a?(Numeric)

  seconds = timeout.to_f
  raise ArgumentError, "timeout must be > 0 (got #{timeout})" unless seconds.positive? && seconds.finite?

  seconds
end