Class: LanguageOperator::Agent::Safety::SafeExecutor

Inherits:
Object
  • Object
show all
Defined in:
lib/language_operator/agent/safety/safe_executor.rb

Overview

Executes Ruby code in a sandboxed context with method whitelisting Wraps the execution context to prevent dangerous method calls at runtime

Defined Under Namespace

Classes: SandboxProxy, SecurityError

Constant Summary collapse

ALWAYS_SAFE_METHODS =

Methods that are always safe to call

%i[
  nil? == != eql? equal? hash object_id class is_a? kind_of? instance_of?
  respond_to? send_to? methods public_methods private_methods
  instance_variables instance_variable_get instance_variable_set
  to_s to_str inspect to_a to_h to_i to_f to_sym
  freeze frozen? dup clone
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, validator: nil) ⇒ SafeExecutor

Returns a new instance of SafeExecutor.



20
21
22
23
24
# File 'lib/language_operator/agent/safety/safe_executor.rb', line 20

def initialize(context, validator: nil)
  @context = context
  @validator = validator || ASTValidator.new
  @audit_log = []
end

Instance Attribute Details

#audit_logObject (readonly)

Get audit log



74
75
76
# File 'lib/language_operator/agent/safety/safe_executor.rb', line 74

def audit_log
  @audit_log
end

Instance Method Details

#eval(code, file_path = '(eval)') ⇒ Object

Execute code in the sandboxed context

Parameters:

  • Ruby code to execute

  • (defaults to: '(eval)')

    Path to file (for error reporting)

Returns:

  • Result of code execution



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/language_operator/agent/safety/safe_executor.rb', line 30

def eval(code, file_path = '(eval)')
  # Step 1: Validate code with AST analysis
  @validator.validate!(code, file_path)

  # Step 2: Execute in sandboxed context
  sandbox = SandboxProxy.new(@context, self)

  # Step 3: Execute using instance_eval with smart constant injection
  # Only inject constants that won't conflict with user-defined ones
  safe_constants = %w[Numeric Integer Float String Array Hash TrueClass FalseClass Time Date
                      ArgumentError TypeError RuntimeError StandardError]

  # Find which constants user code defines to avoid redefinition warnings
  user_defined_constants = safe_constants.select { |const| code.include?("#{const} =") }

  # Only inject constants that user code doesn't define
  constants_to_inject = safe_constants - user_defined_constants

  if constants_to_inject.any?
    # Inject only safe constants that won't conflict
    safe_setup = constants_to_inject.map { |name| "#{name} = ::#{name}" }.join("\n")
    # rubocop:disable Style/DocumentDynamicEvalDefinition
    sandbox.instance_eval("#{safe_setup}\n#{code}", __FILE__, __LINE__)
    # rubocop:enable Style/DocumentDynamicEvalDefinition
  else
    # User defines all constants, just run their code
    sandbox.instance_eval(code, file_path, 1)
  end
rescue ASTValidator::SecurityError => e
  # Re-raise validation errors as executor errors for clarity
  raise SecurityError, "Code validation failed: #{e.message}"
end

#log_call(receiver, method_name, args) ⇒ Object

Log method calls for auditing



64
65
66
67
68
69
70
71
# File 'lib/language_operator/agent/safety/safe_executor.rb', line 64

def log_call(receiver, method_name, args)
  @audit_log << {
    timestamp: Time.now,
    receiver: receiver.class.name,
    method: method_name,
    args: args.map(&:class).map(&:name)
  }
end